prizmkit 1.1.98 → 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.
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/README.md +5 -5
- package/bundled/dev-pipeline/launch-bugfix-daemon.sh +1 -1
- package/bundled/dev-pipeline/launch-feature-daemon.sh +1 -1
- package/bundled/dev-pipeline/launch-refactor-daemon.sh +1 -1
- package/bundled/dev-pipeline/lib/common.sh +244 -25
- package/bundled/dev-pipeline/reset-bug.sh +32 -2
- package/bundled/dev-pipeline/reset-feature.sh +32 -2
- package/bundled/dev-pipeline/reset-refactor.sh +32 -2
- package/bundled/dev-pipeline/run-bugfix.sh +133 -18
- package/bundled/dev-pipeline/run-feature.sh +134 -18
- package/bundled/dev-pipeline/run-refactor.sh +133 -18
- package/bundled/dev-pipeline/scripts/check-session-status.py +74 -1
- package/bundled/dev-pipeline/scripts/continuation.py +374 -0
- package/bundled/dev-pipeline/scripts/detect-stuck.py +5 -5
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +29 -35
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +19 -25
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +19 -25
- package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +1 -1
- package/bundled/dev-pipeline/scripts/init-pipeline.py +1 -1
- package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +1 -1
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +146 -16
- package/bundled/dev-pipeline/scripts/update-bug-status.py +220 -13
- package/bundled/dev-pipeline/scripts/update-feature-status.py +244 -18
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +220 -13
- package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +1 -1
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +2 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +3 -3
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +3 -3
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +3 -3
- package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +31 -66
- package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
- package/bundled/dev-pipeline/tests/conftest.py +1 -0
- package/bundled/dev-pipeline/tests/test_auto_skip.py +518 -8
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +103 -0
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +60 -0
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +43 -0
- package/bundled/dev-pipeline-windows/lib/common.ps1 +172 -10
- package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +95 -11
- package/bundled/dev-pipeline-windows/lib/reset.ps1 +34 -0
- package/bundled/dev-pipeline-windows/reset-bug.ps1 +1 -0
- package/bundled/dev-pipeline-windows/reset-feature.ps1 +1 -0
- package/bundled/dev-pipeline-windows/reset-refactor.ps1 +1 -0
- package/bundled/dev-pipeline-windows/scripts/check-session-status.py +74 -1
- package/bundled/dev-pipeline-windows/scripts/continuation.py +374 -0
- package/bundled/dev-pipeline-windows/scripts/detect-stuck.py +5 -5
- package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +29 -35
- package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +20 -25
- package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +20 -25
- package/bundled/dev-pipeline-windows/scripts/init-bugfix-pipeline.py +1 -1
- package/bundled/dev-pipeline-windows/scripts/init-pipeline.py +1 -1
- package/bundled/dev-pipeline-windows/scripts/init-refactor-pipeline.py +1 -1
- package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +146 -16
- package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +220 -13
- package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +244 -18
- package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +220 -13
- package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +1 -1
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +2 -2
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +3 -3
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +3 -3
- package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +3 -3
- package/bundled/dev-pipeline-windows/templates/sections/log-size-awareness.md +31 -68
- package/bundled/dev-pipeline-windows/templates/session-status-schema.json +1 -1
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +2 -2
- package/bundled/skills/feature-pipeline-launcher/SKILL.md +2 -2
- package/bundled/skills/recovery-workflow/SKILL.md +2 -2
- package/bundled/skills/recovery-workflow/evals/evals.json +2 -2
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +2 -2
- package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +2 -2
- package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +2 -2
- package/bundled/skills-windows/recovery-workflow/SKILL.md +2 -2
- package/bundled/skills-windows/recovery-workflow/evals/evals.json +2 -2
- package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +2 -2
- package/package.json +1 -1
- package/bundled/dev-pipeline/scripts/monitor-log.sh +0 -104
- 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
|
|
@@ -113,8 +113,8 @@ def load_json(path):
|
|
|
113
113
|
|
|
114
114
|
|
|
115
115
|
def discover_item_ids(state_dir, subdir):
|
|
116
|
-
"""Return a sorted list of item IDs found
|
|
117
|
-
items_dir = os.path.join(state_dir, subdir)
|
|
116
|
+
"""Return a sorted list of item IDs found directly under the typed state dir."""
|
|
117
|
+
items_dir = os.path.join(state_dir, subdir) if subdir else state_dir
|
|
118
118
|
if not os.path.isdir(items_dir):
|
|
119
119
|
return []
|
|
120
120
|
ids = []
|
|
@@ -363,9 +363,9 @@ def find_task_list(state_dir):
|
|
|
363
363
|
|
|
364
364
|
# Pipeline type configurations
|
|
365
365
|
PIPELINE_CONFIG = {
|
|
366
|
-
"feature": {"subdir": "
|
|
367
|
-
"bugfix": {"subdir": "
|
|
368
|
-
"refactor": {"subdir": "
|
|
366
|
+
"feature": {"subdir": "", "items_key": "features", "id_label": "feature_id"},
|
|
367
|
+
"bugfix": {"subdir": "", "items_key": "bugs", "id_label": "bug_id"},
|
|
368
|
+
"refactor": {"subdir": "", "items_key": "refactors", "id_label": "refactor_id"},
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
|
|
@@ -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
|
|
|
@@ -392,9 +394,7 @@ def get_prev_session_status(state_dir, feature_id):
|
|
|
392
394
|
return "N/A (first run)"
|
|
393
395
|
|
|
394
396
|
# Try to read the feature status file to find the last session
|
|
395
|
-
feature_status_path = os.path.join(
|
|
396
|
-
state_dir, "features", feature_id, "status.json"
|
|
397
|
-
)
|
|
397
|
+
feature_status_path = os.path.join(state_dir, feature_id, "status.json")
|
|
398
398
|
if not os.path.isfile(feature_status_path):
|
|
399
399
|
return "N/A (first run)"
|
|
400
400
|
|
|
@@ -409,8 +409,7 @@ def get_prev_session_status(state_dir, feature_id):
|
|
|
409
409
|
return "N/A (first run)"
|
|
410
410
|
|
|
411
411
|
# Try to read the last session's session-status.json
|
|
412
|
-
session_status_path = os.path.join(
|
|
413
|
-
state_dir, "features", feature_id, "sessions",
|
|
412
|
+
session_status_path = os.path.join(state_dir, feature_id, "sessions",
|
|
414
413
|
last_session_id, "session-status.json"
|
|
415
414
|
)
|
|
416
415
|
if not os.path.isfile(session_status_path):
|
|
@@ -1322,8 +1321,7 @@ def _tier_header(pipeline_mode):
|
|
|
1322
1321
|
"lite": (
|
|
1323
1322
|
"# Dev-Pipeline Session Bootstrap — Tier 1 (Single Agent)\n",
|
|
1324
1323
|
"**Tier 1 — Single Agent**: You handle everything directly. "
|
|
1325
|
-
"No normal work subagents
|
|
1326
|
-
"allowed persistent background subagent. No TeamCreate.\n",
|
|
1324
|
+
"No normal work subagents. No TeamCreate.\n",
|
|
1327
1325
|
),
|
|
1328
1326
|
"standard": (
|
|
1329
1327
|
"# Dev-Pipeline Session Bootstrap — Tier 2 (Dual Agent)\n",
|
|
@@ -1361,8 +1359,7 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
|
|
|
1361
1359
|
if pipeline_mode == "lite":
|
|
1362
1360
|
specific = [
|
|
1363
1361
|
"- Tier 1: you handle everything directly — no normal work "
|
|
1364
|
-
"subagents needed
|
|
1365
|
-
"persistent background subagent",
|
|
1362
|
+
"subagents needed",
|
|
1366
1363
|
]
|
|
1367
1364
|
elif pipeline_mode == "standard":
|
|
1368
1365
|
specific = [
|
|
@@ -1375,8 +1372,7 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
|
|
|
1375
1372
|
"- Gate checks enforce Implementation Log and Review Notes are "
|
|
1376
1373
|
"written before proceeding",
|
|
1377
1374
|
"- Do NOT use `run_in_background=true` when spawning normal work "
|
|
1378
|
-
"subagents
|
|
1379
|
-
"background subagent",
|
|
1375
|
+
"subagents",
|
|
1380
1376
|
"- On timeout: check snapshot + git diff HEAD → model:lite → "
|
|
1381
1377
|
"remaining steps only → max 2 retries per phase → "
|
|
1382
1378
|
"orchestrator fallback",
|
|
@@ -1391,8 +1387,7 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
|
|
|
1391
1387
|
"- Gate checks enforce Implementation Log and Review Notes are "
|
|
1392
1388
|
"written before proceeding",
|
|
1393
1389
|
"- Do NOT use `run_in_background=true` when spawning normal work "
|
|
1394
|
-
"agents
|
|
1395
|
-
"background subagent",
|
|
1390
|
+
"agents",
|
|
1396
1391
|
"- On timeout: check snapshot → model:lite → remaining steps "
|
|
1397
1392
|
"only → max 2 retries → orchestrator fallback",
|
|
1398
1393
|
]
|
|
@@ -1432,8 +1427,7 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
|
|
|
1432
1427
|
if pipeline_mode != "lite":
|
|
1433
1428
|
mission += (
|
|
1434
1429
|
" When you spawn normal work subagents, wait for each to finish "
|
|
1435
|
-
"(run_in_background=false).
|
|
1436
|
-
"allowed persistent background subagent."
|
|
1430
|
+
"(run_in_background=false)."
|
|
1437
1431
|
)
|
|
1438
1432
|
if pipeline_mode == "full":
|
|
1439
1433
|
mission += (
|
|
@@ -1931,12 +1925,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1931
1925
|
dev_url = f"http://localhost:{dev_port}"
|
|
1932
1926
|
|
|
1933
1927
|
max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
|
|
1934
|
-
if max_log_size == 0:
|
|
1935
|
-
poll_interval = DEFAULT_LOG_MONITOR_POLL_INTERVAL
|
|
1936
|
-
else:
|
|
1937
|
-
poll_interval = _parse_positive_int_env(
|
|
1938
|
-
"LOG_MONITOR_POLL_INTERVAL", DEFAULT_LOG_MONITOR_POLL_INTERVAL,
|
|
1939
|
-
)
|
|
1940
1928
|
|
|
1941
1929
|
replacements = {
|
|
1942
1930
|
"{{LOG_SIZE_AWARENESS}}": load_log_size_section(script_dir),
|
|
@@ -1986,22 +1974,10 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1986
1974
|
"{{DEV_URL}}": dev_url,
|
|
1987
1975
|
"{{MAX_LOG_SIZE}}": str(max_log_size),
|
|
1988
1976
|
"{{MAX_LOG_SIZE_HUMAN}}": _format_bytes(max_log_size),
|
|
1989
|
-
"{{LOG_MONITOR_POLL_INTERVAL}}": str(poll_interval),
|
|
1990
|
-
"{{LOG_MONITOR_STATUS_FILE}}": os.path.join(
|
|
1991
|
-
".prizmkit", "state", "features", args.feature_id,
|
|
1992
|
-
"sessions", args.session_id, "logs", "log-monitor-status.txt",
|
|
1993
|
-
),
|
|
1994
|
-
"{{LOG_MONITOR_BASELINE_FILE}}": os.path.join(
|
|
1995
|
-
".prizmkit", "state", "features", args.feature_id,
|
|
1996
|
-
"sessions", args.session_id, "logs", "log-monitor-baseline.txt",
|
|
1997
|
-
),
|
|
1998
1977
|
"{{SESSION_LOG_PATH}}": os.path.join(
|
|
1999
1978
|
".prizmkit", "state", "features", args.feature_id,
|
|
2000
1979
|
"sessions", args.session_id, "logs", "session.log",
|
|
2001
1980
|
),
|
|
2002
|
-
"{{LOG_MONITOR_SCRIPT}}": os.path.join(
|
|
2003
|
-
".prizmkit", "dev-pipeline", "scripts", "monitor-log.sh",
|
|
2004
|
-
),
|
|
2005
1981
|
}
|
|
2006
1982
|
|
|
2007
1983
|
return replacements, effective_resume, browser_enabled, browser_tool
|
|
@@ -2088,6 +2064,9 @@ def main():
|
|
|
2088
2064
|
agent_prompt_replacements = load_agent_prompts(templates_dir)
|
|
2089
2065
|
replacements.update(agent_prompt_replacements)
|
|
2090
2066
|
|
|
2067
|
+
if is_continuation(args):
|
|
2068
|
+
replacements["{{LOG_SIZE_AWARENESS}}"] = ""
|
|
2069
|
+
|
|
2091
2070
|
# Extract state needed for assembly
|
|
2092
2071
|
pipeline_mode = replacements.get("{{PIPELINE_MODE}}", "lite")
|
|
2093
2072
|
init_done = replacements.get("{{INIT_DONE}}", "false") == "true"
|
|
@@ -2107,6 +2086,11 @@ def main():
|
|
|
2107
2086
|
retry_count=int(args.retry_count),
|
|
2108
2087
|
browser_tool=browser_tool,
|
|
2109
2088
|
)
|
|
2089
|
+
if is_continuation(args):
|
|
2090
|
+
sections = [
|
|
2091
|
+
(name, content) for name, content in sections
|
|
2092
|
+
if name != "log-size-awareness"
|
|
2093
|
+
]
|
|
2110
2094
|
rendered = render_from_sections(sections, replacements)
|
|
2111
2095
|
except FileNotFoundError as exc:
|
|
2112
2096
|
LOGGER.warning(
|
|
@@ -2168,6 +2152,7 @@ def main():
|
|
|
2168
2152
|
)
|
|
2169
2153
|
feature_slug = replacements.get("{{FEATURE_SLUG}}", "")
|
|
2170
2154
|
checkpoint_path = ""
|
|
2155
|
+
checkpoint = {}
|
|
2171
2156
|
|
|
2172
2157
|
if use_sections and feature_slug:
|
|
2173
2158
|
checkpoint = generate_checkpoint_definition(
|
|
@@ -2189,7 +2174,7 @@ def main():
|
|
|
2189
2174
|
)
|
|
2190
2175
|
|
|
2191
2176
|
# On resume, merge existing completed state (with artifact validation)
|
|
2192
|
-
if is_resume and os.path.exists(checkpoint_path):
|
|
2177
|
+
if (is_resume or is_continuation(args)) and os.path.exists(checkpoint_path):
|
|
2193
2178
|
try:
|
|
2194
2179
|
with open(checkpoint_path, "r", encoding="utf-8") as f:
|
|
2195
2180
|
existing = json.load(f)
|
|
@@ -2208,6 +2193,15 @@ def main():
|
|
|
2208
2193
|
json.dump(checkpoint, f, indent=2, ensure_ascii=False)
|
|
2209
2194
|
LOGGER.info("Wrote checkpoint to %s", checkpoint_path)
|
|
2210
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
|
+
|
|
2211
2205
|
# ── Success JSON ───────────────────────────────────────────────────
|
|
2212
2206
|
feature_model = feature.get("model", "")
|
|
2213
2207
|
mode_agent_counts = {"lite": 1, "standard": 3, "full": 3}
|