prizmkit 1.1.99 → 1.1.100

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 (57) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/lib/common.sh +244 -25
  3. package/bundled/dev-pipeline/reset-bug.sh +30 -0
  4. package/bundled/dev-pipeline/reset-feature.sh +30 -0
  5. package/bundled/dev-pipeline/reset-refactor.sh +30 -0
  6. package/bundled/dev-pipeline/run-bugfix.sh +128 -13
  7. package/bundled/dev-pipeline/run-feature.sh +130 -14
  8. package/bundled/dev-pipeline/run-refactor.sh +128 -13
  9. package/bundled/dev-pipeline/scripts/check-session-status.py +74 -1
  10. package/bundled/dev-pipeline/scripts/continuation.py +374 -0
  11. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +27 -30
  12. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +15 -20
  13. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +15 -20
  14. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +146 -16
  15. package/bundled/dev-pipeline/scripts/update-bug-status.py +214 -6
  16. package/bundled/dev-pipeline/scripts/update-feature-status.py +237 -6
  17. package/bundled/dev-pipeline/scripts/update-refactor-status.py +214 -6
  18. package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +1 -1
  19. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +2 -2
  20. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +3 -3
  21. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +3 -3
  22. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +1 -1
  23. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +3 -3
  24. package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +31 -66
  25. package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
  26. package/bundled/dev-pipeline/tests/conftest.py +1 -0
  27. package/bundled/dev-pipeline/tests/test_auto_skip.py +510 -0
  28. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +103 -0
  29. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +60 -0
  30. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +43 -0
  31. package/bundled/dev-pipeline-windows/lib/common.ps1 +172 -10
  32. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +93 -9
  33. package/bundled/dev-pipeline-windows/lib/reset.ps1 +34 -0
  34. package/bundled/dev-pipeline-windows/reset-bug.ps1 +1 -0
  35. package/bundled/dev-pipeline-windows/reset-feature.ps1 +1 -0
  36. package/bundled/dev-pipeline-windows/reset-refactor.ps1 +1 -0
  37. package/bundled/dev-pipeline-windows/scripts/check-session-status.py +74 -1
  38. package/bundled/dev-pipeline-windows/scripts/continuation.py +374 -0
  39. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +27 -30
  40. package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +16 -20
  41. package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +16 -20
  42. package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +146 -16
  43. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +214 -6
  44. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +237 -6
  45. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +214 -6
  46. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +1 -1
  47. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +2 -2
  48. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +3 -3
  49. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +3 -3
  50. package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +1 -1
  51. package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +3 -3
  52. package/bundled/dev-pipeline-windows/templates/sections/log-size-awareness.md +31 -68
  53. package/bundled/dev-pipeline-windows/templates/session-status-schema.json +1 -1
  54. package/bundled/skills/_metadata.json +1 -1
  55. package/package.json +1 -1
  56. package/bundled/dev-pipeline/scripts/monitor-log.sh +0 -104
  57. package/bundled/dev-pipeline-windows/scripts/monitor-log.ps1 +0 -102
@@ -0,0 +1,374 @@
1
+ """Continuation prompt and durable handoff helpers for pipeline prompt generators."""
2
+
3
+ import os
4
+
5
+ SUPPORTED_CONTINUATION_MODES = {"context_overflow"}
6
+ TASK_TYPES = {"feature", "bugfix", "refactor"}
7
+
8
+
9
+ def add_continuation_args(parser, default_task_type):
10
+ """Add continuation-mode CLI arguments to a prompt generator parser."""
11
+ parser.add_argument(
12
+ "--continuation-mode",
13
+ choices=sorted(SUPPORTED_CONTINUATION_MODES),
14
+ default="",
15
+ help="Continuation recovery mode, currently context_overflow.",
16
+ )
17
+ parser.add_argument(
18
+ "--previous-session-id",
19
+ default="",
20
+ help="Session ID that triggered this continuation.",
21
+ )
22
+ parser.add_argument(
23
+ "--continuation-count",
24
+ default="0",
25
+ help="Cumulative continuation count for this task.",
26
+ )
27
+ parser.add_argument(
28
+ "--context-overflow-count",
29
+ default="0",
30
+ help="Cumulative context overflow count for this task.",
31
+ )
32
+ parser.add_argument(
33
+ "--task-type",
34
+ choices=sorted(TASK_TYPES),
35
+ default=default_task_type,
36
+ help="Task type for continuation handoff metadata.",
37
+ )
38
+ parser.add_argument(
39
+ "--active-dev-branch",
40
+ default="",
41
+ help="Development branch being reused by the continuation.",
42
+ )
43
+ parser.add_argument(
44
+ "--base-branch",
45
+ default="",
46
+ help="Base branch preserved across continuation sessions.",
47
+ )
48
+ parser.add_argument(
49
+ "--continuation-summary-path",
50
+ default="",
51
+ help="Durable continuation summary artifact path.",
52
+ )
53
+
54
+
55
+ def is_continuation(args):
56
+ """Return True when continuation mode is active."""
57
+ return bool(getattr(args, "continuation_mode", ""))
58
+
59
+
60
+ def _safe_int(value, default=0):
61
+ try:
62
+ parsed = int(value)
63
+ except (TypeError, ValueError):
64
+ return default
65
+ return parsed if parsed >= 0 else default
66
+
67
+
68
+ def _norm(path):
69
+ return str(path).replace(os.sep, "/")
70
+
71
+
72
+ def _rel_path(project_root, path):
73
+ """Return a stable project-relative path when possible."""
74
+ if not path:
75
+ return "(not available)"
76
+ text = str(path)
77
+ if text.startswith(".prizmkit/") or text.startswith(".prizmkit\\"):
78
+ return _norm(text)
79
+ try:
80
+ abs_path = os.path.abspath(text)
81
+ root = os.path.abspath(project_root)
82
+ rel = os.path.relpath(abs_path, root)
83
+ if not rel.startswith(".."): # keep paths under the project concise
84
+ return _norm(rel)
85
+ except ValueError:
86
+ pass
87
+ return _norm(text)
88
+
89
+
90
+ def _dedupe(items):
91
+ result = []
92
+ seen = set()
93
+ for item in items:
94
+ if not item:
95
+ continue
96
+ key = _norm(item)
97
+ if key in seen:
98
+ continue
99
+ seen.add(key)
100
+ result.append(item)
101
+ return result
102
+
103
+
104
+ def continuation_summary_paths(task_type, task_id, task_slug, override_path=""):
105
+ """Return all durable continuation summary paths to write."""
106
+ if task_type == "feature":
107
+ default_path = os.path.join(
108
+ ".prizmkit", "specs", task_slug, "continuation-summary.md",
109
+ )
110
+ return _dedupe([override_path, default_path])
111
+
112
+ task_dir = "bugfix" if task_type == "bugfix" else "refactor"
113
+ task_local = os.path.join(
114
+ ".prizmkit", task_dir, task_id, "continuation-summary.md",
115
+ )
116
+ specs_path = os.path.join(
117
+ ".prizmkit", "specs", task_slug or task_id, "continuation-summary.md",
118
+ )
119
+ return _dedupe([override_path, specs_path, task_local])
120
+
121
+
122
+ def durable_paths(task_type, task_id, task_slug):
123
+ """Return stable durable artifact paths for a task."""
124
+ if task_type == "feature":
125
+ artifact_dir = os.path.join(".prizmkit", "specs", task_slug)
126
+ elif task_type == "bugfix":
127
+ artifact_dir = os.path.join(".prizmkit", "bugfix", task_id)
128
+ else:
129
+ artifact_dir = os.path.join(".prizmkit", "refactor", task_id)
130
+
131
+ return {
132
+ "artifact_dir": artifact_dir,
133
+ "checkpoint": os.path.join(artifact_dir, "workflow-checkpoint.json"),
134
+ "plan": os.path.join(artifact_dir, "plan.md"),
135
+ "context_snapshot": os.path.join(artifact_dir, "context-snapshot.md"),
136
+ "failure_log": os.path.join(artifact_dir, "failure-log.md"),
137
+ }
138
+
139
+
140
+ def runtime_paths(args, project_root, task_type, task_id, previous_session_id):
141
+ """Return fresh runtime paths for the new session and previous log pointer."""
142
+ output_path = getattr(args, "output", "")
143
+ session_dir = os.path.dirname(os.path.abspath(output_path)) if output_path else ""
144
+ logs_dir = os.path.join(session_dir, "logs") if session_dir else ""
145
+ session_status = os.path.join(session_dir, "session-status.json") if session_dir else ""
146
+
147
+ state_dir = getattr(args, "state_dir", "") or os.path.join(
148
+ ".prizmkit", "state", "features" if task_type == "feature" else task_type,
149
+ )
150
+ previous_log = "(unknown)"
151
+ if previous_session_id:
152
+ previous_log = os.path.join(
153
+ state_dir, task_id, "sessions", previous_session_id, "logs", "session.log",
154
+ )
155
+
156
+ return {
157
+ "session_dir": _rel_path(project_root, session_dir),
158
+ "bootstrap_prompt": _rel_path(project_root, output_path),
159
+ "session_log": _rel_path(project_root, os.path.join(logs_dir, "session.log")),
160
+ "progress_json": _rel_path(project_root, os.path.join(logs_dir, "progress.json")),
161
+ "log_monitor_status": _rel_path(project_root, os.path.join(logs_dir, "log-monitor-status.txt")),
162
+ "log_monitor_baseline": _rel_path(project_root, os.path.join(logs_dir, "log-monitor-baseline.txt")),
163
+ "pid_file": _rel_path(project_root, os.path.join(logs_dir, "ai.pid")),
164
+ "session_status": _rel_path(project_root, session_status),
165
+ "previous_session_log": _rel_path(project_root, previous_log),
166
+ }
167
+
168
+
169
+ def checkpoint_cursor(checkpoint):
170
+ """Summarize current checkpoint state and cursor."""
171
+ steps = checkpoint.get("steps", []) if isinstance(checkpoint, dict) else []
172
+ completed = []
173
+ skipped = []
174
+ in_progress = []
175
+ pending = []
176
+ failed = []
177
+ first_cursor = None
178
+
179
+ for step in steps:
180
+ skill = step.get("skill", "unknown")
181
+ label = "{} ({})".format(step.get("id", "?"), skill)
182
+ status = step.get("status", "pending")
183
+ if status == "completed":
184
+ completed.append(label)
185
+ elif status == "skipped":
186
+ skipped.append(label)
187
+ elif status == "in_progress":
188
+ in_progress.append(label)
189
+ if first_cursor is None:
190
+ first_cursor = label
191
+ elif status == "failed":
192
+ failed.append(label)
193
+ if first_cursor is None:
194
+ first_cursor = label
195
+ else:
196
+ pending.append(label)
197
+ if first_cursor is None:
198
+ first_cursor = label
199
+
200
+ if first_cursor is None:
201
+ first_cursor = "(all checkpoint steps completed or skipped)"
202
+
203
+ return {
204
+ "completed": completed,
205
+ "skipped": skipped,
206
+ "in_progress": in_progress,
207
+ "pending": pending,
208
+ "failed": failed,
209
+ "first_cursor": first_cursor,
210
+ }
211
+
212
+
213
+ def _list_or_none(values):
214
+ if not values:
215
+ return "- (none)"
216
+ return "\n".join("- {}".format(value) for value in values)
217
+
218
+
219
+ def _build_summary(context):
220
+ cursor = context["cursor"]
221
+ durable = context["durable"]
222
+ runtime = context["runtime"]
223
+ summary_paths = context["summary_paths"]
224
+ lines = [
225
+ "# Continuation Summary",
226
+ "",
227
+ "## Reason",
228
+ "",
229
+ "- Continuation mode: {}".format(context["mode"]),
230
+ "- This continuation was started after context overflow.",
231
+ "- This artifact is the durable handoff summary; do not rely on the previous full transcript.",
232
+ "",
233
+ "## Previous Session",
234
+ "",
235
+ "- Previous session id: {}".format(context["previous_session_id"] or "(unknown)"),
236
+ "- Previous session log: {}".format(runtime["previous_session_log"]),
237
+ "- Previous full session.log should only be read if synthesized artifacts are insufficient.",
238
+ "",
239
+ "## Task Identity",
240
+ "",
241
+ "- Task type: {}".format(context["task_type"]),
242
+ "- Task id: {}".format(context["task_id"]),
243
+ "- Task slug: {}".format(context["task_slug"]),
244
+ "- Active dev branch: {}".format(context["active_dev_branch"] or "(preserve current worktree branch)"),
245
+ "- Base branch: {}".format(context["base_branch"] or "(unknown)"),
246
+ "- Continuation count: {}".format(context["continuation_count"]),
247
+ "- Context overflow count: {}".format(context["context_overflow_count"]),
248
+ "",
249
+ "## Observed Partial Progress",
250
+ "",
251
+ "- First pending/in-progress checkpoint step: {}".format(cursor["first_cursor"]),
252
+ "- Completed checkpoint steps:",
253
+ _list_or_none(cursor["completed"]),
254
+ "- Skipped checkpoint steps:",
255
+ _list_or_none(cursor["skipped"]),
256
+ "- In-progress checkpoint steps:",
257
+ _list_or_none(cursor["in_progress"]),
258
+ "- Pending checkpoint steps:",
259
+ _list_or_none(cursor["pending"]),
260
+ "- Failed checkpoint steps:",
261
+ _list_or_none(cursor["failed"]),
262
+ "",
263
+ "## Durable Pointers",
264
+ "",
265
+ "- Start with current worktree state: `git status` and `git diff`.",
266
+ "- Durable task artifact directory: {}".format(_norm(durable["artifact_dir"])),
267
+ "- Workflow checkpoint: {}".format(_norm(durable["checkpoint"])),
268
+ "- Context snapshot: {}".format(_norm(durable["context_snapshot"])),
269
+ "- Plan: {}".format(_norm(durable["plan"])),
270
+ "- Failure log: {}".format(_norm(durable["failure_log"])),
271
+ "- Continuation summaries:",
272
+ _list_or_none([_norm(path) for path in summary_paths]),
273
+ "",
274
+ ]
275
+ return "\n".join(lines)
276
+
277
+
278
+ def _build_addendum(context):
279
+ cursor = context["cursor"]
280
+ durable = context["durable"]
281
+ runtime = context["runtime"]
282
+ summary_paths = context["summary_paths"]
283
+ lines = [
284
+ "## Continuation Addendum — Context Overflow Recovery",
285
+ "",
286
+ "This is an automatic continuation after context overflow.",
287
+ "This is a fresh headless AI CLI session for the same task.",
288
+ "Continue on the current branch/worktree.",
289
+ "Do not reset, clean, switch branches, or discard work.",
290
+ "",
291
+ "### Required First Actions",
292
+ "",
293
+ "- Start with `git status` and `git diff`.",
294
+ "- Reuse the same durable task artifact directory: `{}`.".format(_norm(durable["artifact_dir"])),
295
+ "- Treat completed/skipped checkpoint steps as already done.",
296
+ "- Continue from the first pending/in-progress checkpoint step: `{}`.".format(cursor["first_cursor"]),
297
+ "- Do not read the previous full `session.log` unless explicitly necessary.",
298
+ "- If previous runtime context is needed, prefer synthesized artifacts and summaries over logs.",
299
+ "- Do not attempt `/compact` as the recovery mechanism.",
300
+ "",
301
+ "### Preserved Durable Context",
302
+ "",
303
+ "- Task type: `{}`".format(context["task_type"]),
304
+ "- Task id: `{}`".format(context["task_id"]),
305
+ "- Task slug: `{}`".format(context["task_slug"]),
306
+ "- Dev branch: `{}`".format(context["active_dev_branch"] or "(current branch)"),
307
+ "- Base branch: `{}`".format(context["base_branch"] or "(unknown)"),
308
+ "- Checkpoint path: `{}`".format(_norm(durable["checkpoint"])),
309
+ "- Plan path: `{}`".format(_norm(durable["plan"])),
310
+ "- Context snapshot path: `{}`".format(_norm(durable["context_snapshot"])),
311
+ "- Failure log path: `{}`".format(_norm(durable["failure_log"])),
312
+ "- Continuation summary path(s): `{}`".format(", ".join(_norm(p) for p in summary_paths)),
313
+ "- Continuation count: `{}`".format(context["continuation_count"]),
314
+ "- Context overflow count: `{}`".format(context["context_overflow_count"]),
315
+ "- Previous session id: `{}`".format(context["previous_session_id"] or "(unknown)"),
316
+ "- Previous session log reference: `{}`".format(runtime["previous_session_log"]),
317
+ "",
318
+ "### Fresh Session Runtime Paths",
319
+ "",
320
+ "- Fresh session directory: `{}`".format(runtime["session_dir"]),
321
+ "- Generated bootstrap prompt path: `{}`".format(runtime["bootstrap_prompt"]),
322
+ "- Session log path: `{}`".format(runtime["session_log"]),
323
+ "- Progress JSON path: `{}`".format(runtime["progress_json"]),
324
+ "- Log monitor status path: `{}`".format(runtime["log_monitor_status"]),
325
+ "- Log monitor baseline path: `{}`".format(runtime["log_monitor_baseline"]),
326
+ "- PID file: `{}`".format(runtime["pid_file"]),
327
+ "- Session status file: `{}`".format(runtime["session_status"]),
328
+ "",
329
+ ]
330
+ return "\n".join(lines)
331
+
332
+
333
+ def append_continuation_handoff(rendered_prompt, args, project_root, task_type,
334
+ task_id, task_slug, checkpoint,
335
+ summary_paths=None):
336
+ """Write continuation summaries and append the addendum to a prompt."""
337
+ if not is_continuation(args):
338
+ return rendered_prompt
339
+
340
+ mode = getattr(args, "continuation_mode", "")
341
+ if mode not in SUPPORTED_CONTINUATION_MODES:
342
+ raise ValueError("Unsupported continuation mode: {}".format(mode))
343
+
344
+ durable = durable_paths(task_type, task_id, task_slug)
345
+ paths = summary_paths or continuation_summary_paths(
346
+ task_type, task_id, task_slug, getattr(args, "continuation_summary_path", ""),
347
+ )
348
+ paths = [_rel_path(project_root, path) for path in paths]
349
+ previous_session_id = getattr(args, "previous_session_id", "")
350
+ context = {
351
+ "mode": mode,
352
+ "task_type": task_type,
353
+ "task_id": task_id,
354
+ "task_slug": task_slug,
355
+ "previous_session_id": previous_session_id,
356
+ "continuation_count": _safe_int(getattr(args, "continuation_count", 0)),
357
+ "context_overflow_count": _safe_int(getattr(args, "context_overflow_count", 0)),
358
+ "active_dev_branch": getattr(args, "active_dev_branch", ""),
359
+ "base_branch": getattr(args, "base_branch", ""),
360
+ "durable": durable,
361
+ "runtime": runtime_paths(args, project_root, task_type, task_id, previous_session_id),
362
+ "cursor": checkpoint_cursor(checkpoint or {}),
363
+ "summary_paths": paths,
364
+ }
365
+
366
+ summary_text = _build_summary(context)
367
+ for summary_path in paths:
368
+ abs_summary = os.path.join(project_root, summary_path)
369
+ os.makedirs(os.path.dirname(abs_summary), exist_ok=True)
370
+ with open(abs_summary, "w", encoding="utf-8") as f:
371
+ f.write(summary_text)
372
+
373
+ addendum = _build_addendum(context)
374
+ return rendered_prompt.rstrip() + "\n\n" + addendum
@@ -28,11 +28,11 @@ import re
28
28
  import sys
29
29
 
30
30
  from utils import enrich_global_context, load_json_file, read_platform_conventions, setup_logging
31
+ from continuation import add_continuation_args, append_continuation_handoff, is_continuation
31
32
 
32
33
 
33
34
  DEFAULT_MAX_RETRIES = 3
34
35
  DEFAULT_MAX_LOG_SIZE = 2097152
35
- DEFAULT_LOG_MONITOR_POLL_INTERVAL = 30
36
36
 
37
37
  LOGGER = setup_logging("generate-bootstrap-prompt")
38
38
 
@@ -110,6 +110,8 @@ def parse_args():
110
110
  help="Run tests and extract baseline failures (slower, optional)",
111
111
  )
112
112
 
113
+ add_continuation_args(parser, "feature")
114
+
113
115
  return parser.parse_args()
114
116
 
115
117
 
@@ -1319,8 +1321,7 @@ def _tier_header(pipeline_mode):
1319
1321
  "lite": (
1320
1322
  "# Dev-Pipeline Session Bootstrap — Tier 1 (Single Agent)\n",
1321
1323
  "**Tier 1 — Single Agent**: You handle everything directly. "
1322
- "No normal work subagents; the log-size monitor is the only "
1323
- "allowed persistent background subagent. No TeamCreate.\n",
1324
+ "No normal work subagents. No TeamCreate.\n",
1324
1325
  ),
1325
1326
  "standard": (
1326
1327
  "# Dev-Pipeline Session Bootstrap — Tier 2 (Dual Agent)\n",
@@ -1358,8 +1359,7 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
1358
1359
  if pipeline_mode == "lite":
1359
1360
  specific = [
1360
1361
  "- Tier 1: you handle everything directly — no normal work "
1361
- "subagents needed; the log-size monitor is the only allowed "
1362
- "persistent background subagent",
1362
+ "subagents needed",
1363
1363
  ]
1364
1364
  elif pipeline_mode == "standard":
1365
1365
  specific = [
@@ -1372,8 +1372,7 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
1372
1372
  "- Gate checks enforce Implementation Log and Review Notes are "
1373
1373
  "written before proceeding",
1374
1374
  "- Do NOT use `run_in_background=true` when spawning normal work "
1375
- "subagents; the log-size monitor is the only allowed persistent "
1376
- "background subagent",
1375
+ "subagents",
1377
1376
  "- On timeout: check snapshot + git diff HEAD → model:lite → "
1378
1377
  "remaining steps only → max 2 retries per phase → "
1379
1378
  "orchestrator fallback",
@@ -1388,8 +1387,7 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
1388
1387
  "- Gate checks enforce Implementation Log and Review Notes are "
1389
1388
  "written before proceeding",
1390
1389
  "- Do NOT use `run_in_background=true` when spawning normal work "
1391
- "agents; the log-size monitor is the only allowed persistent "
1392
- "background subagent",
1390
+ "agents",
1393
1391
  "- On timeout: check snapshot → model:lite → remaining steps "
1394
1392
  "only → max 2 retries → orchestrator fallback",
1395
1393
  ]
@@ -1429,8 +1427,7 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
1429
1427
  if pipeline_mode != "lite":
1430
1428
  mission += (
1431
1429
  " When you spawn normal work subagents, wait for each to finish "
1432
- "(run_in_background=false). The log-size monitor is the only "
1433
- "allowed persistent background subagent."
1430
+ "(run_in_background=false)."
1434
1431
  )
1435
1432
  if pipeline_mode == "full":
1436
1433
  mission += (
@@ -1928,12 +1925,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
1928
1925
  dev_url = f"http://localhost:{dev_port}"
1929
1926
 
1930
1927
  max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
1931
- if max_log_size == 0:
1932
- poll_interval = DEFAULT_LOG_MONITOR_POLL_INTERVAL
1933
- else:
1934
- poll_interval = _parse_positive_int_env(
1935
- "LOG_MONITOR_POLL_INTERVAL", DEFAULT_LOG_MONITOR_POLL_INTERVAL,
1936
- )
1937
1928
 
1938
1929
  replacements = {
1939
1930
  "{{LOG_SIZE_AWARENESS}}": load_log_size_section(script_dir),
@@ -1983,22 +1974,10 @@ def build_replacements(args, feature, features, global_context, script_dir):
1983
1974
  "{{DEV_URL}}": dev_url,
1984
1975
  "{{MAX_LOG_SIZE}}": str(max_log_size),
1985
1976
  "{{MAX_LOG_SIZE_HUMAN}}": _format_bytes(max_log_size),
1986
- "{{LOG_MONITOR_POLL_INTERVAL}}": str(poll_interval),
1987
- "{{LOG_MONITOR_STATUS_FILE}}": os.path.join(
1988
- ".prizmkit", "state", "features", args.feature_id,
1989
- "sessions", args.session_id, "logs", "log-monitor-status.txt",
1990
- ),
1991
- "{{LOG_MONITOR_BASELINE_FILE}}": os.path.join(
1992
- ".prizmkit", "state", "features", args.feature_id,
1993
- "sessions", args.session_id, "logs", "log-monitor-baseline.txt",
1994
- ),
1995
1977
  "{{SESSION_LOG_PATH}}": os.path.join(
1996
1978
  ".prizmkit", "state", "features", args.feature_id,
1997
1979
  "sessions", args.session_id, "logs", "session.log",
1998
1980
  ),
1999
- "{{LOG_MONITOR_SCRIPT}}": os.path.join(
2000
- ".prizmkit", "dev-pipeline", "scripts", "monitor-log.sh",
2001
- ),
2002
1981
  }
2003
1982
 
2004
1983
  return replacements, effective_resume, browser_enabled, browser_tool
@@ -2085,6 +2064,9 @@ def main():
2085
2064
  agent_prompt_replacements = load_agent_prompts(templates_dir)
2086
2065
  replacements.update(agent_prompt_replacements)
2087
2066
 
2067
+ if is_continuation(args):
2068
+ replacements["{{LOG_SIZE_AWARENESS}}"] = ""
2069
+
2088
2070
  # Extract state needed for assembly
2089
2071
  pipeline_mode = replacements.get("{{PIPELINE_MODE}}", "lite")
2090
2072
  init_done = replacements.get("{{INIT_DONE}}", "false") == "true"
@@ -2104,6 +2086,11 @@ def main():
2104
2086
  retry_count=int(args.retry_count),
2105
2087
  browser_tool=browser_tool,
2106
2088
  )
2089
+ if is_continuation(args):
2090
+ sections = [
2091
+ (name, content) for name, content in sections
2092
+ if name != "log-size-awareness"
2093
+ ]
2107
2094
  rendered = render_from_sections(sections, replacements)
2108
2095
  except FileNotFoundError as exc:
2109
2096
  LOGGER.warning(
@@ -2165,6 +2152,7 @@ def main():
2165
2152
  )
2166
2153
  feature_slug = replacements.get("{{FEATURE_SLUG}}", "")
2167
2154
  checkpoint_path = ""
2155
+ checkpoint = {}
2168
2156
 
2169
2157
  if use_sections and feature_slug:
2170
2158
  checkpoint = generate_checkpoint_definition(
@@ -2186,7 +2174,7 @@ def main():
2186
2174
  )
2187
2175
 
2188
2176
  # On resume, merge existing completed state (with artifact validation)
2189
- if is_resume and os.path.exists(checkpoint_path):
2177
+ if (is_resume or is_continuation(args)) and os.path.exists(checkpoint_path):
2190
2178
  try:
2191
2179
  with open(checkpoint_path, "r", encoding="utf-8") as f:
2192
2180
  existing = json.load(f)
@@ -2205,6 +2193,15 @@ def main():
2205
2193
  json.dump(checkpoint, f, indent=2, ensure_ascii=False)
2206
2194
  LOGGER.info("Wrote checkpoint to %s", checkpoint_path)
2207
2195
 
2196
+ if is_continuation(args):
2197
+ rendered = append_continuation_handoff(
2198
+ rendered, args, project_root, args.task_type, args.feature_id,
2199
+ feature_slug, checkpoint,
2200
+ )
2201
+ err = write_output(args.output, rendered)
2202
+ if err:
2203
+ emit_failure(err)
2204
+
2208
2205
  # ── Success JSON ───────────────────────────────────────────────────
2209
2206
  feature_model = feature.get("model", "")
2210
2207
  mode_agent_counts = {"lite": 1, "standard": 3, "full": 3}
@@ -20,11 +20,11 @@ import re
20
20
  import sys
21
21
 
22
22
  from utils import enrich_global_context, load_json_file, read_platform_conventions, setup_logging
23
+ from continuation import add_continuation_args, append_continuation_handoff, is_continuation
23
24
 
24
25
 
25
26
  DEFAULT_MAX_RETRIES = 3
26
27
  DEFAULT_MAX_LOG_SIZE = 2097152
27
- DEFAULT_LOG_MONITOR_POLL_INTERVAL = 30
28
28
 
29
29
  LOGGER = setup_logging("generate-bugfix-prompt")
30
30
 
@@ -47,6 +47,8 @@ def parse_args():
47
47
  parser.add_argument("--template", default=None, help="Custom template path. Defaults to {script_dir}/../templates/bugfix-bootstrap-prompt.md")
48
48
  parser.add_argument("--mode", default=None, help="Pipeline execution mode override: lite, standard, full")
49
49
  parser.add_argument("--critic", default=None, help="Enable critic agent: true/false")
50
+ add_continuation_args(parser, "bugfix")
51
+
50
52
  return parser.parse_args()
51
53
 
52
54
 
@@ -357,33 +359,15 @@ def build_replacements(args, bug, global_context, script_dir):
357
359
  browser_verify_steps = " # (reproduce bug and verify fix)"
358
360
 
359
361
  max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
360
- if max_log_size == 0:
361
- poll_interval = DEFAULT_LOG_MONITOR_POLL_INTERVAL
362
- else:
363
- poll_interval = _parse_positive_int_env(
364
- "LOG_MONITOR_POLL_INTERVAL", DEFAULT_LOG_MONITOR_POLL_INTERVAL,
365
- )
366
362
 
367
363
  replacements = {
368
364
  "{{LOG_SIZE_AWARENESS}}": load_log_size_section(script_dir),
369
365
  "{{MAX_LOG_SIZE}}": str(max_log_size),
370
366
  "{{MAX_LOG_SIZE_HUMAN}}": _format_bytes(max_log_size),
371
- "{{LOG_MONITOR_POLL_INTERVAL}}": str(poll_interval),
372
- "{{LOG_MONITOR_STATUS_FILE}}": os.path.join(
373
- ".prizmkit", "state", "bugfix", args.bug_id,
374
- "sessions", args.session_id, "logs", "log-monitor-status.txt",
375
- ),
376
- "{{LOG_MONITOR_BASELINE_FILE}}": os.path.join(
377
- ".prizmkit", "state", "bugfix", args.bug_id,
378
- "sessions", args.session_id, "logs", "log-monitor-baseline.txt",
379
- ),
380
367
  "{{SESSION_LOG_PATH}}": os.path.join(
381
368
  ".prizmkit", "state", "bugfix", args.bug_id,
382
369
  "sessions", args.session_id, "logs", "session.log",
383
370
  ),
384
- "{{LOG_MONITOR_SCRIPT}}": os.path.join(
385
- ".prizmkit", "dev-pipeline", "scripts", "monitor-log.sh",
386
- ),
387
371
  "{{RUN_ID}}": args.run_id,
388
372
  "{{SESSION_ID}}": args.session_id,
389
373
  "{{BUG_ID}}": args.bug_id,
@@ -660,6 +644,8 @@ def main():
660
644
 
661
645
  # Build replacements
662
646
  replacements = build_replacements(args, bug, global_context, script_dir)
647
+ if is_continuation(args):
648
+ replacements["{{LOG_SIZE_AWARENESS}}"] = ""
663
649
 
664
650
  # Add checkpoint path to replacements
665
651
  checkpoint_rel = os.path.join(
@@ -684,7 +670,7 @@ def main():
684
670
  checkpoint = generate_bugfix_checkpoint(args.bug_id, args.session_id)
685
671
 
686
672
  is_resume = args.resume_phase != "null"
687
- if is_resume and os.path.exists(checkpoint_path):
673
+ if (is_resume or is_continuation(args)) and os.path.exists(checkpoint_path):
688
674
  try:
689
675
  with open(checkpoint_path, "r", encoding="utf-8") as f:
690
676
  existing = json.load(f)
@@ -703,6 +689,15 @@ def main():
703
689
  json.dump(checkpoint, f, indent=2, ensure_ascii=False)
704
690
  LOGGER.info("Wrote bugfix checkpoint to %s", checkpoint_path)
705
691
 
692
+ if is_continuation(args):
693
+ rendered = append_continuation_handoff(
694
+ rendered, args, project_root, args.task_type, args.bug_id,
695
+ args.bug_id, checkpoint,
696
+ )
697
+ err = write_output(args.output, rendered)
698
+ if err:
699
+ emit_failure(err)
700
+
706
701
  # Resolve critic and mode
707
702
  bug_critic = bug.get("critic", False)
708
703
  if args.critic is not None: