prizmkit 1.1.152 → 1.1.154

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 (44) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/README.md +100 -87
  3. package/bundled/dev-pipeline/assets/skill-subagent-integration.md +1 -1
  4. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +14 -0
  5. package/bundled/dev-pipeline/prizmkit_runtime/cli.py +192 -110
  6. package/bundled/dev-pipeline/prizmkit_runtime/commands.py +146 -111
  7. package/bundled/dev-pipeline/prizmkit_runtime/daemon.py +3 -5
  8. package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +0 -1
  9. package/bundled/dev-pipeline/prizmkit_runtime/paths.py +0 -3
  10. package/bundled/dev-pipeline/prizmkit_runtime/reset.py +122 -15
  11. package/bundled/dev-pipeline/prizmkit_runtime/reset_preserve.py +1 -1
  12. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +1 -1
  13. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +19 -52
  14. package/bundled/dev-pipeline/prizmkit_runtime/status.py +106 -1
  15. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +2 -2
  16. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +10 -12
  17. package/bundled/dev-pipeline/scripts/update-bug-status.py +196 -67
  18. package/bundled/dev-pipeline/scripts/update-checkpoint.py +16 -3
  19. package/bundled/dev-pipeline/scripts/update-feature-status.py +45 -117
  20. package/bundled/dev-pipeline/scripts/update-refactor-status.py +45 -119
  21. package/bundled/dev-pipeline/scripts/utils.py +119 -0
  22. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +3 -2
  23. package/bundled/dev-pipeline/tests/test_auto_skip.py +111 -32
  24. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +148 -12
  25. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +0 -19
  26. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +113 -188
  27. package/bundled/dev-pipeline/tests/test_recovery_workflow.py +211 -0
  28. package/bundled/dev-pipeline/tests/test_reset_modes.py +935 -0
  29. package/bundled/dev-pipeline/tests/test_reset_preserve.py +5 -5
  30. package/bundled/dev-pipeline/tests/test_unified_cli.py +544 -181
  31. package/bundled/skills/_metadata.json +1 -1
  32. package/bundled/skills/bug-planner/references/schema-validation.md +1 -1
  33. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +1 -1
  34. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -13
  35. package/bundled/skills/feature-pipeline-launcher/SKILL.md +22 -14
  36. package/bundled/skills/recovery-workflow/SKILL.md +7 -5
  37. package/bundled/skills/recovery-workflow/evals/evals.json +2 -2
  38. package/bundled/skills/recovery-workflow/references/detection.md +3 -3
  39. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +1 -1
  40. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +21 -13
  41. package/bundled/templates/project-memory-template.md +19 -11
  42. package/package.json +1 -1
  43. package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +0 -228
  44. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +0 -767
@@ -9,136 +9,231 @@ from collections.abc import Sequence
9
9
  from .commands import CommandResult, handle_diagnostics, handle_runtime_command
10
10
  from .compat import UnsupportedPythonVersion, ensure_supported_python
11
11
  from .paths import resolve_runtime_paths
12
- from .runtime_helper import render_result, run_helper_command
13
12
 
14
13
  DESCRIPTION = (
15
14
  "PrizmKit canonical Python runtime CLI.\n"
16
- "Runs pipeline, reset, daemon, recovery, status, and diagnostics commands without shell wrappers."
15
+ "Runs operation-first pipeline, status, reset, daemon, diagnostics, and helper commands."
17
16
  )
18
17
 
18
+ _FAMILIES = ("feature", "bugfix", "refactor")
19
+ _GLOBAL_OPTIONS_WITH_VALUES = {"--project-root", "--pipeline-root"}
20
+ _LEAF_OPTIONS_WITH_VALUES = {
21
+ "--env",
22
+ "--features",
23
+ "--lines",
24
+ "-n",
25
+ "--max-retries",
26
+ "--max-infra-retries",
27
+ "--mode",
28
+ "--resume-phase",
29
+ }
30
+
31
+
32
+ def _set_handler(
33
+ parser: argparse.ArgumentParser,
34
+ group: str,
35
+ action: str,
36
+ target: str | None = None,
37
+ ) -> None:
38
+ parser.set_defaults(handler="runtime", group=group, action=action, target=target)
19
39
 
20
- def _add_passthrough_options(parser: argparse.ArgumentParser) -> None:
21
- parser.add_argument(
22
- "args",
23
- nargs=argparse.REMAINDER,
24
- help="Arguments forwarded to the selected Python runtime command.",
25
- )
40
+
41
+ def _non_negative_int(value: str) -> int:
42
+ try:
43
+ parsed = int(value)
44
+ except ValueError as exc:
45
+ raise argparse.ArgumentTypeError("must be an integer") from exc
46
+ if parsed < 0:
47
+ raise argparse.ArgumentTypeError("must be non-negative")
48
+ return parsed
49
+
50
+
51
+ def _add_runner_arguments(parser: argparse.ArgumentParser, family: str) -> None:
52
+ parser.add_argument("operand", nargs="?", help="Optional task ID or family plan path.")
53
+ parser.add_argument("second_operand", nargs="?", help="Optional family plan path or task ID.")
54
+ if family == "feature":
55
+ parser.add_argument("--features", help="Feature ID, range, or comma-separated selection.")
56
+ parser.add_argument("--max-retries", type=_non_negative_int)
57
+ parser.add_argument("--max-infra-retries", type=_non_negative_int)
58
+ parser.add_argument("--mode")
59
+ parser.add_argument("--resume-phase")
60
+ parser.add_argument("--dry-run", action="store_true")
61
+ parser.add_argument("--clean", action="store_true")
62
+ parser.add_argument("--no-reset", action="store_true")
63
+
64
+
65
+ def _add_run_subparsers(
66
+ subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
67
+ ) -> None:
68
+ run_parser = subparsers.add_parser("run", help="Run a foreground pipeline family")
69
+ family_subparsers = run_parser.add_subparsers(dest="run_family", required=True)
70
+ for family in _FAMILIES:
71
+ family_parser = family_subparsers.add_parser(family, help=f"Run the {family} pipeline")
72
+ _add_runner_arguments(family_parser, family)
73
+ _set_handler(family_parser, "run", "run", family)
26
74
 
27
75
 
28
- def _set_handler(parser: argparse.ArgumentParser, group: str, action: str, target: str | None = None) -> None:
29
- parser.set_defaults(handler="runtime", group=group, action=action, target=target)
76
+ def _add_status_subparsers(
77
+ subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
78
+ ) -> None:
79
+ status_parser = subparsers.add_parser("status", help="Inspect pipeline task state")
80
+ target_subparsers = status_parser.add_subparsers(dest="status_target", required=True)
81
+ for family in _FAMILIES:
82
+ family_parser = target_subparsers.add_parser(family, help=f"Show {family} status")
83
+ _add_runner_arguments(family_parser, family)
84
+ _set_handler(family_parser, "status", "status", family)
85
+ all_parser = target_subparsers.add_parser(
86
+ "all",
87
+ help="Show read-only status for every configured family",
88
+ )
89
+ _set_handler(all_parser, "status", "status", "all")
30
90
 
31
91
 
32
- def _add_action_subparsers(
92
+ def _add_reset_subparsers(
93
+ subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
94
+ ) -> None:
95
+ reset_parser = subparsers.add_parser("reset", help="Apply one explicit reset mode")
96
+ family_subparsers = reset_parser.add_subparsers(dest="reset_family", required=True)
97
+ for family in _FAMILIES:
98
+ family_parser = family_subparsers.add_parser(family, help=f"Reset {family} task state")
99
+ family_parser.add_argument("operand", nargs="?", help="Task ID, range, or family plan path.")
100
+ family_parser.add_argument("second_operand", nargs="?", help="Optional family plan path.")
101
+ family_parser.add_argument("--state-only", action="store_true")
102
+ family_parser.add_argument("--fresh-checkout", action="store_true")
103
+ family_parser.add_argument("--clean", action="store_true")
104
+ family_parser.add_argument("--preserve-runtime", action="store_true")
105
+ family_parser.add_argument("--all", action="store_true")
106
+ family_parser.add_argument("--auto-skipped", action="store_true")
107
+ family_parser.add_argument("--failed", action="store_true")
108
+ family_parser.add_argument("--stalled", action="store_true")
109
+ _set_handler(family_parser, "reset", "reset", family)
110
+
111
+
112
+ def _add_daemon_start_arguments(parser: argparse.ArgumentParser, family: str) -> None:
113
+ parser.add_argument("list_path", nargs="?", help="Optional family plan path.")
114
+ parser.add_argument("--env", help="Quoted KEY=VALUE daemon environment overrides.")
115
+ parser.add_argument("--mode", choices=("lite", "standard", "full"))
116
+ if family == "feature":
117
+ parser.add_argument("--features", help="Feature ID, range, or comma-separated selection.")
118
+
119
+
120
+ def _add_daemon_subparsers(
121
+ subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
122
+ ) -> None:
123
+ daemon_parser = subparsers.add_parser("daemon", help="Manage background pipeline processes")
124
+ family_subparsers = daemon_parser.add_subparsers(dest="daemon_family", required=True)
125
+ for family in _FAMILIES:
126
+ family_parser = family_subparsers.add_parser(family, help=f"Manage the {family} daemon")
127
+ action_subparsers = family_parser.add_subparsers(dest="daemon_action", required=True)
128
+ for action in ("start", "stop", "status", "logs", "restart"):
129
+ action_parser = action_subparsers.add_parser(action, help=f"{action.title()} the {family} daemon")
130
+ if action in {"start", "restart"}:
131
+ _add_daemon_start_arguments(action_parser, family)
132
+ elif action == "logs":
133
+ action_parser.add_argument("--lines", "-n", type=_non_negative_int, default=50)
134
+ action_parser.add_argument("--follow", "-f", action="store_true")
135
+ _set_handler(action_parser, "daemon", action, family)
136
+
137
+
138
+ def _add_diagnostics_subparsers(
33
139
  subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
34
- group: str,
35
- actions: Sequence[str],
36
140
  ) -> None:
37
- group_parser = subparsers.add_parser(group, help=f"{group} pipeline commands")
38
- action_subparsers = group_parser.add_subparsers(dest="action_name", required=True)
39
- for action in actions:
40
- action_parser = action_subparsers.add_parser(action, help=f"Run {group} {action}")
41
- _add_passthrough_options(action_parser)
42
- _set_handler(action_parser, group, action)
43
-
44
-
45
- def _add_reset_subparsers(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
46
- reset_parser = subparsers.add_parser("reset", help="Explicit reset commands")
47
- reset_subparsers = reset_parser.add_subparsers(dest="reset_target", required=True)
48
- for target in ("feature", "bugfix", "refactor"):
49
- target_parser = reset_subparsers.add_parser(target, help=f"Run {target} reset")
50
- _add_passthrough_options(target_parser)
51
- _set_handler(target_parser, "reset", "reset", target)
52
- describe_parser = reset_subparsers.add_parser("describe", help="Describe reset command boundaries")
53
- _set_handler(describe_parser, "reset", "describe")
54
-
55
-
56
- def _add_daemon_subparsers(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
57
- daemon_parser = subparsers.add_parser("daemon", help="Daemon lifecycle commands")
58
- target_subparsers = daemon_parser.add_subparsers(dest="daemon_target", required=True)
59
- for target in ("feature", "bugfix", "refactor"):
60
- target_parser = target_subparsers.add_parser(target, help=f"{target} daemon commands")
61
- action_subparsers = target_parser.add_subparsers(dest="daemon_action", required=True)
62
- for action in ("start", "stop", "status", "logs", "restart", "describe"):
63
- action_parser = action_subparsers.add_parser(action, help=f"Run daemon {target} {action}")
64
- _add_passthrough_options(action_parser)
65
- _set_handler(action_parser, "daemon", action, target)
66
-
67
-
68
- def _add_status_subparsers(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
69
- status_parser = subparsers.add_parser("status", help="Status commands")
70
- status_subparsers = status_parser.add_subparsers(dest="status_target", required=True)
71
- for target in ("feature", "bugfix", "refactor", "all"):
72
- target_parser = status_subparsers.add_parser(target, help=f"Show {target} status")
73
- _add_passthrough_options(target_parser)
74
- _set_handler(target_parser, "status", "status", target)
75
-
76
-
77
- def _add_diagnostics_subparsers(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
78
- diagnostics_parser = subparsers.add_parser("diagnostics", help="Python runtime diagnostics")
79
- diagnostics_subparsers = diagnostics_parser.add_subparsers(dest="diagnostics_action", required=True)
80
- for action in ("paths", "entrypoints", "version"):
81
- action_parser = diagnostics_subparsers.add_parser(action, help=f"Show {action} diagnostics")
141
+ diagnostics_parser = subparsers.add_parser("diagnostics", help="Inspect Python runtime configuration")
142
+ action_subparsers = diagnostics_parser.add_subparsers(dest="diagnostics_action", required=True)
143
+ for action in ("paths", "entrypoints", "version", "ai-cli"):
144
+ action_parser = action_subparsers.add_parser(action, help=f"Show {action} diagnostics")
82
145
  action_parser.set_defaults(handler="diagnostics", action=action)
83
146
 
84
147
 
85
- def _add_helper_subparser(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
86
- helper_parser = subparsers.add_parser("helper", help="Cross-platform prompt runtime helper commands")
148
+ def _add_helper_subparser(
149
+ subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
150
+ ) -> None:
151
+ helper_parser = subparsers.add_parser("helper", help="Run low-level cross-platform helper commands")
152
+ helper_parser.add_argument("helper_command", nargs="?", help="Helper command namespace.")
87
153
  helper_parser.add_argument(
88
- "args",
154
+ "helper_args",
89
155
  nargs=argparse.REMAINDER,
90
156
  help="Arguments forwarded to prizmkit-runtime-helper.",
91
157
  )
92
- helper_parser.set_defaults(handler="helper", group="helper", action="run", target=None)
93
-
94
-
95
- _PIPELINE_GROUPS = {"feature", "bugfix", "refactor", "recovery"}
96
- _GLOBAL_OPTIONS_WITH_VALUES = {"--project-root", "--pipeline-root"}
158
+ _set_handler(helper_parser, "helper", "run")
97
159
 
98
160
 
99
- def _split_passthrough_args(argv: Sequence[str]) -> tuple[list[str], tuple[str, ...]]:
100
- """Split parser-owned command tokens from legacy args while preserving exact arg order."""
161
+ def _parser_argv(argv: Sequence[str]) -> list[str]:
162
+ """Normalize interspersed leaf operands for argparse validation only."""
101
163
  tokens = [str(arg) for arg in argv]
102
- prefix: list[str] = []
103
164
  index = 0
104
165
  while index < len(tokens):
105
166
  token = tokens[index]
106
167
  if token in _GLOBAL_OPTIONS_WITH_VALUES:
107
- prefix.append(token)
108
- index += 1
109
- if index < len(tokens):
110
- prefix.append(tokens[index])
111
- index += 1
168
+ index += 2
112
169
  continue
113
170
  if any(token.startswith(f"{option}=") for option in _GLOBAL_OPTIONS_WITH_VALUES):
114
- prefix.append(token)
115
171
  index += 1
116
172
  continue
117
173
  break
174
+ if index >= len(tokens):
175
+ return tokens
118
176
 
177
+ group = tokens[index]
178
+ depth = {"run": 2, "status": 2, "reset": 2, "daemon": 3}.get(group)
179
+ if depth is None or len(tokens) <= index + depth:
180
+ return tokens
181
+
182
+ command_end = index + depth
183
+ operands: list[str] = []
184
+ options: list[str] = []
185
+ tail = tokens[command_end:]
186
+ tail_index = 0
187
+ while tail_index < len(tail):
188
+ token = tail[tail_index]
189
+ option_name = token.split("=", 1)[0]
190
+ if token.startswith("-"):
191
+ options.append(token)
192
+ if (
193
+ "=" not in token
194
+ and option_name in _LEAF_OPTIONS_WITH_VALUES
195
+ and tail_index + 1 < len(tail)
196
+ ):
197
+ tail_index += 1
198
+ options.append(tail[tail_index])
199
+ else:
200
+ operands.append(token)
201
+ tail_index += 1
202
+ return [*tokens[:command_end], *operands, *options]
203
+
204
+
205
+ def _runtime_action_args(argv: Sequence[str]) -> tuple[str, ...]:
206
+ """Return the original validated leaf arguments in their exact order."""
207
+ tokens = [str(arg) for arg in argv]
208
+ index = 0
209
+ while index < len(tokens):
210
+ token = tokens[index]
211
+ if token in _GLOBAL_OPTIONS_WITH_VALUES:
212
+ index += 2
213
+ continue
214
+ if any(token.startswith(f"{option}=") for option in _GLOBAL_OPTIONS_WITH_VALUES):
215
+ index += 1
216
+ continue
217
+ break
119
218
  if index >= len(tokens):
120
- return tokens, ()
219
+ return ()
121
220
 
122
221
  group = tokens[index]
123
- if group in _PIPELINE_GROUPS:
124
- required_parts = 2
125
- elif group in {"reset", "status", "diagnostics"}:
126
- required_parts = 2
127
- elif group == "daemon":
128
- required_parts = 3
129
- else:
130
- return tokens, ()
131
-
132
- command_end = min(len(tokens), index + required_parts)
133
- prefix.extend(tokens[index:command_end])
134
- passthrough = tuple(tokens[command_end:])
135
- if group == "diagnostics":
136
- return tokens, ()
137
- return prefix, passthrough
222
+ depth = {
223
+ "run": 2,
224
+ "status": 2,
225
+ "reset": 2,
226
+ "daemon": 3,
227
+ "diagnostics": 2,
228
+ "helper": 1,
229
+ }.get(group)
230
+ if depth is None:
231
+ return ()
232
+ return tuple(tokens[index + depth :])
138
233
 
139
234
 
140
235
  def build_parser() -> argparse.ArgumentParser:
141
- """Build the standard-library argparse command tree."""
236
+ """Build the standard-library operation-first argparse command tree."""
142
237
  parser = argparse.ArgumentParser(
143
238
  prog="prizmkit-runtime",
144
239
  description=DESCRIPTION,
@@ -154,13 +249,10 @@ def build_parser() -> argparse.ArgumentParser:
154
249
  )
155
250
  subparsers = parser.add_subparsers(dest="command_group", required=True)
156
251
 
157
- _add_action_subparsers(subparsers, "feature", ("run", "status", "reset", "unskip", "test-cli", "describe"))
158
- _add_action_subparsers(subparsers, "bugfix", ("run", "status", "reset", "unskip", "describe"))
159
- _add_action_subparsers(subparsers, "refactor", ("run", "status", "reset", "unskip", "describe"))
160
- _add_action_subparsers(subparsers, "recovery", ("run", "detect", "describe"))
252
+ _add_run_subparsers(subparsers)
253
+ _add_status_subparsers(subparsers)
161
254
  _add_reset_subparsers(subparsers)
162
255
  _add_daemon_subparsers(subparsers)
163
- _add_status_subparsers(subparsers)
164
256
  _add_diagnostics_subparsers(subparsers)
165
257
  _add_helper_subparser(subparsers)
166
258
  return parser
@@ -174,14 +266,6 @@ def _dispatch(args: argparse.Namespace) -> CommandResult:
174
266
  paths = _runtime_paths_from_args(args)
175
267
  if args.handler == "diagnostics":
176
268
  return handle_diagnostics(args.action, paths)
177
- if args.handler == "helper":
178
- helper_result = run_helper_command(tuple(getattr(args, "args", ()) or ()))
179
- return CommandResult(
180
- helper_result.exit_code,
181
- "Python runtime helper",
182
- "Cross-platform prompt runtime helper result.",
183
- stdout=render_result(helper_result, json_output="--json" in tuple(getattr(args, "args", ()) or ())),
184
- )
185
269
  return handle_runtime_command(
186
270
  args.group,
187
271
  args.action,
@@ -192,7 +276,7 @@ def _dispatch(args: argparse.Namespace) -> CommandResult:
192
276
 
193
277
 
194
278
  def main(argv: Sequence[str] | None = None) -> int:
195
- """Run the unified runtime CLI."""
279
+ """Run the canonical operation-first runtime CLI."""
196
280
  try:
197
281
  ensure_supported_python()
198
282
  except UnsupportedPythonVersion as exc:
@@ -201,10 +285,8 @@ def main(argv: Sequence[str] | None = None) -> int:
201
285
 
202
286
  parser = build_parser()
203
287
  parse_argv = list(sys.argv[1:] if argv is None else argv)
204
- parser_argv, passthrough_args = _split_passthrough_args(parse_argv)
205
- args = parser.parse_args(parser_argv)
206
- if passthrough_args:
207
- args.args = tuple(getattr(args, "args", ()) or ()) + passthrough_args
288
+ args = parser.parse_args(_parser_argv(parse_argv))
289
+ args.args = _runtime_action_args(parse_argv)
208
290
  result = _dispatch(args)
209
291
  if result.stderr:
210
292
  print(result.stderr, file=sys.stderr)