prizmkit 1.1.153 → 1.1.155

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.
Files changed (178) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/codex/skill-adapter.js +1 -1
  3. package/bundled/adapters/pi/skill-adapter.js +2 -1
  4. package/bundled/dev-pipeline/.env.example +2 -6
  5. package/bundled/dev-pipeline/README.md +120 -113
  6. package/bundled/dev-pipeline/assets/skill-subagent-integration.md +1 -1
  7. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +355 -15
  8. package/bundled/dev-pipeline/prizmkit_runtime/cli.py +189 -110
  9. package/bundled/dev-pipeline/prizmkit_runtime/commands.py +146 -111
  10. package/bundled/dev-pipeline/prizmkit_runtime/daemon.py +4 -20
  11. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +48 -14
  12. package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +0 -1
  13. package/bundled/dev-pipeline/prizmkit_runtime/paths.py +0 -3
  14. package/bundled/dev-pipeline/prizmkit_runtime/reset.py +122 -15
  15. package/bundled/dev-pipeline/prizmkit_runtime/reset_preserve.py +1 -1
  16. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +50 -2
  17. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +1 -10
  18. package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +3 -3
  19. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +139 -74
  20. package/bundled/dev-pipeline/prizmkit_runtime/runtime_commit.py +482 -0
  21. package/bundled/dev-pipeline/prizmkit_runtime/status.py +106 -1
  22. package/bundled/dev-pipeline/prizmkit_runtime/test_result.py +64 -11
  23. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +145 -367
  24. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +91 -62
  25. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +105 -69
  26. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +2 -2
  27. package/bundled/dev-pipeline/scripts/prompt_framework.py +36 -41
  28. package/bundled/dev-pipeline/scripts/update-bug-status.py +197 -71
  29. package/bundled/dev-pipeline/scripts/update-checkpoint.py +77 -16
  30. package/bundled/dev-pipeline/scripts/update-feature-status.py +46 -121
  31. package/bundled/dev-pipeline/scripts/update-refactor-status.py +46 -123
  32. package/bundled/dev-pipeline/scripts/utils.py +119 -0
  33. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +22 -27
  34. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +71 -104
  35. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +3 -2
  36. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +59 -111
  37. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +37 -72
  38. package/bundled/dev-pipeline/templates/sections/artifact-manifest.md +39 -0
  39. package/bundled/dev-pipeline/templates/sections/bugfix-critical-paths.md +3 -1
  40. package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +8 -25
  41. package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +3 -3
  42. package/bundled/dev-pipeline/templates/sections/bugfix-phase-implement.md +2 -2
  43. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +3 -3
  44. package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +2 -2
  45. package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +4 -4
  46. package/bundled/dev-pipeline/templates/sections/bugfix-session-context.md +0 -1
  47. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +7 -4
  48. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +14 -27
  49. package/bundled/dev-pipeline/templates/sections/feature-completion-summary.md +20 -0
  50. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +8 -71
  51. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +7 -7
  52. package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +3 -3
  53. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +4 -4
  54. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +5 -17
  55. package/bundled/dev-pipeline/templates/sections/refactor-critical-paths.md +3 -1
  56. package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +10 -23
  57. package/bundled/dev-pipeline/templates/sections/refactor-phase-implement.md +2 -2
  58. package/bundled/dev-pipeline/templates/sections/refactor-phase-plan.md +2 -2
  59. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +3 -3
  60. package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +2 -2
  61. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +7 -7
  62. package/bundled/dev-pipeline/templates/sections/refactor-session-context.md +0 -1
  63. package/bundled/dev-pipeline/templates/sections/retrospective.md +12 -0
  64. package/bundled/dev-pipeline/templates/sections/runtime-commit-handoff.md +35 -0
  65. package/bundled/dev-pipeline/templates/session-status-schema.json +45 -62
  66. package/bundled/dev-pipeline/tests/test_auto_skip.py +111 -32
  67. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +617 -30
  68. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +262 -194
  69. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +62 -23
  70. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +76 -28
  71. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +281 -206
  72. package/bundled/dev-pipeline/tests/test_recovery_workflow.py +322 -0
  73. package/bundled/dev-pipeline/tests/test_reset_modes.py +935 -0
  74. package/bundled/dev-pipeline/tests/test_reset_preserve.py +5 -7
  75. package/bundled/dev-pipeline/tests/test_runtime_commit.py +608 -0
  76. package/bundled/dev-pipeline/tests/test_unified_cli.py +617 -188
  77. package/bundled/skills/_metadata.json +9 -9
  78. package/bundled/skills/app-planner/SKILL.md +3 -3
  79. package/bundled/skills/app-planner/references/generated-plan-review.md +2 -1
  80. package/bundled/skills/app-planner/references/rules/backend/derivation-rules.md +56 -64
  81. package/bundled/skills/app-planner/references/rules/backend/fixed-rules.md +85 -240
  82. package/bundled/skills/app-planner/references/rules/backend/question-bank.md +25 -25
  83. package/bundled/skills/app-planner/references/rules/backend/question-manifest.json +16 -7
  84. package/bundled/skills/app-planner/references/rules/backend/template.md +74 -92
  85. package/bundled/skills/app-planner/references/rules/database/derivation-rules.md +93 -93
  86. package/bundled/skills/app-planner/references/rules/database/fixed-rules.md +66 -173
  87. package/bundled/skills/app-planner/references/rules/database/question-bank.md +31 -26
  88. package/bundled/skills/app-planner/references/rules/database/question-manifest.json +13 -4
  89. package/bundled/skills/app-planner/references/rules/database/template.md +76 -98
  90. package/bundled/skills/app-planner/references/rules/frontend/derivation-rules.md +68 -95
  91. package/bundled/skills/app-planner/references/rules/frontend/fixed-rules.md +93 -166
  92. package/bundled/skills/app-planner/references/rules/frontend/question-bank.md +46 -36
  93. package/bundled/skills/app-planner/references/rules/frontend/question-manifest.json +11 -2
  94. package/bundled/skills/app-planner/references/rules/frontend/template.md +79 -257
  95. package/bundled/skills/app-planner/references/rules/mobile/derivation-rules.md +91 -99
  96. package/bundled/skills/app-planner/references/rules/mobile/fixed-rules.md +73 -246
  97. package/bundled/skills/app-planner/references/rules/mobile/question-bank.md +17 -17
  98. package/bundled/skills/app-planner/references/rules/mobile/question-manifest.json +17 -2
  99. package/bundled/skills/app-planner/references/rules/mobile/template.md +73 -85
  100. package/bundled/skills/app-planner/references/rules-configuration.md +133 -65
  101. package/bundled/skills/app-planner/scripts/validate-rules-configuration.py +291 -0
  102. package/bundled/skills/bug-planner/references/schema-validation.md +1 -1
  103. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +1 -1
  104. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -13
  105. package/bundled/skills/feature-pipeline-launcher/SKILL.md +22 -14
  106. package/bundled/skills/prizmkit/SKILL.md +17 -18
  107. package/bundled/skills/prizmkit/references/workflow-state-protocol.md +22 -18
  108. package/bundled/skills/prizmkit-code-review/SKILL.md +22 -58
  109. package/bundled/skills/prizmkit-code-review/references/independent-code-review.md +6 -6
  110. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +2 -2
  111. package/bundled/skills/prizmkit-committer/SKILL.md +82 -132
  112. package/bundled/skills/prizmkit-deploy/SKILL.md +27 -11
  113. package/bundled/skills/prizmkit-deploy/assets/deploy-document-template.md +89 -0
  114. package/bundled/skills/prizmkit-deploy/references/ci-cd-workflows.md +44 -14
  115. package/bundled/skills/prizmkit-deploy/references/cloud-platform-deploy.md +19 -3
  116. package/bundled/skills/prizmkit-deploy/references/database-setup.md +22 -12
  117. package/bundled/skills/prizmkit-deploy/references/deploy-config-schema.md +3 -1
  118. package/bundled/skills/prizmkit-deploy/references/deploy-history-schema.md +22 -6
  119. package/bundled/skills/prizmkit-deploy/references/deploy-metadata-schema.json +49 -0
  120. package/bundled/skills/prizmkit-deploy/references/live-validation-notes.md +1 -1
  121. package/bundled/skills/prizmkit-deploy/references/pending-input-schema.json +164 -0
  122. package/bundled/skills/prizmkit-deploy/references/ssh-adapter-flow.md +9 -8
  123. package/bundled/skills/prizmkit-deploy/references/ssh-execution-flow.md +6 -6
  124. package/bundled/skills/prizmkit-implement/SKILL.md +30 -66
  125. package/bundled/skills/prizmkit-implement/references/implementation-subagent-procedure.md +4 -4
  126. package/bundled/skills/prizmkit-init/SKILL.md +4 -0
  127. package/bundled/skills/prizmkit-plan/SKILL.md +21 -64
  128. package/bundled/skills/prizmkit-plan/references/artifact-identity.md +38 -0
  129. package/bundled/skills/prizmkit-plan/references/independent-plan-review.md +4 -4
  130. package/bundled/skills/prizmkit-plan/references/review-plan-spec-loop.md +5 -5
  131. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +11 -1
  132. package/bundled/skills/prizmkit-retrospective/SKILL.md +63 -77
  133. package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +3 -3
  134. package/bundled/skills/prizmkit-retrospective/references/retrospective-result-schema.json +138 -0
  135. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +17 -23
  136. package/bundled/skills/prizmkit-test/SKILL.md +64 -51
  137. package/bundled/skills/prizmkit-test/references/boundary-contract-and-test-double-guidance.md +206 -0
  138. package/bundled/skills/prizmkit-test/references/independent-test-review.md +17 -9
  139. package/bundled/skills/prizmkit-test/references/test-coverage-model.md +45 -21
  140. package/bundled/skills/prizmkit-test/references/test-report-template.md +20 -13
  141. package/bundled/skills/prizmkit-workflow/SKILL.md +35 -37
  142. package/bundled/skills/prizmkit-workflow/references/artifact-identity.md +34 -0
  143. package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +22 -18
  144. package/bundled/skills/recovery-workflow/SKILL.md +9 -6
  145. package/bundled/skills/recovery-workflow/evals/evals.json +2 -2
  146. package/bundled/skills/recovery-workflow/references/detection.md +7 -6
  147. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +16 -6
  148. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +21 -13
  149. package/bundled/templates/project-memory-template.md +19 -11
  150. package/package.json +1 -1
  151. package/src/scaffold.js +33 -22
  152. package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +0 -228
  153. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +0 -767
  154. package/bundled/dev-pipeline/templates/sections/bugfix-artifacts.md +0 -16
  155. package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +0 -31
  156. package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +0 -7
  157. package/bundled/dev-pipeline/templates/sections/critical-paths-lite.md +0 -7
  158. package/bundled/dev-pipeline/templates/sections/directory-convention-agent.md +0 -9
  159. package/bundled/dev-pipeline/templates/sections/directory-convention-lite.md +0 -6
  160. package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +0 -15
  161. package/bundled/dev-pipeline/templates/sections/phase-commit.md +0 -61
  162. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +0 -24
  163. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +0 -12
  164. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +0 -44
  165. package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +0 -25
  166. package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +0 -20
  167. package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +0 -20
  168. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +0 -20
  169. package/bundled/dev-pipeline/templates/sections/refactor-artifacts.md +0 -17
  170. package/bundled/dev-pipeline/templates/sections/refactor-session-status.md +0 -28
  171. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +0 -52
  172. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +0 -52
  173. package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +0 -174
  174. package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +0 -174
  175. package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +0 -174
  176. package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +0 -174
  177. package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +0 -174
  178. package/bundled/skills/prizmkit-test/references/external-contract-mock-guidance.md +0 -119
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
 
6
6
  import argparse
7
7
  import json
8
+ import re
8
9
  import subprocess
9
10
  from pathlib import Path
10
11
 
@@ -14,19 +15,19 @@ FAMILIES = (
14
15
  "name": "feature",
15
16
  "state_dir": "features",
16
17
  "plan_path": ".prizmkit/plans/feature-list.json",
17
- "id_prefix": "F-",
18
+ "id_pattern": r"^F-\d{3}$",
18
19
  },
19
20
  {
20
21
  "name": "bugfix",
21
22
  "state_dir": "bugfix",
22
23
  "plan_path": ".prizmkit/plans/bug-fix-list.json",
23
- "id_prefix": "B-",
24
+ "id_pattern": r"^B-\d{3}$",
24
25
  },
25
26
  {
26
27
  "name": "refactor",
27
28
  "state_dir": "refactor",
28
29
  "plan_path": ".prizmkit/plans/refactor-list.json",
29
- "id_prefix": "R-",
30
+ "id_pattern": r"^R-\d{3}$",
30
31
  },
31
32
  )
32
33
 
@@ -66,7 +67,7 @@ def current_branch(project_root: Path) -> str:
66
67
 
67
68
 
68
69
  def command_for(family: dict[str, str], task_id: str) -> str:
69
- return f"python3 ./.prizmkit/dev-pipeline/cli.py {family['name']} run {task_id}"
70
+ return f"python3 ./.prizmkit/dev-pipeline/cli.py run {family['name']} {task_id}"
70
71
 
71
72
 
72
73
  def detect_candidates(project_root: Path) -> list[dict[str, object]]:
@@ -83,9 +84,18 @@ def detect_candidates(project_root: Path) -> list[dict[str, object]]:
83
84
  data = load_json(checkout_path)
84
85
  if not data or str(data.get("state") or "") != "active":
85
86
  continue
86
- task_id = str(data.get("task_id") or checkout_path.parent.name).strip()
87
+ raw_task_id = data.get("task_id")
88
+ if not isinstance(raw_task_id, str):
89
+ continue
90
+ task_id = raw_task_id.strip()
87
91
  branch = str(data.get("active_dev_branch") or "").strip()
88
- if not task_id.startswith(family["id_prefix"]) or not branch_exists(project_root, branch):
92
+ if (
93
+ task_id != raw_task_id
94
+ or data.get("task_type") != family["name"]
95
+ or checkout_path.parent.name != task_id
96
+ or re.fullmatch(family["id_pattern"], task_id) is None
97
+ or not branch_exists(project_root, branch)
98
+ ):
89
99
  continue
90
100
  candidates.append(
91
101
  {
@@ -79,7 +79,7 @@ If the project instruction files or package metadata name a baseline test comman
79
79
 
80
80
  Use one `AskUserQuestion` call with exactly one question:
81
81
 
82
- - **Foreground (Recommended)** — build `refactor run`; output remains attached to the user's terminal.
82
+ - **Foreground (Recommended)** — build `run refactor`; output remains attached to the user's terminal.
83
83
  - **Background daemon** — build `daemon refactor start`; the runtime manages detached execution and logs.
84
84
 
85
85
  Wait for the response before asking configuration.
@@ -113,7 +113,7 @@ Foreground template:
113
113
 
114
114
  ```bash
115
115
  VERBOSE=1 MAX_RETRIES=3 STRICT_BEHAVIOR_CHECK=1 \
116
- python3 ./.prizmkit/dev-pipeline/cli.py refactor run .prizmkit/plans/refactor-list.json
116
+ python3 ./.prizmkit/dev-pipeline/cli.py run refactor .prizmkit/plans/refactor-list.json
117
117
  ```
118
118
 
119
119
  Background template:
@@ -131,7 +131,7 @@ Return:
131
131
 
132
132
  ```bash
133
133
  python3 ./.prizmkit/dev-pipeline/cli.py daemon refactor status
134
- python3 ./.prizmkit/dev-pipeline/cli.py refactor status .prizmkit/plans/refactor-list.json
134
+ python3 ./.prizmkit/dev-pipeline/cli.py status refactor .prizmkit/plans/refactor-list.json
135
135
  ```
136
136
 
137
137
  Do not infer completion from the plan file alone when runtime state may exist.
@@ -164,39 +164,47 @@ Optionally include the daemon status command for the user to run afterward. Do n
164
164
 
165
165
  ## Retry Command
166
166
 
167
- Reset changes state only; it never starts execution and rejects `--run`. Always return reset and run as separate commands.
167
+ Reset never starts execution and rejects `--run`. Every reset command must select exactly one mode: `--state-only`, `--fresh-checkout`, `--clean`, or `--preserve-runtime`. Always return reset and run as separate commands.
168
168
 
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.”
169
+ State-only recovery resets selected failed/skipped roots and only causal `auto_skipped` descendants whose complete blocker set is recovered. It resets retry/session/continuation state but never changes checkout identity, branches, worktrees, source changes, checkpoints, or artifact files. Selecting an auto-skipped refactor includes all of its transitive blocking roots; `--all` recovers all eligible chains. Fresh-checkout performs the former standard reset, can discard uncommitted changes on the active task branch, and records a new checkout boundary. Clean additionally deletes the task branch and clears task history/artifacts. Preserve-runtime scans the whole list for failed roots, retains runtime history and valid checkout/WIP, and alone may replace a missing branch from the recorded base and rewind implementation-and-later checkpoints. Never hide these effects behind the word “retry.”
170
170
 
171
171
  Use `AskUserQuestion` before constructing retry commands:
172
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.
173
+ - **State-only causal recovery** — recover one refactor closure, or all closures with `--all`, while preserving Git/checkouts/checkpoints/artifact files.
174
+ - **Preserve failed runtime (Recommended for continuing failed work)** — recover all failed chains without cleanup while retaining history and continuation evidence; disclose that this is whole-list failed-only recovery.
175
+ - **Fresh-checkout retry** — reset one refactor, then run it separately; warn that the active task branch can lose uncommitted changes.
175
176
  - **Clean retry** — reset one refactor, delete its task branch/history/artifacts, then run it separately.
176
177
  - **Cancel** — return no reset or run command.
177
178
 
179
+ State-only template:
180
+
181
+ ```bash
182
+ python3 ./.prizmkit/dev-pipeline/cli.py reset refactor R-001 --state-only .prizmkit/plans/refactor-list.json
183
+ python3 ./.prizmkit/dev-pipeline/cli.py run refactor R-001 .prizmkit/plans/refactor-list.json
184
+ ```
185
+
178
186
  Preserve-runtime template:
179
187
 
180
188
  ```bash
181
189
  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
190
+ python3 ./.prizmkit/dev-pipeline/cli.py run refactor .prizmkit/plans/refactor-list.json
183
191
  ```
184
192
 
185
- Standard template:
193
+ Fresh-checkout template:
186
194
 
187
195
  ```bash
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
196
+ python3 ./.prizmkit/dev-pipeline/cli.py reset refactor R-001 --fresh-checkout .prizmkit/plans/refactor-list.json
197
+ python3 ./.prizmkit/dev-pipeline/cli.py run refactor R-001 .prizmkit/plans/refactor-list.json
190
198
  ```
191
199
 
192
200
  Clean template:
193
201
 
194
202
  ```bash
195
203
  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
204
+ python3 ./.prizmkit/dev-pipeline/cli.py run refactor R-001 .prizmkit/plans/refactor-list.json
197
205
  ```
198
206
 
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.
207
+ State-only requires exactly one ID or explicit `--all` and rejects ranges/status filters. Fresh-checkout and clean accept one ID, range, or supported status filter. Preserve-runtime requires `--failed`, scans the whole list, and rejects IDs, ranges, `--all`, clean, other modes, and other filters. Retry-budget flags belong on the run command, never reset. For fresh-checkout or clean retry, require explicit acknowledgement of possible uncommitted-change loss; clean additionally requires acknowledgement of branch/history/artifact deletion.
200
208
 
201
209
  ## Output Contract
202
210
 
@@ -44,18 +44,22 @@ Placeholders: `<family> = feature | bugfix | refactor`; `<task-id> = F-NNN | B-N
44
44
 
45
45
  ```bash
46
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]
47
+ python3 ./.prizmkit/dev-pipeline/cli.py run <family> [<list>] [--max-retries N] [--max-infra-retries N]
48
+ python3 ./.prizmkit/dev-pipeline/cli.py run <family> <task-id> [<list>] [--max-retries N] [--max-infra-retries N]
49
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
50
+ # Inspect task state
51
+ python3 ./.prizmkit/dev-pipeline/cli.py status <family> [<list>]
52
+ python3 ./.prizmkit/dev-pipeline/cli.py status all
54
53
 
55
- # Reset state (never starts execution)
56
- python3 ./.prizmkit/dev-pipeline/cli.py reset <family> <task-id> [<list>]
54
+ # Inspect runtime and AI CLI resolution
55
+ python3 ./.prizmkit/dev-pipeline/cli.py diagnostics ai-cli
56
+
57
+ # Reset state (exactly one explicit mode; never starts execution)
58
+ python3 ./.prizmkit/dev-pipeline/cli.py reset <family> <task-id> --state-only [<list>]
59
+ python3 ./.prizmkit/dev-pipeline/cli.py reset <family> --state-only --all [<list>]
60
+ python3 ./.prizmkit/dev-pipeline/cli.py reset <family> <task-id-or-range> --fresh-checkout [<list>]
61
+ python3 ./.prizmkit/dev-pipeline/cli.py reset <family> <task-id-or-range> --clean [<list>]
57
62
  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
63
 
60
64
  # Background operation
61
65
  python3 ./.prizmkit/dev-pipeline/cli.py daemon <family> start [<list>]
@@ -64,10 +68,14 @@ python3 ./.prizmkit/dev-pipeline/cli.py daemon <family> logs [--lines N|--follow
64
68
  python3 ./.prizmkit/dev-pipeline/cli.py daemon <family> stop
65
69
  ```
66
70
 
71
+ Interrupted-task recovery: invoke `recovery-workflow`. Its read-only detector reports active checkout candidates and returns the ordinary `python3 ./.prizmkit/dev-pipeline/cli.py run <family> <task-id>` command without executing it.
72
+
67
73
  Runtime boundaries:
68
74
  - 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.
75
+ - Exactly one of `--state-only`, `--fresh-checkout`, `--clean`, or `--preserve-runtime` is required. Missing/conflicting modes fail before state or Git mutation.
76
+ - State-only accepts one ID or explicit `--all`; it resets causal dependency state while preserving checkout identity, branches/worktrees, source changes, checkpoints, and artifact files.
77
+ - Fresh-checkout and clean accept an ID, range, or supported status filter. Fresh-checkout publishes a new checkout boundary; clean additionally deletes branch/history/artifact resources.
78
+ - Preserve-runtime requires `--failed`, scans the whole list, and rejects IDs, ranges, `--all`, clean, other modes, and other filters. It retains runtime history and valid checkout/WIP; a missing branch is replaced from the recorded base with implementation-and-later checkpoint rewind.
71
79
  - In zsh/Bash multiline commands, `\` must be the final character on the line with no trailing space.
72
80
 
73
81
  ### Lightweight Planning Path
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prizmkit",
3
- "version": "1.1.153",
3
+ "version": "1.1.155",
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": {
package/src/scaffold.js CHANGED
@@ -13,7 +13,8 @@ import path from 'path';
13
13
  import { readFileSync } from 'fs';
14
14
  import { fileURLToPath, pathToFileURL } from 'url';
15
15
  import { dirname, join } from 'path';
16
- import { execSync } from 'child_process';
16
+ import { execFileSync, execSync } from 'child_process';
17
+ import os from 'os';
17
18
  import {
18
19
  loadMetadata,
19
20
  getTemplatesDir,
@@ -1373,6 +1374,19 @@ export async function installPlaywrightCli(projectRoot, dryRun) {
1373
1374
  // OpenCLI 安装
1374
1375
  // ============================================================
1375
1376
 
1377
+ /**
1378
+ * Side-effect-free OpenCLI presence check. Running OpenCLI itself is unsafe here:
1379
+ * browser-oriented commands such as `opencli doctor` create a live browser session.
1380
+ */
1381
+ function isOpenCliAvailable() {
1382
+ try {
1383
+ execFileSync(os.platform() === 'win32' ? 'where.exe' : 'which', ['opencli'], { stdio: 'ignore' });
1384
+ return true;
1385
+ } catch {
1386
+ return false;
1387
+ }
1388
+ }
1389
+
1376
1390
  /**
1377
1391
  * 安装 opencli(全局 npm 包)
1378
1392
  * 用于浏览器交互验证,复用 Chrome 已登录会话(OAuth/SSO/第三方集成)
@@ -1383,12 +1397,11 @@ export async function installOpenCli(projectRoot, dryRun, autoDownload = true) {
1383
1397
  return;
1384
1398
  }
1385
1399
 
1386
- // Check if already installed
1387
- try {
1388
- execSync('opencli --version', { stdio: 'pipe' });
1400
+ // Detect the executable without running it. This keeps repeated PrizmKit
1401
+ // installs/upgrades idempotent and avoids starting an OpenCLI browser session.
1402
+ if (isOpenCliAvailable()) {
1389
1403
  console.log(chalk.green(' ✓ opencli (already installed)'));
1390
- } catch {
1391
- // Not installed, install it
1404
+ } else {
1392
1405
  try {
1393
1406
  console.log(chalk.blue(' ⏳ Installing @jackwener/opencli globally...'));
1394
1407
  execSync('npm install -g @jackwener/opencli@latest', { stdio: 'pipe', timeout: 120000 });
@@ -1400,22 +1413,20 @@ export async function installOpenCli(projectRoot, dryRun, autoDownload = true) {
1400
1413
  }
1401
1414
  }
1402
1415
 
1403
- // Verify opencli connectivity Chrome Browser Bridge extension is a manual install
1404
- try {
1405
- const doctorOutput = execSync('opencli doctor 2>&1', { stdio: 'pipe', timeout: 15000 }).toString();
1406
- if (doctorOutput.includes('[MISSING] Extension') || doctorOutput.includes('[FAIL] Connectivity')) {
1407
- console.log(chalk.yellow(' ⚠ Chrome Browser Bridge extension is NOT connected'));
1408
-
1409
- if (autoDownload) {
1410
- await downloadOpenCliExtension();
1411
- } else {
1412
- printOpenCliManualSteps();
1413
- }
1414
- } else {
1415
- console.log(chalk.green(' ✓ opencli connectivity verified'));
1416
- }
1417
- } catch {
1418
- // doctor command itself failed — already warned about install issues
1416
+ // Check only PrizmKit's downloaded extension files. Connectivity checks are
1417
+ // intentionally user-initiated because `opencli doctor` opens a browser tab.
1418
+ const extensionManifest = path.join(
1419
+ process.env.HOME || process.env.USERPROFILE || '.',
1420
+ '.opencli',
1421
+ 'extension',
1422
+ 'manifest.json',
1423
+ );
1424
+ if (fs.pathExistsSync(extensionManifest)) {
1425
+ console.log(chalk.green(' ✓ OpenCLI extension files (already downloaded)'));
1426
+ } else if (autoDownload) {
1427
+ await downloadOpenCliExtension();
1428
+ } else {
1429
+ printOpenCliManualSteps();
1419
1430
  }
1420
1431
  }
1421
1432
 
@@ -1,228 +0,0 @@
1
- """Recovery detection and execution for Python foreground runner commands."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import subprocess
7
- import sys
8
- import time
9
- from dataclasses import dataclass
10
- from pathlib import Path
11
-
12
- from .checkpoint_state import load_checkpoint_state
13
- from .config import load_runtime_config
14
- from .paths import RuntimePaths
15
- from .runner_models import RunnerEnvironment, SessionPaths
16
- from .sessions import AISessionConfig, AISessionLauncher, detect_stream_json_support
17
-
18
-
19
- @dataclass(frozen=True)
20
- class RecoveryDetectorResolver:
21
- """Resolve recovery detector sources without coupling to one platform copy."""
22
-
23
- paths: RuntimePaths
24
-
25
- def candidates(self) -> tuple[Path, ...]:
26
- project = self.paths.project_root
27
- return (
28
- project / ".agents" / "skills" / "recovery-workflow" / "scripts" / "detect-recovery-state.py",
29
- project / ".claude" / "command-assets" / "recovery-workflow" / "scripts" / "detect-recovery-state.py",
30
- project / ".codebuddy" / "skills" / "recovery-workflow" / "scripts" / "detect-recovery-state.py",
31
- project / ".pi" / "skills" / "recovery-workflow" / "scripts" / "detect-recovery-state.py",
32
- project
33
- / "core"
34
- / "skills"
35
- / "orchestration-skill"
36
- / "workflows"
37
- / "recovery-workflow"
38
- / "scripts"
39
- / "detect-recovery-state.py",
40
- )
41
-
42
- def resolve(self) -> Path:
43
- for candidate in self.candidates():
44
- if candidate.is_file():
45
- return candidate
46
- raise FileNotFoundError("recovery detector script not found")
47
-
48
-
49
- def run_recovery_detect(paths: RuntimePaths) -> tuple[int, str, str, dict[str, object] | None]:
50
- """Run recovery state detection and parse the JSON report."""
51
- detector = RecoveryDetectorResolver(paths).resolve()
52
- completed = subprocess.run(
53
- [sys.executable, str(detector), "--project-root", str(paths.project_root)],
54
- cwd=str(paths.project_root),
55
- stdout=subprocess.PIPE,
56
- stderr=subprocess.PIPE,
57
- text=True,
58
- check=False,
59
- )
60
- data = None
61
- if completed.stdout.strip():
62
- try:
63
- data = json.loads(completed.stdout)
64
- except json.JSONDecodeError:
65
- data = None
66
- return completed.returncode, completed.stdout, completed.stderr, data
67
-
68
-
69
- def run_recovery(paths: RuntimePaths, legacy_args: tuple[str, ...] = ()) -> tuple[int, str, str]:
70
- """Detect recovery state, render a prompt, launch an AI session, and classify."""
71
- dry_run = "--dry-run" in legacy_args
72
- model_override = _option_value(legacy_args, "--model")
73
- code, stdout, stderr, detection = run_recovery_detect(paths)
74
- if code != 0 or not detection:
75
- return code or 1, stdout, stderr
76
- if not detection.get("detected"):
77
- return 0, stdout, stderr
78
-
79
- session_id = "recovery-{}-{}".format(
80
- str(detection.get("workflow_type") or "workflow"),
81
- time.strftime("%Y%m%d-%H%M%S"),
82
- )
83
- session_dir = paths.recovery_state_dir / session_id
84
- session_paths = SessionPaths(
85
- session_dir=session_dir,
86
- prompt_path=session_dir / "bootstrap-prompt.md",
87
- logs_dir=session_dir / "logs",
88
- session_log=session_dir / "logs" / "session.log",
89
- progress_json=session_dir / "logs" / "progress.json",
90
- heartbeat_json=session_dir / "logs" / "heartbeat.json",
91
- session_status_json=session_dir / "session-status.json",
92
- )
93
- detection_json = session_paths.session_dir / "detection.json"
94
- session_paths.session_dir.mkdir(parents=True, exist_ok=True)
95
- detection_json.write_text(json.dumps(detection, indent=2, ensure_ascii=False), encoding="utf-8")
96
- prompt_output = session_paths.prompt_path
97
- generator = paths.pipeline_root / "scripts" / "generate-recovery-prompt.py"
98
- prompt_cmd = [
99
- sys.executable,
100
- str(generator),
101
- "--detection-json",
102
- str(detection_json),
103
- "--output",
104
- str(prompt_output),
105
- "--project-root",
106
- str(paths.project_root),
107
- "--session-id",
108
- session_id,
109
- ]
110
- prompt_result = subprocess.run(
111
- prompt_cmd,
112
- cwd=str(paths.project_root),
113
- stdout=subprocess.PIPE,
114
- stderr=subprocess.PIPE,
115
- text=True,
116
- check=False,
117
- )
118
- if prompt_result.returncode != 0:
119
- return prompt_result.returncode, prompt_result.stdout, prompt_result.stderr
120
- if dry_run:
121
- return 0, prompt_result.stdout, prompt_result.stderr
122
-
123
- config = load_runtime_config(paths)
124
- if not config.ai_client.command:
125
- return 1, "", "No supported AI CLI command was found for recovery\n"
126
- env = RunnerEnvironment.from_env()
127
- stream_json = False
128
- if env.live_output and getattr(config.ai_client, "launch_profile", None) is None:
129
- stream_json = detect_stream_json_support(config.ai_client.command, config.ai_client.platform).enabled
130
- launcher = AISessionLauncher(
131
- AISessionConfig(
132
- cli_command=config.ai_client.command,
133
- platform=config.ai_client.platform or "codebuddy",
134
- model=model_override or config.model,
135
- prompt_path=prompt_output,
136
- cwd=paths.project_root,
137
- log_path=session_paths.session_log,
138
- progress_path=session_paths.progress_json,
139
- heartbeat_path=session_paths.heartbeat_json,
140
- effort=config.effort,
141
- verbose=env.verbose,
142
- live_output=env.live_output,
143
- use_stream_json=stream_json,
144
- launch_profile=getattr(config.ai_client, "launch_profile", None),
145
- stale_kill_threshold_seconds=env.stale_kill_threshold_seconds,
146
- heartbeat_stale_threshold_seconds=env.heartbeat_stale_threshold_seconds,
147
- live_progress_interval_seconds=env.heartbeat_interval_seconds,
148
- live_banner=False,
149
- suppress_stream_output=True,
150
- )
151
- )
152
- result = launcher.run()
153
- checkpoint_path = _recovery_checkpoint_path(paths.project_root, detection)
154
- outcome, semantic = _recovery_outcome(
155
- paths.project_root,
156
- result.exit_code,
157
- checkpoint_path=checkpoint_path,
158
- )
159
- session_paths.session_status_json.write_text(
160
- json.dumps(
161
- {
162
- "session_id": session_id,
163
- "outcome": outcome,
164
- "checkpoint_path": str(checkpoint_path) if checkpoint_path else None,
165
- "checkpoint_state": semantic,
166
- },
167
- indent=2,
168
- )
169
- + "\n",
170
- encoding="utf-8",
171
- )
172
- return 0 if outcome in {"success", "no_changes"} else 1, outcome + "\n", ""
173
-
174
-
175
- def _option_value(args: tuple[str, ...], flag: str) -> str | None:
176
- for index, arg in enumerate(args):
177
- if arg == flag and index + 1 < len(args):
178
- return args[index + 1]
179
- if arg.startswith(flag + "="):
180
- return arg.split("=", 1)[1]
181
- return None
182
-
183
-
184
- def _recovery_checkpoint_path(project_root: Path, detection: dict[str, object]) -> Path | None:
185
- """Select the L4 checkpoint identified by recovery inspection."""
186
- semantic = detection.get("semantic_checkpoint")
187
- if isinstance(semantic, dict) and semantic.get("path"):
188
- candidate = project_root / str(semantic["path"])
189
- if candidate.is_file():
190
- return candidate
191
- execution_owner = detection.get("execution_owner")
192
- checkpoints = execution_owner.get("l4_checkpoints") if isinstance(execution_owner, dict) else []
193
- if isinstance(checkpoints, list) and len(checkpoints) == 1:
194
- candidate = project_root / str(checkpoints[0])
195
- if candidate.is_file():
196
- return candidate
197
- return None
198
-
199
-
200
- def _recovery_outcome(
201
- project_root: Path,
202
- exit_code: int | None,
203
- *,
204
- checkpoint_path: Path | None = None,
205
- ) -> tuple[str, dict[str, object]]:
206
- """Classify recovery from semantic L4 state without mutating partial work."""
207
- if checkpoint_path is not None:
208
- state = load_checkpoint_state(checkpoint_path, project_root=project_root)
209
- semantic = state.semantic_snapshot
210
- if state.valid and state.semantic_complete:
211
- return "success", semantic
212
- if state.valid and state.semantic_blocked:
213
- return "workflow_blocked", semantic
214
- if state.exists and not state.valid:
215
- return "blocked_invalid_checkpoint", semantic
216
- return "partial" if exit_code == 0 else "failed", semantic
217
-
218
- status = subprocess.run(
219
- ["git", "-C", str(project_root), "status", "--porcelain"],
220
- stdout=subprocess.PIPE,
221
- stderr=subprocess.DEVNULL,
222
- text=True,
223
- check=False,
224
- )
225
- has_changes = bool(status.stdout.strip())
226
- if exit_code == 0 and not has_changes:
227
- return "no_changes", {}
228
- return ("partial" if has_changes else "failed"), {}