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.
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/lib/common.sh +244 -25
- package/bundled/dev-pipeline/reset-bug.sh +30 -0
- package/bundled/dev-pipeline/reset-feature.sh +30 -0
- package/bundled/dev-pipeline/reset-refactor.sh +30 -0
- package/bundled/dev-pipeline/run-bugfix.sh +128 -13
- package/bundled/dev-pipeline/run-feature.sh +130 -14
- package/bundled/dev-pipeline/run-refactor.sh +128 -13
- 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/generate-bootstrap-prompt.py +27 -30
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +15 -20
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +15 -20
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +146 -16
- package/bundled/dev-pipeline/scripts/update-bug-status.py +214 -6
- package/bundled/dev-pipeline/scripts/update-feature-status.py +237 -6
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +214 -6
- 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 +510 -0
- 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 +93 -9
- 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/generate-bootstrap-prompt.py +27 -30
- package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +16 -20
- package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +16 -20
- package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +146 -16
- package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +214 -6
- package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +237 -6
- package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +214 -6
- 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/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
|
@@ -22,6 +22,77 @@ ID_FIELDS = ["feature_id", "bug_id", "refactor_id"]
|
|
|
22
22
|
|
|
23
23
|
LOGGER = setup_logging("check-session-status")
|
|
24
24
|
|
|
25
|
+
CONTEXT_OVERFLOW_CODE = "context_overflow"
|
|
26
|
+
CONTEXT_CODE_VALUES = {
|
|
27
|
+
"context_overflow",
|
|
28
|
+
"context_too_large",
|
|
29
|
+
"context_length_exceeded",
|
|
30
|
+
"model_context_window_exceeded",
|
|
31
|
+
}
|
|
32
|
+
CONTEXT_TEXT_PATTERNS = (
|
|
33
|
+
"input exceeds the context window",
|
|
34
|
+
"context window of this model",
|
|
35
|
+
"context window exceeded",
|
|
36
|
+
"maximum context length exceeded",
|
|
37
|
+
"prompt is too long",
|
|
38
|
+
"too many tokens",
|
|
39
|
+
"input token count exceeds",
|
|
40
|
+
)
|
|
41
|
+
REQUEST_TOO_LARGE_TEXT = "request too large"
|
|
42
|
+
REQUEST_TOO_LARGE_SEMANTICS = ("context", "token", "tokens", "prompt", "input", "model")
|
|
43
|
+
ERROR_LIKE_STATUSES = {"failed", "crashed", "timed_out", "context_overflow"}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _flatten_values(value):
|
|
47
|
+
if isinstance(value, dict):
|
|
48
|
+
values = []
|
|
49
|
+
for child in value.values():
|
|
50
|
+
values.extend(_flatten_values(child))
|
|
51
|
+
return values
|
|
52
|
+
if isinstance(value, list):
|
|
53
|
+
values = []
|
|
54
|
+
for child in value:
|
|
55
|
+
values.extend(_flatten_values(child))
|
|
56
|
+
return values
|
|
57
|
+
if value is None:
|
|
58
|
+
return []
|
|
59
|
+
return [str(value)]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _is_error_like_status(data):
|
|
63
|
+
status = str(data.get("status", "")).lower()
|
|
64
|
+
if status in ERROR_LIKE_STATUSES:
|
|
65
|
+
return True
|
|
66
|
+
if data.get("last_result_is_error") is True or data.get("is_error") is True:
|
|
67
|
+
return True
|
|
68
|
+
if data.get("api_error_status") not in (None, ""):
|
|
69
|
+
return True
|
|
70
|
+
errors = data.get("errors")
|
|
71
|
+
return isinstance(errors, list) and len(errors) > 0
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def has_context_overflow_signal(data):
|
|
75
|
+
"""Return true when session-status data carries a context overflow signal.
|
|
76
|
+
|
|
77
|
+
Success remains success: direct status handling happens before this helper.
|
|
78
|
+
Exact machine codes are accepted only from structured code fields. Natural
|
|
79
|
+
language context/token phrases require a failed/error-like status or an
|
|
80
|
+
explicit error field to avoid prompt/documentation false positives.
|
|
81
|
+
"""
|
|
82
|
+
if not isinstance(data, dict):
|
|
83
|
+
return False
|
|
84
|
+
for field in ("fatal_error_code", "api_error_code", "error_code", "stop_reason"):
|
|
85
|
+
value = data.get(field)
|
|
86
|
+
if isinstance(value, str) and value.lower() in CONTEXT_CODE_VALUES:
|
|
87
|
+
return True
|
|
88
|
+
if not _is_error_like_status(data):
|
|
89
|
+
return False
|
|
90
|
+
text = " ".join(_flatten_values(data)).lower()
|
|
91
|
+
if any(pattern in text for pattern in CONTEXT_TEXT_PATTERNS):
|
|
92
|
+
return True
|
|
93
|
+
return REQUEST_TOO_LARGE_TEXT in text and any(
|
|
94
|
+
semantic in text for semantic in REQUEST_TOO_LARGE_SEMANTICS
|
|
95
|
+
)
|
|
25
96
|
|
|
26
97
|
def parse_args():
|
|
27
98
|
parser = argparse.ArgumentParser(
|
|
@@ -79,12 +150,14 @@ def determine_status(data):
|
|
|
79
150
|
"""Determine the single-line status string from the parsed data.
|
|
80
151
|
|
|
81
152
|
Returns one of: success, partial_resumable, partial_not_resumable,
|
|
82
|
-
failed, commit_missing, docs_missing, merge_conflict.
|
|
153
|
+
failed, commit_missing, docs_missing, merge_conflict, context_overflow.
|
|
83
154
|
"""
|
|
84
155
|
status = data.get("status", "")
|
|
85
156
|
|
|
86
157
|
if status == "success":
|
|
87
158
|
return "success"
|
|
159
|
+
if status == "context_overflow" or has_context_overflow_signal(data):
|
|
160
|
+
return "context_overflow"
|
|
88
161
|
elif status == "partial":
|
|
89
162
|
can_resume = data.get("can_resume", False)
|
|
90
163
|
if can_resume:
|
|
@@ -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
|
|
|
@@ -115,6 +115,8 @@ def parse_args():
|
|
|
115
115
|
help="Do not write workflow-checkpoint.json (used by pipeline dry-run)",
|
|
116
116
|
)
|
|
117
117
|
|
|
118
|
+
add_continuation_args(parser, "feature")
|
|
119
|
+
|
|
118
120
|
return parser.parse_args()
|
|
119
121
|
|
|
120
122
|
|
|
@@ -1357,8 +1359,7 @@ def _tier_header(pipeline_mode):
|
|
|
1357
1359
|
"lite": (
|
|
1358
1360
|
"# Dev-Pipeline Session Bootstrap — Tier 1 (Single Agent)\n",
|
|
1359
1361
|
"**Tier 1 — Single Agent**: You handle everything directly. "
|
|
1360
|
-
"No normal work subagents
|
|
1361
|
-
"allowed persistent background subagent. No TeamCreate.\n",
|
|
1362
|
+
"No normal work subagents. No TeamCreate.\n",
|
|
1362
1363
|
),
|
|
1363
1364
|
"standard": (
|
|
1364
1365
|
"# Dev-Pipeline Session Bootstrap — Tier 2 (Dual Agent)\n",
|
|
@@ -1396,8 +1397,7 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
|
|
|
1396
1397
|
if pipeline_mode == "lite":
|
|
1397
1398
|
specific = [
|
|
1398
1399
|
"- Tier 1: you handle everything directly — no normal work "
|
|
1399
|
-
"subagents needed
|
|
1400
|
-
"persistent background subagent",
|
|
1400
|
+
"subagents needed",
|
|
1401
1401
|
]
|
|
1402
1402
|
elif pipeline_mode == "standard":
|
|
1403
1403
|
specific = [
|
|
@@ -1410,8 +1410,7 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
|
|
|
1410
1410
|
"- Gate checks enforce Implementation Log and Review Notes are "
|
|
1411
1411
|
"written before proceeding",
|
|
1412
1412
|
"- Do NOT use `run_in_background=true` when spawning normal work "
|
|
1413
|
-
"subagents
|
|
1414
|
-
"background subagent",
|
|
1413
|
+
"subagents",
|
|
1415
1414
|
"- On timeout: check snapshot + git diff HEAD → model:lite → "
|
|
1416
1415
|
"remaining steps only → max 2 retries per phase → "
|
|
1417
1416
|
"orchestrator fallback",
|
|
@@ -1426,8 +1425,7 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
|
|
|
1426
1425
|
"- Gate checks enforce Implementation Log and Review Notes are "
|
|
1427
1426
|
"written before proceeding",
|
|
1428
1427
|
"- Do NOT use `run_in_background=true` when spawning normal work "
|
|
1429
|
-
"agents
|
|
1430
|
-
"background subagent",
|
|
1428
|
+
"agents",
|
|
1431
1429
|
"- On timeout: check snapshot → model:lite → remaining steps "
|
|
1432
1430
|
"only → max 2 retries → orchestrator fallback",
|
|
1433
1431
|
]
|
|
@@ -1467,8 +1465,7 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
|
|
|
1467
1465
|
if pipeline_mode != "lite":
|
|
1468
1466
|
mission += (
|
|
1469
1467
|
" When you spawn normal work subagents, wait for each to finish "
|
|
1470
|
-
"(run_in_background=false).
|
|
1471
|
-
"allowed persistent background subagent."
|
|
1468
|
+
"(run_in_background=false)."
|
|
1472
1469
|
)
|
|
1473
1470
|
if pipeline_mode == "full":
|
|
1474
1471
|
mission += (
|
|
@@ -1969,12 +1966,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1969
1966
|
dev_url = f"http://localhost:{dev_port}"
|
|
1970
1967
|
|
|
1971
1968
|
max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
|
|
1972
|
-
if max_log_size == 0:
|
|
1973
|
-
poll_interval = DEFAULT_LOG_MONITOR_POLL_INTERVAL
|
|
1974
|
-
else:
|
|
1975
|
-
poll_interval = _parse_positive_int_env(
|
|
1976
|
-
"LOG_MONITOR_POLL_INTERVAL", DEFAULT_LOG_MONITOR_POLL_INTERVAL,
|
|
1977
|
-
)
|
|
1978
1969
|
|
|
1979
1970
|
replacements = {
|
|
1980
1971
|
"{{LOG_SIZE_AWARENESS}}": load_log_size_section(script_dir),
|
|
@@ -2025,22 +2016,10 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
2025
2016
|
"{{DEV_URL}}": dev_url,
|
|
2026
2017
|
"{{MAX_LOG_SIZE}}": str(max_log_size),
|
|
2027
2018
|
"{{MAX_LOG_SIZE_HUMAN}}": _format_bytes(max_log_size),
|
|
2028
|
-
"{{LOG_MONITOR_POLL_INTERVAL}}": str(poll_interval),
|
|
2029
|
-
"{{LOG_MONITOR_STATUS_FILE}}": os.path.join(
|
|
2030
|
-
".prizmkit", "state", "features", args.feature_id,
|
|
2031
|
-
"sessions", args.session_id, "logs", "log-monitor-status.txt",
|
|
2032
|
-
),
|
|
2033
|
-
"{{LOG_MONITOR_BASELINE_FILE}}": os.path.join(
|
|
2034
|
-
".prizmkit", "state", "features", args.feature_id,
|
|
2035
|
-
"sessions", args.session_id, "logs", "log-monitor-baseline.txt",
|
|
2036
|
-
),
|
|
2037
2019
|
"{{SESSION_LOG_PATH}}": os.path.join(
|
|
2038
2020
|
".prizmkit", "state", "features", args.feature_id,
|
|
2039
2021
|
"sessions", args.session_id, "logs", "session.log",
|
|
2040
2022
|
),
|
|
2041
|
-
"{{LOG_MONITOR_SCRIPT}}": os.path.join(
|
|
2042
|
-
".prizmkit", "dev-pipeline", "scripts", "monitor-log.ps1",
|
|
2043
|
-
),
|
|
2044
2023
|
}
|
|
2045
2024
|
|
|
2046
2025
|
return replacements, effective_resume, browser_enabled, browser_tool
|
|
@@ -2127,6 +2106,9 @@ def main():
|
|
|
2127
2106
|
agent_prompt_replacements = load_agent_prompts(templates_dir)
|
|
2128
2107
|
replacements.update(agent_prompt_replacements)
|
|
2129
2108
|
|
|
2109
|
+
if is_continuation(args):
|
|
2110
|
+
replacements["{{LOG_SIZE_AWARENESS}}"] = ""
|
|
2111
|
+
|
|
2130
2112
|
# Extract state needed for assembly
|
|
2131
2113
|
pipeline_mode = replacements.get("{{PIPELINE_MODE}}", "lite")
|
|
2132
2114
|
init_done = replacements.get("{{INIT_DONE}}", "false") == "true"
|
|
@@ -2146,6 +2128,11 @@ def main():
|
|
|
2146
2128
|
retry_count=int(args.retry_count),
|
|
2147
2129
|
browser_tool=browser_tool,
|
|
2148
2130
|
)
|
|
2131
|
+
if is_continuation(args):
|
|
2132
|
+
sections = [
|
|
2133
|
+
(name, content) for name, content in sections
|
|
2134
|
+
if name != "log-size-awareness"
|
|
2135
|
+
]
|
|
2149
2136
|
rendered = render_from_sections(sections, replacements)
|
|
2150
2137
|
except FileNotFoundError as exc:
|
|
2151
2138
|
LOGGER.warning(
|
|
@@ -2207,6 +2194,7 @@ def main():
|
|
|
2207
2194
|
)
|
|
2208
2195
|
feature_slug = replacements.get("{{FEATURE_SLUG}}", "")
|
|
2209
2196
|
checkpoint_path = ""
|
|
2197
|
+
checkpoint = {}
|
|
2210
2198
|
|
|
2211
2199
|
if not args.no_checkpoint and use_sections and feature_slug:
|
|
2212
2200
|
checkpoint = generate_checkpoint_definition(
|
|
@@ -2228,7 +2216,7 @@ def main():
|
|
|
2228
2216
|
)
|
|
2229
2217
|
|
|
2230
2218
|
# On resume, merge existing completed state (with artifact validation)
|
|
2231
|
-
if is_resume and os.path.exists(checkpoint_path):
|
|
2219
|
+
if (is_resume or is_continuation(args)) and os.path.exists(checkpoint_path):
|
|
2232
2220
|
try:
|
|
2233
2221
|
with open(checkpoint_path, "r", encoding="utf-8") as f:
|
|
2234
2222
|
existing = json.load(f)
|
|
@@ -2247,6 +2235,15 @@ def main():
|
|
|
2247
2235
|
json.dump(checkpoint, f, indent=2, ensure_ascii=False)
|
|
2248
2236
|
LOGGER.info("Wrote checkpoint to %s", checkpoint_path)
|
|
2249
2237
|
|
|
2238
|
+
if is_continuation(args):
|
|
2239
|
+
rendered = append_continuation_handoff(
|
|
2240
|
+
rendered, args, project_root, args.task_type, args.feature_id,
|
|
2241
|
+
feature_slug, checkpoint,
|
|
2242
|
+
)
|
|
2243
|
+
err = write_output(args.output, rendered)
|
|
2244
|
+
if err:
|
|
2245
|
+
emit_failure(err)
|
|
2246
|
+
|
|
2250
2247
|
# ── Success JSON ───────────────────────────────────────────────────
|
|
2251
2248
|
feature_model = feature.get("model", "")
|
|
2252
2249
|
mode_agent_counts = {"lite": 1, "standard": 3, "full": 3}
|