prizmkit 1.1.148 → 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.
Files changed (24) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/README.md +20 -17
  3. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +77 -0
  4. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +234 -39
  5. package/bundled/dev-pipeline/prizmkit_runtime/reset.py +57 -30
  6. package/bundled/dev-pipeline/prizmkit_runtime/reset_preserve.py +822 -0
  7. package/bundled/dev-pipeline/prizmkit_runtime/runner_bookkeeping.py +28 -11
  8. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +71 -28
  9. package/bundled/dev-pipeline/prizmkit_runtime/task_checkout.py +18 -0
  10. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +67 -13
  11. package/bundled/dev-pipeline/tests/test_reset_preserve.py +959 -0
  12. package/bundled/dev-pipeline/tests/test_unified_cli.py +325 -5
  13. package/bundled/skills/_metadata.json +1 -1
  14. package/bundled/skills/app-planner/SKILL.md +27 -27
  15. package/bundled/skills/app-planner/references/architecture-decisions.md +5 -11
  16. package/bundled/skills/app-planner/references/frontend-design-guide.md +1 -1
  17. package/bundled/skills/app-planner/references/generated-plan-review.md +2 -2
  18. package/bundled/skills/app-planner/references/infrastructure-convention-discovery.md +2 -2
  19. package/bundled/skills/app-planner/references/project-conventions-discovery.md +1 -1
  20. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -9
  21. package/bundled/skills/feature-pipeline-launcher/SKILL.md +21 -9
  22. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +21 -9
  23. package/bundled/templates/project-memory-template.md +38 -0
  24. package/package.json +1 -1
@@ -39,7 +39,7 @@ class ResetOptions:
39
39
  item_range: tuple[str, str] | None = None
40
40
  filter_mode: str = ""
41
41
  clean: bool = False
42
- run_after: bool = False
42
+ preserve_runtime: bool = False
43
43
  help_requested: bool = False
44
44
  unknown_args: tuple[str, ...] = ()
45
45
 
@@ -99,14 +99,20 @@ STALLED_FILTER_STATUSES = {
99
99
  def run_reset_command(kind: str, legacy_args: tuple[str, ...], paths) -> CommandResult:
100
100
  """Run a manual reset command for feature, bugfix, or refactor."""
101
101
  family = family_for(kind, paths)
102
- options = _parse_reset_args(family, legacy_args)
102
+ try:
103
+ options = _parse_reset_args(family, legacy_args)
104
+ except ValueError as exc:
105
+ return CommandResult(2, f"Python {kind} reset", str(exc))
103
106
  if options.help_requested:
104
107
  return CommandResult(0, f"Python {kind} reset", _help_text(family), stdout=_help_text(family))
108
+ option_error = _reset_option_error(family, options)
109
+ if option_error:
110
+ return CommandResult(2, f"Python {kind} reset", option_error)
105
111
 
106
112
  list_path = _resolve_list_path(options.list_path, paths.project_root)
107
113
  if not list_path.is_file():
108
114
  return CommandResult(1, f"Python {kind} reset", f"{kind} list not found: {list_path}")
109
- if not (family.state_dir / "pipeline.json").is_file():
115
+ if not options.preserve_runtime and not (family.state_dir / "pipeline.json").is_file():
110
116
  return CommandResult(1, f"Python {kind} reset", f"No pipeline state found. Run the {kind} pipeline first to initialize.")
111
117
 
112
118
  options = ResetOptions(
@@ -115,10 +121,26 @@ def run_reset_command(kind: str, legacy_args: tuple[str, ...], paths) -> Command
115
121
  item_range=options.item_range,
116
122
  filter_mode=options.filter_mode,
117
123
  clean=options.clean,
118
- run_after=options.run_after,
124
+ preserve_runtime=options.preserve_runtime,
119
125
  help_requested=options.help_requested,
120
126
  unknown_args=options.unknown_args,
121
127
  )
128
+ if options.preserve_runtime:
129
+ from .reset_preserve import run_preserve_runtime_reset
130
+
131
+ preserve_result = run_preserve_runtime_reset(
132
+ family,
133
+ options.list_path,
134
+ paths.project_root,
135
+ )
136
+ text = "\n".join(preserve_result.lines(family, options.list_path))
137
+ return CommandResult(
138
+ 0 if preserve_result.ok else 1,
139
+ f"Python {kind} reset",
140
+ text,
141
+ stdout=text,
142
+ )
143
+
122
144
  try:
123
145
  items = _resolve_items(family, options)
124
146
  except ValueError as exc:
@@ -126,18 +148,6 @@ def run_reset_command(kind: str, legacy_args: tuple[str, ...], paths) -> Command
126
148
  if not items:
127
149
  return CommandResult(0, f"Python {kind} reset", f"No {FAMILY_LABELS[kind]}s found with status: {options.filter_mode}")
128
150
 
129
- if options.run_after and len(items) > 1:
130
- options = ResetOptions(
131
- list_path=options.list_path,
132
- item_id=options.item_id,
133
- item_range=options.item_range,
134
- filter_mode=options.filter_mode,
135
- clean=options.clean,
136
- run_after=False,
137
- help_requested=False,
138
- unknown_args=options.unknown_args,
139
- )
140
-
141
151
  lines: list[str] = []
142
152
  success = 0
143
153
  failed = 0
@@ -155,14 +165,6 @@ def run_reset_command(kind: str, legacy_args: tuple[str, ...], paths) -> Command
155
165
  failed += 1
156
166
 
157
167
  lines.append(f"Reset complete: {success} succeeded, {failed} failed")
158
- if options.run_after and len(items) == 1 and failed == 0:
159
- from .runners import run_pipeline_command
160
-
161
- lines.append(f"Auto-retrying {items[0].item_id} via Python foreground runner...")
162
- retry_result = run_pipeline_command(kind, "run", (items[0].item_id, str(options.list_path)), paths)
163
- lines.append(retry_result.render())
164
- return CommandResult(retry_result.exit_code, f"Python {kind} reset", "\n".join(lines), stdout="\n".join(lines))
165
-
166
168
  return CommandResult(0 if failed == 0 else 1, f"Python {kind} reset", "\n".join(lines), stdout="\n".join(lines))
167
169
 
168
170
 
@@ -173,7 +175,7 @@ def _parse_reset_args(family: RunnerFamily, legacy_args: Sequence[str]) -> Reset
173
175
  item_range: tuple[str, str] | None = None
174
176
  filter_mode = ""
175
177
  clean = False
176
- run_after = False
178
+ preserve_runtime = False
177
179
  help_requested = False
178
180
  unknown: list[str] = []
179
181
  positional: list[str] = []
@@ -185,10 +187,19 @@ def _parse_reset_args(family: RunnerFamily, legacy_args: Sequence[str]) -> Reset
185
187
  help_requested = True
186
188
  elif arg == "--clean":
187
189
  clean = True
190
+ elif arg == "--preserve-runtime":
191
+ preserve_runtime = True
188
192
  elif arg == "--run":
189
- run_after = True
193
+ raise ValueError(
194
+ "Reset no longer accepts --run. Run reset first, then execute "
195
+ f"`{family.kind} run` as a separate command."
196
+ )
190
197
  elif arg in FILTER_FLAGS:
191
- filter_mode = FILTER_FLAGS[arg]
198
+ next_filter = FILTER_FLAGS[arg]
199
+ if filter_mode and filter_mode != next_filter:
200
+ unknown.append(arg)
201
+ else:
202
+ filter_mode = next_filter
192
203
  elif arg.startswith("-"):
193
204
  unknown.append(arg)
194
205
  else:
@@ -211,12 +222,26 @@ def _parse_reset_args(family: RunnerFamily, legacy_args: Sequence[str]) -> Reset
211
222
  item_range=item_range,
212
223
  filter_mode=filter_mode,
213
224
  clean=clean,
214
- run_after=run_after,
225
+ preserve_runtime=preserve_runtime,
215
226
  help_requested=help_requested,
216
227
  unknown_args=tuple(unknown),
217
228
  )
218
229
 
219
230
 
231
+ def _reset_option_error(family: RunnerFamily, options: ResetOptions) -> str:
232
+ if options.unknown_args:
233
+ return "Unsupported reset arguments: " + " ".join(options.unknown_args)
234
+ if not options.preserve_runtime:
235
+ return ""
236
+ if options.filter_mode != "failed":
237
+ return "--preserve-runtime requires --failed and cannot be used with another reset filter."
238
+ if options.clean:
239
+ return "--preserve-runtime and --clean are mutually exclusive."
240
+ if options.item_id or options.item_range:
241
+ return "--preserve-runtime is a whole-list failed-chain reset and cannot target an ID or range."
242
+ return ""
243
+
244
+
220
245
  def _resolve_list_path(path: Path, project_root: Path) -> Path:
221
246
  return path.resolve() if path.is_absolute() else (project_root / path).resolve()
222
247
 
@@ -430,7 +455,9 @@ def _numeric_suffix(value: str, prefix: str) -> int:
430
455
  def _help_text(family: RunnerFamily) -> str:
431
456
  label = FAMILY_LABELS[family.kind]
432
457
  return (
433
- f"Usage: prizmkit-runtime reset {family.kind} <{label}-id|range> [--clean] [--run] "
458
+ f"Usage: prizmkit-runtime reset {family.kind} <{label}-id|range> [--clean] "
434
459
  "[--auto-skipped|--failed|--stalled] [list-path]\n"
435
- "Reset is a destructive manual operation and is not used for automatic context continuation."
460
+ f" prizmkit-runtime reset {family.kind} --failed --preserve-runtime [list-path]\n"
461
+ "Reset changes state only and never accepts --run; execute the matching family run command separately.\n"
462
+ "Standard/clean reset abandons the active checkout boundary. Preserve-runtime reset keeps task runtime and work."
436
463
  )