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
|
@@ -43,6 +43,8 @@ SESSION_STATUS_VALUES = [
|
|
|
43
43
|
"crashed",
|
|
44
44
|
"timed_out",
|
|
45
45
|
"infra_error",
|
|
46
|
+
"context_overflow",
|
|
47
|
+
"stalled_context_continuation",
|
|
46
48
|
"commit_missing",
|
|
47
49
|
"docs_missing",
|
|
48
50
|
"merge_conflict",
|
|
@@ -90,6 +92,12 @@ def parse_args():
|
|
|
90
92
|
parser.add_argument("--max-retries", type=int, default=3, help="Maximum code retry count (default: 3)")
|
|
91
93
|
parser.add_argument("--max-infra-retries", type=int, default=3, help="Maximum infrastructure retry count (default: 3)")
|
|
92
94
|
parser.add_argument("--project-root", default=None, help="Project root directory. Required for 'clean' action.")
|
|
95
|
+
parser.add_argument("--active-dev-branch", default=None, help="Development branch preserved for continuation after context overflow.")
|
|
96
|
+
parser.add_argument("--base-branch", default=None, help="Original/base branch for continuation branch reuse.")
|
|
97
|
+
parser.add_argument("--last-fatal-error-code", default=None, help="Fatal runtime error code associated with continuation.")
|
|
98
|
+
parser.add_argument("--continuation-summary-path", default=None, help="Optional continuation summary artifact path.")
|
|
99
|
+
parser.add_argument("--no-progress-count", type=int, default=None, help="Consecutive context-overflow sessions without objective progress.")
|
|
100
|
+
parser.add_argument("--progress-fingerprint", default=None, help="JSON progress fingerprint captured after the session.")
|
|
93
101
|
return parser.parse_args()
|
|
94
102
|
|
|
95
103
|
|
|
@@ -122,7 +130,7 @@ def load_refactor_status(state_dir, refactor_id):
|
|
|
122
130
|
The 'status' field is NOT included — status lives exclusively
|
|
123
131
|
in refactor-list.json.
|
|
124
132
|
"""
|
|
125
|
-
status_path = os.path.join(state_dir,
|
|
133
|
+
status_path = os.path.join(state_dir, refactor_id, "status.json")
|
|
126
134
|
if not os.path.isfile(status_path):
|
|
127
135
|
return _default_status(refactor_id)
|
|
128
136
|
data, err = load_json_file(status_path)
|
|
@@ -137,7 +145,7 @@ def save_refactor_status(state_dir, refactor_id, status_data):
|
|
|
137
145
|
"""Write the status.json for a refactor (runtime fields only)."""
|
|
138
146
|
# Defensively strip status — it belongs in refactor-list.json
|
|
139
147
|
status_data.pop("status", None)
|
|
140
|
-
status_path = os.path.join(state_dir,
|
|
148
|
+
status_path = os.path.join(state_dir, refactor_id, "status.json")
|
|
141
149
|
return write_json_file(status_path, status_data)
|
|
142
150
|
|
|
143
151
|
|
|
@@ -168,6 +176,128 @@ def update_refactor_in_list(refactor_list_path, refactor_id, new_status):
|
|
|
168
176
|
return write_json_file(refactor_list_path, data)
|
|
169
177
|
|
|
170
178
|
|
|
179
|
+
CONTINUATION_PENDING_FIELDS = [
|
|
180
|
+
"active_dev_branch",
|
|
181
|
+
"base_branch",
|
|
182
|
+
"continuation_pending",
|
|
183
|
+
"continuation_reason",
|
|
184
|
+
"last_context_overflow_session_id",
|
|
185
|
+
"previous_session_id",
|
|
186
|
+
"last_fatal_error_code",
|
|
187
|
+
"continuation_summary_path",
|
|
188
|
+
"no_progress_count",
|
|
189
|
+
"last_progress_fingerprint",
|
|
190
|
+
"needs_attention",
|
|
191
|
+
"stalled_context_continuation",
|
|
192
|
+
"stalled_reason",
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
CONTINUATION_COUNTER_FIELDS = [
|
|
196
|
+
"context_overflow_count",
|
|
197
|
+
"continuation_count",
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def has_pending_continuation_metadata(status_data):
|
|
202
|
+
"""Return True when runtime state still has pending continuation metadata."""
|
|
203
|
+
return any(key in status_data for key in CONTINUATION_PENDING_FIELDS)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def clear_pending_continuation_metadata(status_data):
|
|
207
|
+
"""Clear branch/pending continuation fields while preserving observability counters."""
|
|
208
|
+
for key in CONTINUATION_PENDING_FIELDS:
|
|
209
|
+
status_data.pop(key, None)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def reset_continuation_metadata(status_data):
|
|
213
|
+
"""Clear all continuation runtime metadata, including manual-reset counters."""
|
|
214
|
+
clear_pending_continuation_metadata(status_data)
|
|
215
|
+
for key in CONTINUATION_COUNTER_FIELDS:
|
|
216
|
+
status_data.pop(key, None)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
STALL_REASON = "context_overflow_without_git_checkpoint_or_artifact_progress"
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _load_progress_fingerprint(raw):
|
|
223
|
+
if not raw:
|
|
224
|
+
return None
|
|
225
|
+
try:
|
|
226
|
+
return json.loads(raw)
|
|
227
|
+
except (TypeError, ValueError):
|
|
228
|
+
return {"unparsed": str(raw)}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _set_continuation_common(status_data, args, session_id):
|
|
232
|
+
if args.active_dev_branch:
|
|
233
|
+
status_data["active_dev_branch"] = args.active_dev_branch
|
|
234
|
+
if args.base_branch:
|
|
235
|
+
status_data["base_branch"] = args.base_branch
|
|
236
|
+
status_data["last_context_overflow_session_id"] = session_id
|
|
237
|
+
status_data["previous_session_id"] = session_id
|
|
238
|
+
status_data["last_fatal_error_code"] = args.last_fatal_error_code or "context_overflow"
|
|
239
|
+
if args.continuation_summary_path:
|
|
240
|
+
status_data["continuation_summary_path"] = args.continuation_summary_path
|
|
241
|
+
else:
|
|
242
|
+
status_data.pop("continuation_summary_path", None)
|
|
243
|
+
if args.no_progress_count is not None:
|
|
244
|
+
status_data["no_progress_count"] = max(0, args.no_progress_count)
|
|
245
|
+
fingerprint = _load_progress_fingerprint(args.progress_fingerprint)
|
|
246
|
+
if fingerprint is not None:
|
|
247
|
+
status_data["last_progress_fingerprint"] = fingerprint
|
|
248
|
+
status_data["last_infra_error_session_id"] = None
|
|
249
|
+
status_data["resume_from_phase"] = None
|
|
250
|
+
if session_id:
|
|
251
|
+
status_data["last_session_id"] = session_id
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def set_context_overflow_continuation_metadata(status_data, args, session_id):
|
|
255
|
+
"""Record continuation metadata for a context-overflow outcome in status.json."""
|
|
256
|
+
status_data["context_overflow_count"] = status_data.get("context_overflow_count", 0) + 1
|
|
257
|
+
status_data["continuation_count"] = status_data.get("continuation_count", 0) + 1
|
|
258
|
+
status_data["continuation_pending"] = True
|
|
259
|
+
status_data["continuation_reason"] = "context_overflow"
|
|
260
|
+
status_data.pop("needs_attention", None)
|
|
261
|
+
status_data.pop("stalled_context_continuation", None)
|
|
262
|
+
status_data.pop("stalled_reason", None)
|
|
263
|
+
_set_continuation_common(status_data, args, session_id)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def set_stalled_context_continuation_metadata(status_data, args, session_id):
|
|
267
|
+
"""Record no-progress context-overflow stall without consuming retry budgets."""
|
|
268
|
+
status_data["context_overflow_count"] = status_data.get("context_overflow_count", 0) + 1
|
|
269
|
+
status_data["continuation_count"] = status_data.get("continuation_count", 0) + 1
|
|
270
|
+
_set_continuation_common(status_data, args, session_id)
|
|
271
|
+
status_data["continuation_pending"] = False
|
|
272
|
+
status_data["continuation_reason"] = STALL_REASON
|
|
273
|
+
status_data["needs_attention"] = True
|
|
274
|
+
status_data["stalled_context_continuation"] = True
|
|
275
|
+
status_data["stalled_reason"] = STALL_REASON
|
|
276
|
+
if args.no_progress_count is None or args.no_progress_count < 2:
|
|
277
|
+
status_data["no_progress_count"] = 2
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def continuation_metadata_summary(status_data):
|
|
281
|
+
"""Return additive continuation fields for CLI JSON outputs."""
|
|
282
|
+
return {
|
|
283
|
+
"active_dev_branch": status_data.get("active_dev_branch"),
|
|
284
|
+
"base_branch": status_data.get("base_branch"),
|
|
285
|
+
"continuation_pending": status_data.get("continuation_pending", False),
|
|
286
|
+
"continuation_reason": status_data.get("continuation_reason"),
|
|
287
|
+
"last_context_overflow_session_id": status_data.get("last_context_overflow_session_id"),
|
|
288
|
+
"last_fatal_error_code": status_data.get("last_fatal_error_code"),
|
|
289
|
+
"previous_session_id": status_data.get("previous_session_id"),
|
|
290
|
+
"context_overflow_count": status_data.get("context_overflow_count", 0),
|
|
291
|
+
"continuation_count": status_data.get("continuation_count", 0),
|
|
292
|
+
"no_progress_count": status_data.get("no_progress_count", 0),
|
|
293
|
+
"last_progress_fingerprint": status_data.get("last_progress_fingerprint"),
|
|
294
|
+
"needs_attention": status_data.get("needs_attention", False),
|
|
295
|
+
"stalled_context_continuation": status_data.get("stalled_context_continuation", False),
|
|
296
|
+
"stalled_reason": status_data.get("stalled_reason"),
|
|
297
|
+
"continuation_summary_path": status_data.get("continuation_summary_path"),
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
171
301
|
# ---------------------------------------------------------------------------
|
|
172
302
|
# Action: get_next
|
|
173
303
|
# ---------------------------------------------------------------------------
|
|
@@ -275,6 +405,7 @@ def action_get_next(refactor_list_data, state_dir):
|
|
|
275
405
|
"infra_error_count": chosen_status_data.get("infra_error_count", 0),
|
|
276
406
|
"resume_from_phase": chosen_status_data.get("resume_from_phase", None),
|
|
277
407
|
}
|
|
408
|
+
result.update(continuation_metadata_summary(chosen_status_data))
|
|
278
409
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
279
410
|
|
|
280
411
|
|
|
@@ -298,12 +429,15 @@ def action_update(args, refactor_list_path, state_dir):
|
|
|
298
429
|
|
|
299
430
|
rs = load_refactor_status(state_dir, refactor_id)
|
|
300
431
|
|
|
432
|
+
current_list_status = get_refactor_status_from_list(refactor_list_path, refactor_id)
|
|
433
|
+
|
|
301
434
|
# Track what status we write to refactor-list.json
|
|
302
|
-
new_status =
|
|
435
|
+
new_status = current_list_status
|
|
303
436
|
|
|
304
437
|
if session_status == "success":
|
|
305
438
|
rs["infra_error_count"] = 0
|
|
306
439
|
rs["last_infra_error_session_id"] = None
|
|
440
|
+
clear_pending_continuation_metadata(rs)
|
|
307
441
|
new_status = "completed"
|
|
308
442
|
rs["resume_from_phase"] = None
|
|
309
443
|
err = update_refactor_in_list(refactor_list_path, refactor_id, "completed")
|
|
@@ -335,7 +469,7 @@ def action_update(args, refactor_list_path, state_dir):
|
|
|
335
469
|
rs["infra_error_count"] = infra_error_count
|
|
336
470
|
rs["last_infra_error_session_id"] = session_id
|
|
337
471
|
rs["max_infra_retries"] = max_infra_retries
|
|
338
|
-
rs["degraded_reason"] =
|
|
472
|
+
rs["degraded_reason"] = session_status
|
|
339
473
|
if session_id:
|
|
340
474
|
rs["last_session_id"] = session_id
|
|
341
475
|
rs["resume_from_phase"] = None
|
|
@@ -347,6 +481,23 @@ def action_update(args, refactor_list_path, state_dir):
|
|
|
347
481
|
else:
|
|
348
482
|
new_status = "pending"
|
|
349
483
|
|
|
484
|
+
err = update_refactor_in_list(refactor_list_path, refactor_id, new_status)
|
|
485
|
+
if err:
|
|
486
|
+
error_out("Failed to update .prizmkit/plans/refactor-list.json: {}".format(err))
|
|
487
|
+
return
|
|
488
|
+
elif session_status == "context_overflow":
|
|
489
|
+
# Context overflow is continuation state, not code or infra retry state.
|
|
490
|
+
set_context_overflow_continuation_metadata(rs, args, session_id)
|
|
491
|
+
new_status = "in_progress"
|
|
492
|
+
if current_list_status != "in_progress":
|
|
493
|
+
err = update_refactor_in_list(refactor_list_path, refactor_id, "in_progress")
|
|
494
|
+
if err:
|
|
495
|
+
error_out("Failed to update .prizmkit/plans/refactor-list.json: {}".format(err))
|
|
496
|
+
return
|
|
497
|
+
elif session_status == "stalled_context_continuation":
|
|
498
|
+
# No-progress context-overflow stall pauses automation without consuming retry budgets.
|
|
499
|
+
set_stalled_context_continuation_metadata(rs, args, session_id)
|
|
500
|
+
new_status = "failed"
|
|
350
501
|
err = update_refactor_in_list(refactor_list_path, refactor_id, new_status)
|
|
351
502
|
if err:
|
|
352
503
|
error_out("Failed to update .prizmkit/plans/refactor-list.json: {}".format(err))
|
|
@@ -415,6 +566,14 @@ def action_update(args, refactor_list_path, state_dir):
|
|
|
415
566
|
summary["restart_policy"] = "infra_retry"
|
|
416
567
|
summary["infra_error_count"] = rs.get("infra_error_count", 0)
|
|
417
568
|
summary["artifacts_preserved"] = True
|
|
569
|
+
elif session_status == "context_overflow":
|
|
570
|
+
summary["restart_policy"] = "context_overflow_continuation"
|
|
571
|
+
summary.update(continuation_metadata_summary(rs))
|
|
572
|
+
summary["artifacts_preserved"] = True
|
|
573
|
+
elif session_status == "stalled_context_continuation":
|
|
574
|
+
summary["restart_policy"] = "needs_attention"
|
|
575
|
+
summary.update(continuation_metadata_summary(rs))
|
|
576
|
+
summary["artifacts_preserved"] = True
|
|
418
577
|
elif session_status != "success":
|
|
419
578
|
summary["restart_policy"] = "full_restart"
|
|
420
579
|
summary["cleanup_performed"] = cleaned
|
|
@@ -442,7 +601,7 @@ def cleanup_refactor_artifacts(state_dir, refactor_id, project_root=None):
|
|
|
442
601
|
cleaned = []
|
|
443
602
|
|
|
444
603
|
# 1) Remove all session history
|
|
445
|
-
sessions_dir = os.path.join(state_dir,
|
|
604
|
+
sessions_dir = os.path.join(state_dir, refactor_id, "sessions")
|
|
446
605
|
sessions_deleted = 0
|
|
447
606
|
if os.path.isdir(sessions_dir):
|
|
448
607
|
for entry in os.listdir(sessions_dir):
|
|
@@ -453,7 +612,7 @@ def cleanup_refactor_artifacts(state_dir, refactor_id, project_root=None):
|
|
|
453
612
|
cleaned.append("Deleted {} session(s) from {}".format(sessions_deleted, sessions_dir))
|
|
454
613
|
|
|
455
614
|
# 2) Remove transient files under refactor dir (keep status.json)
|
|
456
|
-
refactor_dir = os.path.join(state_dir,
|
|
615
|
+
refactor_dir = os.path.join(state_dir, refactor_id)
|
|
457
616
|
if os.path.isdir(refactor_dir):
|
|
458
617
|
for entry in os.listdir(refactor_dir):
|
|
459
618
|
if entry == "status.json" or entry == "sessions":
|
|
@@ -481,9 +640,47 @@ def cleanup_refactor_artifacts(state_dir, refactor_id, project_root=None):
|
|
|
481
640
|
return cleaned
|
|
482
641
|
|
|
483
642
|
|
|
643
|
+
def _resolve_project_file(project_root, path):
|
|
644
|
+
if not path:
|
|
645
|
+
return None
|
|
646
|
+
if os.path.isabs(path):
|
|
647
|
+
target = os.path.abspath(path)
|
|
648
|
+
else:
|
|
649
|
+
target = os.path.abspath(os.path.join(project_root, path))
|
|
650
|
+
root = os.path.abspath(project_root)
|
|
651
|
+
try:
|
|
652
|
+
rel = os.path.relpath(target, root)
|
|
653
|
+
except ValueError:
|
|
654
|
+
return None
|
|
655
|
+
if rel == os.pardir or rel.startswith(os.pardir + os.sep):
|
|
656
|
+
return None
|
|
657
|
+
return target
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def cleanup_continuation_artifacts(project_root, status_data, candidate_paths=None):
|
|
661
|
+
"""Delete durable continuation handoff files for a manual clean reset."""
|
|
662
|
+
candidates = []
|
|
663
|
+
if isinstance(status_data, dict):
|
|
664
|
+
candidates.append(status_data.get("continuation_summary_path"))
|
|
665
|
+
candidates.extend(candidate_paths or [])
|
|
666
|
+
|
|
667
|
+
cleaned = []
|
|
668
|
+
seen = set()
|
|
669
|
+
for candidate in candidates:
|
|
670
|
+
target = _resolve_project_file(project_root, candidate)
|
|
671
|
+
if not target or target in seen:
|
|
672
|
+
continue
|
|
673
|
+
seen.add(target)
|
|
674
|
+
if os.path.basename(target) != "continuation-summary.md":
|
|
675
|
+
continue
|
|
676
|
+
if os.path.isfile(target):
|
|
677
|
+
os.remove(target)
|
|
678
|
+
cleaned.append("Deleted continuation artifact {}".format(target))
|
|
679
|
+
return cleaned
|
|
680
|
+
|
|
681
|
+
|
|
484
682
|
def load_session_status(state_dir, refactor_id, session_id):
|
|
485
|
-
session_status_path = os.path.join(
|
|
486
|
-
state_dir, "refactors", refactor_id, "sessions",
|
|
683
|
+
session_status_path = os.path.join(state_dir, refactor_id, "sessions",
|
|
487
684
|
session_id, "session-status.json"
|
|
488
685
|
)
|
|
489
686
|
data, err = load_json_file(session_status_path)
|
|
@@ -757,6 +954,7 @@ def action_reset(args, refactor_list_path, state_dir):
|
|
|
757
954
|
rs["sessions"] = []
|
|
758
955
|
rs["last_session_id"] = None
|
|
759
956
|
rs["resume_from_phase"] = None
|
|
957
|
+
reset_continuation_metadata(rs)
|
|
760
958
|
rs["updated_at"] = now_iso()
|
|
761
959
|
|
|
762
960
|
err = save_refactor_status(state_dir, refactor_id, rs)
|
|
@@ -797,7 +995,7 @@ def action_clean(args, refactor_list_path, state_dir):
|
|
|
797
995
|
cleaned = []
|
|
798
996
|
|
|
799
997
|
# 1. Delete session history
|
|
800
|
-
sessions_dir = os.path.join(state_dir,
|
|
998
|
+
sessions_dir = os.path.join(state_dir, refactor_id, "sessions")
|
|
801
999
|
sessions_deleted = 0
|
|
802
1000
|
if os.path.isdir(sessions_dir):
|
|
803
1001
|
for entry in os.listdir(sessions_dir):
|
|
@@ -824,12 +1022,19 @@ def action_clean(args, refactor_list_path, state_dir):
|
|
|
824
1022
|
old_status = get_refactor_status_from_list(refactor_list_path, refactor_id)
|
|
825
1023
|
old_retry = rs.get("retry_count", 0)
|
|
826
1024
|
|
|
1025
|
+
continuation_candidates = [
|
|
1026
|
+
os.path.join(".prizmkit", "refactor", refactor_id, "continuation-summary.md"),
|
|
1027
|
+
os.path.join(".prizmkit", "specs", refactor_id, "continuation-summary.md"),
|
|
1028
|
+
]
|
|
1029
|
+
cleaned.extend(cleanup_continuation_artifacts(project_root, rs, continuation_candidates))
|
|
1030
|
+
|
|
827
1031
|
rs["retry_count"] = 0
|
|
828
1032
|
rs["infra_error_count"] = 0
|
|
829
1033
|
rs["last_infra_error_session_id"] = None
|
|
830
1034
|
rs["sessions"] = []
|
|
831
1035
|
rs["last_session_id"] = None
|
|
832
1036
|
rs["resume_from_phase"] = None
|
|
1037
|
+
reset_continuation_metadata(rs)
|
|
833
1038
|
rs["updated_at"] = now_iso()
|
|
834
1039
|
|
|
835
1040
|
err = save_refactor_status(state_dir, refactor_id, rs)
|
|
@@ -905,10 +1110,11 @@ def action_start(args, refactor_list_path, state_dir):
|
|
|
905
1110
|
error_out("Failed to save refactor status: {}".format(err))
|
|
906
1111
|
return
|
|
907
1112
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1113
|
+
if old_status != "in_progress":
|
|
1114
|
+
err = update_refactor_in_list(refactor_list_path, refactor_id, "in_progress")
|
|
1115
|
+
if err:
|
|
1116
|
+
error_out("Failed to update .prizmkit/plans/refactor-list.json: {}".format(err))
|
|
1117
|
+
return
|
|
912
1118
|
|
|
913
1119
|
result = {
|
|
914
1120
|
"action": "start",
|
|
@@ -1060,6 +1266,7 @@ def action_unskip(args, refactor_list_path, state_dir):
|
|
|
1060
1266
|
rs["sessions"] = []
|
|
1061
1267
|
rs["last_session_id"] = None
|
|
1062
1268
|
rs["resume_from_phase"] = None
|
|
1269
|
+
reset_continuation_metadata(rs)
|
|
1063
1270
|
rs["updated_at"] = now_iso()
|
|
1064
1271
|
save_refactor_status(state_dir, rid, rs)
|
|
1065
1272
|
|
|
@@ -35,7 +35,7 @@ Test command:
|
|
|
35
35
|
Known baseline failures:
|
|
36
36
|
`{{BASELINE_FAILURES}}`
|
|
37
37
|
|
|
38
|
-
If plan.md has more than 5 tasks,
|
|
38
|
+
If plan.md has more than 5 tasks, update durable checkpoints/artifacts after every 3 tasks, minimize output, and rely on runner continuation if context overflow occurs. In an interactive Claude Code session operated manually outside the headless pipeline, `/compact` may be used as an optional convenience only.
|
|
39
39
|
|
|
40
40
|
## Required Outputs
|
|
41
41
|
|
|
@@ -12,7 +12,7 @@ You are the **session orchestrator**. Implement Feature {{FEATURE_ID}}: "{{FEATU
|
|
|
12
12
|
|
|
13
13
|
**CRITICAL**: You MUST NOT exit until ALL work is complete and committed.
|
|
14
14
|
|
|
15
|
-
**Tier 1 — Single Agent**: You handle everything directly. No normal work subagents
|
|
15
|
+
**Tier 1 — Single Agent**: You handle everything directly. No normal work subagents. No TeamCreate.
|
|
16
16
|
|
|
17
17
|
### Feature Description
|
|
18
18
|
|
|
@@ -145,7 +145,7 @@ You know this project's tech stack. Identify ALL test commands that apply (e.g.,
|
|
|
145
145
|
- Runs tests using `TEST_CMD` after each task
|
|
146
146
|
- Writes '## Implementation Log' to `context-snapshot.md`
|
|
147
147
|
|
|
148
|
-
**3b-
|
|
148
|
+
**3b-checkpoint.** Context management — if plan.md has more than 5 tasks, update durable checkpoints/artifacts after every 3 tasks, minimize output, and continue. If context overflow occurs in a headless run, the runner starts a fresh continuation session from those checkpoints and artifacts. In an interactive Claude Code session operated manually outside the headless pipeline, `/compact` may be used as an optional convenience only.
|
|
149
149
|
|
|
150
150
|
**3c.** After implement completes, verify:
|
|
151
151
|
1. All tasks in plan.md are `[x]`
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
You are the **session orchestrator**. Implement Feature {{FEATURE_ID}}: "{{FEATURE_TITLE}}".
|
|
12
12
|
|
|
13
|
-
**CRITICAL**: You MUST NOT exit until ALL work is complete and committed. When you spawn normal work subagents, wait for each to finish (run_in_background=false).
|
|
13
|
+
**CRITICAL**: You MUST NOT exit until ALL work is complete and committed. When you spawn normal work subagents, wait for each to finish (run_in_background=false).
|
|
14
14
|
|
|
15
15
|
**Tier 2 — Dual Agent**: You handle context + planning directly. Then spawn Dev and Reviewer subagents. Spawn Dev and Reviewer agents via the Agent tool.
|
|
16
16
|
|
|
@@ -276,7 +276,7 @@ Prompt:
|
|
|
276
276
|
> ⚠️ DO NOT re-read source files already listed in Section 4 File Manifest unless you need implementation detail beyond the interface summary.
|
|
277
277
|
> 1. Read `.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md` for full context.
|
|
278
278
|
> 2. Run `/prizmkit-implement` to execute the tasks in plan.md. Run tests with: `{{TEST_CMD}}`. Known baseline failures (pre-existing, not your fault): `{{BASELINE_FAILURES}}`.
|
|
279
|
-
> 3. If plan.md has more than 5 tasks:
|
|
279
|
+
> 3. If plan.md has more than 5 tasks: update durable checkpoints/artifacts after every 3 tasks, minimize output, and rely on runner continuation if context overflow occurs. In an interactive Claude Code session operated manually outside the headless pipeline, `/compact` may be used as an optional convenience only.
|
|
280
280
|
> 4. After implement completes, verify the '## Implementation Log' section was written to context-snapshot.md.
|
|
281
281
|
> 5. Do NOT execute any git commands (no git add/commit/reset/push).
|
|
282
282
|
> Do NOT exit until all tasks are [x] and the '## Implementation Log' section is written in context-snapshot.md."
|
|
@@ -604,7 +604,7 @@ If you encounter an unrecoverable error, context overflow, or are about to exit
|
|
|
604
604
|
- Build context-snapshot.md FIRST; all subagents read it instead of re-reading source files
|
|
605
605
|
- context-snapshot.md is append-only: orchestrator writes Sections 1-4, Dev appends Implementation Log, Reviewer appends Review Notes
|
|
606
606
|
- Gate checks enforce Implementation Log and Review Notes are written before proceeding
|
|
607
|
-
- Do NOT use `run_in_background=true` when spawning normal work subagents
|
|
607
|
+
- Do NOT use `run_in_background=true` when spawning normal work subagents
|
|
608
608
|
- **NEVER delete, modify, or touch any file under `.prizmkit/state/`** — those are pipeline runtime files managed by the runner
|
|
609
609
|
- NEVER run `rm -rf`, `git clean`, or any destructive filesystem operations
|
|
610
610
|
- `/prizmkit-committer` is mandatory, and must not be replaced with manual git commit commands
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
You are the **session orchestrator**. Implement Feature {{FEATURE_ID}}: "{{FEATURE_TITLE}}".
|
|
12
12
|
|
|
13
|
-
**CRITICAL**: You MUST NOT exit until ALL work is complete and committed. When you spawn normal work subagents, wait for each to finish (run_in_background=false).
|
|
13
|
+
**CRITICAL**: You MUST NOT exit until ALL work is complete and committed. When you spawn normal work subagents, wait for each to finish (run_in_background=false). Do NOT spawn work agents in background and exit — that kills the session.
|
|
14
14
|
|
|
15
15
|
**Tier 3 — Full Team**: For complex features, use the full pipeline (Phase 0–6) with Dev + Reviewer agents spawned via the Agent tool.
|
|
16
16
|
|
|
@@ -284,7 +284,7 @@ Prompt:
|
|
|
284
284
|
> ⚠️ DO NOT re-read source files already listed in Section 4 File Manifest unless you need implementation detail beyond the interface summary.
|
|
285
285
|
> 1. Read `.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md` for full context.
|
|
286
286
|
> 2. Run `/prizmkit-implement` to execute the tasks in plan.md. Run tests with: `{{TEST_CMD}}`. Known baseline failures (pre-existing, not your fault): `{{BASELINE_FAILURES}}`.
|
|
287
|
-
> 3. If plan.md has more than 5 tasks:
|
|
287
|
+
> 3. If plan.md has more than 5 tasks: update durable checkpoints/artifacts after every 3 tasks, minimize output, and rely on runner continuation if context overflow occurs. In an interactive Claude Code session operated manually outside the headless pipeline, `/compact` may be used as an optional convenience only.
|
|
288
288
|
> 4. After implement completes, verify the '## Implementation Log' section was written to context-snapshot.md.
|
|
289
289
|
> 5. Do NOT execute any git commands (no git add/commit/reset/push).
|
|
290
290
|
> Do NOT exit until all tasks are [x] and the '## Implementation Log' section is written in context-snapshot.md."
|
|
@@ -638,7 +638,7 @@ If you encounter an unrecoverable error, context overflow, or are about to exit
|
|
|
638
638
|
- Tier 3: full team — Dev (implementation) → Reviewer (review) — spawn agents directly via Agent tool
|
|
639
639
|
- context-snapshot.md is append-only: orchestrator writes Sections 1-4, Dev appends Implementation Log, Reviewer appends Review Notes
|
|
640
640
|
- Gate checks enforce Implementation Log and Review Notes are written before proceeding
|
|
641
|
-
- Do NOT use `run_in_background=true` when spawning normal work agents
|
|
641
|
+
- Do NOT use `run_in_background=true` when spawning normal work agents
|
|
642
642
|
- Commit phase must use `/prizmkit-committer`; do NOT replace with manual git commit commands
|
|
643
643
|
- **NEVER delete, modify, or touch any file under `.prizmkit/state/`** — those are pipeline runtime files managed by the runner
|
|
644
644
|
- NEVER run `rm -rf`, `git clean`, or any destructive filesystem operations
|
|
@@ -13,7 +13,7 @@ You are the **bug fix session agent**. Fix Bug {{BUG_ID}}: "{{BUG_TITLE}}".
|
|
|
13
13
|
|
|
14
14
|
**CRITICAL**: You MUST NOT exit until ALL work is complete and committed.
|
|
15
15
|
|
|
16
|
-
**SUBAGENT LIFECYCLE**:
|
|
16
|
+
**SUBAGENT LIFECYCLE**: Any normal work subagent must be awaited with `run_in_background=false`. Do not spawn persistent background subagents for headless recovery.
|
|
17
17
|
|
|
18
18
|
**NON-INTERACTIVE MODE**: There is NO human on the other end. NEVER ask for user confirmation, NEVER wait for user input. Make decisions autonomously and move forward.
|
|
19
19
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
You are the **refactor session orchestrator**. Execute Refactor {{REFACTOR_ID}}: "{{REFACTOR_TITLE}}".
|
|
14
14
|
|
|
15
|
-
**CRITICAL SESSION LIFECYCLE RULE**: You MUST NOT exit until ALL work is complete and session-status.json is written. When you spawn normal work subagents, you MUST **wait for each to finish** (run_in_background=false) before proceeding.
|
|
15
|
+
**CRITICAL SESSION LIFECYCLE RULE**: You MUST NOT exit until ALL work is complete and session-status.json is written. When you spawn normal work subagents, you MUST **wait for each to finish** (run_in_background=false) before proceeding. Do NOT spawn a work agent in the background and exit — that kills the session.
|
|
16
16
|
|
|
17
17
|
**NON-INTERACTIVE MODE**: You are running in headless non-interactive mode. There is NO human on the other end. NEVER ask for user confirmation, NEVER wait for user input, NEVER use interactive prompts (e.g. "Would you like me to…"). If a skill has an interactive step (e.g. offer remediation, ask for approval), skip it and proceed autonomously. Make decisions based on the data available and move forward.
|
|
18
18
|
|
|
@@ -185,7 +185,7 @@ Evidence must cover affected UI render, primary user interactions, behavior-sens
|
|
|
185
185
|
|
|
186
186
|
{{END_IF_BROWSER_INTERACTION}}
|
|
187
187
|
|
|
188
|
-
7. If the refactor involves multiple files:
|
|
188
|
+
7. If the refactor involves multiple files: update durable checkpoints/artifacts at the midpoint, minimize output, and rely on runner continuation if context overflow occurs. In an interactive Claude Code session operated manually outside the headless pipeline, `/compact` may be used as an optional convenience only.
|
|
189
189
|
8. After all tasks complete, run the full test suite one final time
|
|
190
190
|
"
|
|
191
191
|
- **Wait for Dev to return**
|
|
@@ -337,4 +337,4 @@ Write to: `{{SESSION_STATUS_PATH}}`
|
|
|
337
337
|
- **Commit with** `refactor(<scope>):` prefix, NOT `feat:` or `fix:`
|
|
338
338
|
- **Run full retrospective** (Job 1 + Job 2) — refactoring changes code structure
|
|
339
339
|
- ALWAYS write session-status.json before exiting
|
|
340
|
-
- Do NOT use `run_in_background=true` when spawning normal work agents
|
|
340
|
+
- Do NOT use `run_in_background=true` when spawning normal work agents
|
|
@@ -1,76 +1,41 @@
|
|
|
1
|
-
## Session Log Budget
|
|
1
|
+
## Session Log Budget Awareness (CHECKPOINT GUIDANCE)
|
|
2
2
|
|
|
3
|
-
Your session log grows with every tool call and
|
|
4
|
-
|
|
3
|
+
Your session log grows with every tool call and agent interaction. Use this
|
|
4
|
+
section as passive checkpoint guidance only; do not spawn a background helper for
|
|
5
|
+
log-size checks.
|
|
5
6
|
|
|
6
|
-
###
|
|
7
|
+
### Checkpoint Before Large Work Batches
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
- `MAX_LOG_SIZE` controls whether this advisory section is included. It is not a
|
|
10
|
+
runtime recovery mechanism.
|
|
11
|
+
- Before major phases or large task batches, update durable artifacts that a
|
|
12
|
+
continuation session can trust: plan/task checkboxes, context snapshots,
|
|
13
|
+
implementation or review notes, workflow checkpoints, session status, and test
|
|
14
|
+
reports where applicable.
|
|
15
|
+
- Keep command output small. Capture large outputs to temporary files and read
|
|
16
|
+
only filtered summaries needed for the current decision.
|
|
17
|
+
- Prefer already-built context snapshots and manifests over broad source-file
|
|
18
|
+
re-reads.
|
|
10
19
|
|
|
11
|
-
|
|
12
|
-
Run this command and keep it running:
|
|
13
|
-
bash {{LOG_MONITOR_SCRIPT}} {{SESSION_LOG_PATH}} {{MAX_LOG_SIZE}} {{LOG_MONITOR_POLL_INTERVAL}} {{LOG_MONITOR_STATUS_FILE}} {{LOG_MONITOR_BASELINE_FILE}}
|
|
14
|
-
```
|
|
20
|
+
### Headless Recovery Source of Truth
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
- Headless recovery must rely on durable checkpoints and artifacts, not an
|
|
23
|
+
interactive slash command.
|
|
24
|
+
- If context overflow still occurs, the runner detects the overflow and starts a
|
|
25
|
+
fresh continuation session from checkpoint files and durable artifacts.
|
|
26
|
+
- Durable checkpoint files and artifacts are the source of truth for what was
|
|
27
|
+
completed, what remains, and the tests or review gates already run.
|
|
18
28
|
|
|
19
|
-
|
|
29
|
+
### Interactive Claude Code Note
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
The status file contains structured diagnostics:
|
|
26
|
-
|
|
27
|
-
```json
|
|
28
|
-
{
|
|
29
|
-
"status": "OK | COMPACT_NEEDED",
|
|
30
|
-
"total_bytes": <current session.log size>,
|
|
31
|
-
"baseline_bytes": <size after last /compact>,
|
|
32
|
-
"effective_bytes": <total - baseline, incremental growth>,
|
|
33
|
-
"threshold_bytes": <{{MAX_LOG_SIZE}}>,
|
|
34
|
-
"overage_bytes": <effective - threshold, 0 when OK>,
|
|
35
|
-
"usage_pct": <effective / threshold * 100>
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Extract fields for decision-making:
|
|
40
|
-
```bash
|
|
41
|
-
status=$(jq -r '.status' {{LOG_MONITOR_STATUS_FILE}})
|
|
42
|
-
effective=$(jq -r '.effective_bytes' {{LOG_MONITOR_STATUS_FILE}})
|
|
43
|
-
overage=$(jq -r '.overage_bytes' {{LOG_MONITOR_STATUS_FILE}})
|
|
44
|
-
usage=$(jq -r '.usage_pct' {{LOG_MONITOR_STATUS_FILE}})
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
3. **On COMPACT_NEEDED** (effective_bytes > {{MAX_LOG_SIZE}} bytes, {{MAX_LOG_SIZE_HUMAN}}):
|
|
48
|
-
- Check `overage_bytes` to decide urgency:
|
|
49
|
-
- **Minor overage** (< 512KB): complete the current sub-step, update durable workflow checkpoint artifacts, write baseline, `/compact`
|
|
50
|
-
- **Major overage** (>= 512KB): update durable workflow checkpoint artifacts, write baseline, `/compact` — do NOT start new work
|
|
51
|
-
- Write baseline before `/compact`:
|
|
52
|
-
```bash
|
|
53
|
-
wc -c < {{SESSION_LOG_PATH}} > {{LOG_MONITOR_BASELINE_FILE}}
|
|
54
|
-
```
|
|
55
|
-
- Execute `/compact`
|
|
56
|
-
- Continue the workflow after `/compact`; re-read this status file before the next major phase
|
|
57
|
-
|
|
58
|
-
### How the Baseline Prevents Infinite Compaction Loops
|
|
59
|
-
|
|
60
|
-
- Before the first `/compact`: baseline = 0, effective = full log size → triggers when log exceeds threshold
|
|
61
|
-
- After `/compact`: baseline = current log size, effective ≈ 0 → status resets to OK
|
|
62
|
-
- The monitor only triggers again when **new** log growth since the last compact exceeds the threshold
|
|
63
|
-
- Each `/compact` resets the baseline, breaking the infinite loop
|
|
31
|
+
- `/compact` may help when a human is manually operating an interactive Claude
|
|
32
|
+
Code session.
|
|
33
|
+
- `/compact` is optional interactive convenience only. It must not be required
|
|
34
|
+
for headless recovery and must not be delegated to a background helper.
|
|
64
35
|
|
|
65
36
|
### Log Growth Reduction Tips
|
|
66
37
|
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
|
|
72
|
-
### What Survives
|
|
73
|
-
|
|
74
|
-
- All git commits are permanent
|
|
75
|
-
- The workflow checkpoint is preserved and auto-resumed
|
|
76
|
-
- `/compact` compresses context without losing progress
|
|
38
|
+
- Reuse context-snapshot.md instead of re-reading files already summarized there.
|
|
39
|
+
- Filter command output before loading it into context.
|
|
40
|
+
- Keep agent prompts focused and avoid unnecessary transcript expansion.
|
|
41
|
+
- Checkpoint often so automatic continuation can resume from durable state.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"status": {
|
|
17
17
|
"type": "string",
|
|
18
|
-
"enum": ["success", "partial", "failed", "commit_missing", "docs_missing", "merge_conflict"]
|
|
18
|
+
"enum": ["success", "partial", "failed", "commit_missing", "docs_missing", "merge_conflict", "context_overflow"]
|
|
19
19
|
},
|
|
20
20
|
"completed_phases": {
|
|
21
21
|
"type": "array",
|
|
@@ -24,5 +24,6 @@ def _load_hyphenated_module(module_name, filename):
|
|
|
24
24
|
# Register hyphenated script files so tests can import them with underscores.
|
|
25
25
|
_load_hyphenated_module("generate_bootstrap_prompt", "generate-bootstrap-prompt.py")
|
|
26
26
|
_load_hyphenated_module("generate_bugfix_prompt", "generate-bugfix-prompt.py")
|
|
27
|
+
_load_hyphenated_module("generate_refactor_prompt", "generate-refactor-prompt.py")
|
|
27
28
|
_load_hyphenated_module("prizmkit_test_gate", "prizmkit-test-gate.py")
|
|
28
29
|
_load_hyphenated_module("update_feature_status", "update-feature-status.py")
|