prizmkit 1.1.96 → 1.1.98
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/run-bugfix.sh +5 -1
- package/bundled/dev-pipeline/run-feature.sh +5 -1
- package/bundled/dev-pipeline/run-refactor.sh +5 -1
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +98 -7
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +86 -3
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +86 -3
- package/bundled/dev-pipeline/scripts/monitor-log.sh +104 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +4 -1
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +5 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +5 -2
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +3 -0
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +3 -2
- package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +76 -0
- package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +2 -0
- package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +98 -7
- package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +86 -3
- package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +86 -3
- package/bundled/dev-pipeline-windows/scripts/monitor-log.ps1 +102 -0
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +4 -1
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +5 -2
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +5 -2
- package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +3 -0
- package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +3 -2
- package/bundled/dev-pipeline-windows/templates/sections/log-size-awareness.md +78 -0
- package/bundled/skills/_metadata.json +1 -1
- package/package.json +1 -1
package/bundled/VERSION.json
CHANGED
|
@@ -23,6 +23,8 @@ set -euo pipefail
|
|
|
23
23
|
# HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)
|
|
24
24
|
# STALE_KILL_THRESHOLD Auto-kill session after N seconds of no progress (default: 900)
|
|
25
25
|
# HEARTBEAT_STALE_THRESHOLD Heartbeat stale threshold in seconds (default: 600)
|
|
26
|
+
# MAX_LOG_SIZE Session log size threshold for compact warning (default: 2097152 = 2MB, set 0 to disable)
|
|
27
|
+
# LOG_MONITOR_POLL_INTERVAL Log monitor poll interval in seconds (default: 30)
|
|
26
28
|
# DEV_BRANCH Custom dev branch name (default: auto-generated bugfix/pipeline-{run_id})
|
|
27
29
|
# AUTO_PUSH Auto-push to remote after successful bug fix (default: 0). Set to 1 to enable.
|
|
28
30
|
# STOP_ON_FAILURE Stop pipeline after a task exhausts all retries (default: 0). Set to 1 to stop.
|
|
@@ -283,7 +285,7 @@ finalize_bug_status_after_branch_return() {
|
|
|
283
285
|
--session-id "$session_id" \
|
|
284
286
|
--max-retries "$max_retries" \
|
|
285
287
|
--max-infra-retries "$max_infra_retries" \
|
|
286
|
-
--action update
|
|
288
|
+
--action update) || {
|
|
287
289
|
log_error "Failed to update bug status: $update_output"
|
|
288
290
|
update_output=""
|
|
289
291
|
}
|
|
@@ -1227,6 +1229,8 @@ show_help() {
|
|
|
1227
1229
|
echo " VERBOSE Set to 1 for verbose AI CLI output"
|
|
1228
1230
|
echo " HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)"
|
|
1229
1231
|
echo " STALE_KILL_THRESHOLD Auto-kill session after N seconds of no progress (default: 900)"
|
|
1232
|
+
echo " MAX_LOG_SIZE Session log size threshold for compact warning (default: 2097152 = 2MB, set 0 to disable)"
|
|
1233
|
+
echo " LOG_MONITOR_POLL_INTERVAL Log monitor poll interval in seconds (default: 30)"
|
|
1230
1234
|
echo " STOP_ON_FAILURE Stop pipeline when a task exhausts retries: 0|1 (default: 0)"
|
|
1231
1235
|
echo ""
|
|
1232
1236
|
echo "Examples:"
|
|
@@ -25,6 +25,8 @@ set -euo pipefail
|
|
|
25
25
|
# HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)
|
|
26
26
|
# STALE_KILL_THRESHOLD Auto-kill session after N seconds of no progress (default: 900)
|
|
27
27
|
# HEARTBEAT_STALE_THRESHOLD Heartbeat stale threshold in seconds (default: 600)
|
|
28
|
+
# MAX_LOG_SIZE Session log size threshold for compact warning (default: 2097152 = 2MB, set 0 to disable)
|
|
29
|
+
# LOG_MONITOR_POLL_INTERVAL Log monitor poll interval in seconds (default: 30)
|
|
28
30
|
# PIPELINE_MODE Override mode for all features: lite|standard|full (used by daemon)
|
|
29
31
|
# DEV_BRANCH Custom dev branch name (default: auto-generated dev/{feature_id}-YYYYMMDDHHmm)
|
|
30
32
|
# AUTO_PUSH Auto-push to remote after successful feature (default: 0). Set to 1 to enable.
|
|
@@ -402,7 +404,7 @@ finalize_feature_status_after_branch_return() {
|
|
|
402
404
|
--session-id "$session_id" \
|
|
403
405
|
--max-retries "$max_retries" \
|
|
404
406
|
--max-infra-retries "$max_infra_retries" \
|
|
405
|
-
--action update
|
|
407
|
+
--action update) || {
|
|
406
408
|
log_error "Failed to update feature status: $update_output"
|
|
407
409
|
log_error ".prizmkit/plans/feature-list.json may be out of sync. Manual intervention needed."
|
|
408
410
|
}
|
|
@@ -1433,6 +1435,8 @@ show_help() {
|
|
|
1433
1435
|
echo " HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)"
|
|
1434
1436
|
echo " STALE_KILL_THRESHOLD Auto-kill session after N seconds of no progress (default: 900)"
|
|
1435
1437
|
echo " HEARTBEAT_STALE_THRESHOLD Heartbeat stale threshold in seconds (default: 600)"
|
|
1438
|
+
echo " MAX_LOG_SIZE Session log size threshold for compact warning (default: 2097152 = 2MB, set 0 to disable)"
|
|
1439
|
+
echo " LOG_MONITOR_POLL_INTERVAL Log monitor poll interval in seconds (default: 30)"
|
|
1436
1440
|
echo " PIPELINE_MODE Override mode for all features: lite|standard|full"
|
|
1437
1441
|
echo " ENABLE_CRITIC Enable critic review for all features: true|false"
|
|
1438
1442
|
echo " STOP_ON_FAILURE Stop pipeline when a task exhausts retries: 0|1 (default: 0)"
|
|
@@ -23,6 +23,8 @@ set -euo pipefail
|
|
|
23
23
|
# HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)
|
|
24
24
|
# STALE_KILL_THRESHOLD Auto-kill session after N seconds of no progress (default: 900)
|
|
25
25
|
# HEARTBEAT_STALE_THRESHOLD Heartbeat stale threshold in seconds (default: 600)
|
|
26
|
+
# MAX_LOG_SIZE Session log size threshold for compact warning (default: 2097152 = 2MB, set 0 to disable)
|
|
27
|
+
# LOG_MONITOR_POLL_INTERVAL Log monitor poll interval in seconds (default: 30)
|
|
26
28
|
# DEV_BRANCH Custom dev branch name (default: auto-generated refactor/pipeline-{run_id})
|
|
27
29
|
# AUTO_PUSH Auto-push to remote after successful refactor (default: 0). Set to 1 to enable.
|
|
28
30
|
# STOP_ON_FAILURE Stop pipeline after a task exhausts all retries (default: 0). Set to 1 to stop.
|
|
@@ -309,7 +311,7 @@ finalize_refactor_status_after_branch_return() {
|
|
|
309
311
|
--session-id "$session_id" \
|
|
310
312
|
--max-retries "$max_retries" \
|
|
311
313
|
--max-infra-retries "$max_infra_retries" \
|
|
312
|
-
--action update
|
|
314
|
+
--action update) || {
|
|
313
315
|
log_error "Failed to update refactor status: $update_output"
|
|
314
316
|
update_output=""
|
|
315
317
|
}
|
|
@@ -1261,6 +1263,8 @@ show_help() {
|
|
|
1261
1263
|
echo " STRICT_BEHAVIOR_CHECK Force full test suite after each refactor (default: 1)"
|
|
1262
1264
|
echo " HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)"
|
|
1263
1265
|
echo " STALE_KILL_THRESHOLD Auto-kill session after N seconds of no progress (default: 900)"
|
|
1266
|
+
echo " MAX_LOG_SIZE Session log size threshold for compact warning (default: 2097152 = 2MB, set 0 to disable)"
|
|
1267
|
+
echo " LOG_MONITOR_POLL_INTERVAL Log monitor poll interval in seconds (default: 30)"
|
|
1264
1268
|
echo " STOP_ON_FAILURE Stop pipeline when a task exhausts retries: 0|1 (default: 0)"
|
|
1265
1269
|
echo ""
|
|
1266
1270
|
echo "Examples:"
|
|
@@ -31,6 +31,8 @@ from utils import enrich_global_context, load_json_file, read_platform_conventio
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
DEFAULT_MAX_RETRIES = 3
|
|
34
|
+
DEFAULT_MAX_LOG_SIZE = 2097152
|
|
35
|
+
DEFAULT_LOG_MONITOR_POLL_INTERVAL = 30
|
|
34
36
|
|
|
35
37
|
LOGGER = setup_logging("generate-bootstrap-prompt")
|
|
36
38
|
|
|
@@ -152,6 +154,54 @@ def compute_feature_slug(feature_id, title):
|
|
|
152
154
|
return "{}-{}".format(numeric, slug)
|
|
153
155
|
|
|
154
156
|
|
|
157
|
+
def _format_bytes(n):
|
|
158
|
+
"""Format a byte count to a human-readable string."""
|
|
159
|
+
if n >= 1048576:
|
|
160
|
+
return "{}MB".format(n // 1048576)
|
|
161
|
+
elif n >= 1024:
|
|
162
|
+
return "{}KB".format(n // 1024)
|
|
163
|
+
return "{}B".format(n)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _parse_non_negative_int_env(name, default):
|
|
167
|
+
"""Parse a non-negative integer environment variable."""
|
|
168
|
+
value = os.environ.get(name, str(default))
|
|
169
|
+
try:
|
|
170
|
+
parsed = int(value)
|
|
171
|
+
except ValueError:
|
|
172
|
+
emit_failure("{} must be a non-negative integer".format(name))
|
|
173
|
+
if parsed < 0:
|
|
174
|
+
emit_failure("{} must be a non-negative integer".format(name))
|
|
175
|
+
return parsed
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _parse_positive_int_env(name, default):
|
|
179
|
+
"""Parse a positive integer environment variable."""
|
|
180
|
+
value = os.environ.get(name, str(default))
|
|
181
|
+
try:
|
|
182
|
+
parsed = int(value)
|
|
183
|
+
except ValueError:
|
|
184
|
+
emit_failure("{} must be a positive integer".format(name))
|
|
185
|
+
if parsed <= 0:
|
|
186
|
+
emit_failure("{} must be a positive integer".format(name))
|
|
187
|
+
return parsed
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def load_log_size_section(script_dir):
|
|
192
|
+
"""Load the optional log-size awareness prompt section."""
|
|
193
|
+
max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
|
|
194
|
+
if max_log_size == 0:
|
|
195
|
+
return ""
|
|
196
|
+
|
|
197
|
+
section_path = os.path.join(
|
|
198
|
+
script_dir, "..", "templates", "sections", "log-size-awareness.md",
|
|
199
|
+
)
|
|
200
|
+
content, err = read_text_file(section_path)
|
|
201
|
+
if err:
|
|
202
|
+
return ""
|
|
203
|
+
return content
|
|
204
|
+
|
|
155
205
|
def format_acceptance_criteria(criteria):
|
|
156
206
|
"""Format acceptance criteria as a markdown bullet list."""
|
|
157
207
|
if not criteria:
|
|
@@ -1272,7 +1322,8 @@ def _tier_header(pipeline_mode):
|
|
|
1272
1322
|
"lite": (
|
|
1273
1323
|
"# Dev-Pipeline Session Bootstrap — Tier 1 (Single Agent)\n",
|
|
1274
1324
|
"**Tier 1 — Single Agent**: You handle everything directly. "
|
|
1275
|
-
"No subagents
|
|
1325
|
+
"No normal work subagents; the log-size monitor is the only "
|
|
1326
|
+
"allowed persistent background subagent. No TeamCreate.\n",
|
|
1276
1327
|
),
|
|
1277
1328
|
"standard": (
|
|
1278
1329
|
"# Dev-Pipeline Session Bootstrap — Tier 2 (Dual Agent)\n",
|
|
@@ -1309,7 +1360,9 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
|
|
|
1309
1360
|
|
|
1310
1361
|
if pipeline_mode == "lite":
|
|
1311
1362
|
specific = [
|
|
1312
|
-
"- Tier 1: you handle everything directly — no
|
|
1363
|
+
"- Tier 1: you handle everything directly — no normal work "
|
|
1364
|
+
"subagents needed; the log-size monitor is the only allowed "
|
|
1365
|
+
"persistent background subagent",
|
|
1313
1366
|
]
|
|
1314
1367
|
elif pipeline_mode == "standard":
|
|
1315
1368
|
specific = [
|
|
@@ -1321,7 +1374,9 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
|
|
|
1321
1374
|
"appends Review Notes",
|
|
1322
1375
|
"- Gate checks enforce Implementation Log and Review Notes are "
|
|
1323
1376
|
"written before proceeding",
|
|
1324
|
-
"- Do NOT use `run_in_background=true` when spawning
|
|
1377
|
+
"- Do NOT use `run_in_background=true` when spawning normal work "
|
|
1378
|
+
"subagents; the log-size monitor is the only allowed persistent "
|
|
1379
|
+
"background subagent",
|
|
1325
1380
|
"- On timeout: check snapshot + git diff HEAD → model:lite → "
|
|
1326
1381
|
"remaining steps only → max 2 retries per phase → "
|
|
1327
1382
|
"orchestrator fallback",
|
|
@@ -1335,7 +1390,9 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
|
|
|
1335
1390
|
"appends Review Notes",
|
|
1336
1391
|
"- Gate checks enforce Implementation Log and Review Notes are "
|
|
1337
1392
|
"written before proceeding",
|
|
1338
|
-
"- Do NOT use `run_in_background=true` when spawning
|
|
1393
|
+
"- Do NOT use `run_in_background=true` when spawning normal work "
|
|
1394
|
+
"agents; the log-size monitor is the only allowed persistent "
|
|
1395
|
+
"background subagent",
|
|
1339
1396
|
"- On timeout: check snapshot → model:lite → remaining steps "
|
|
1340
1397
|
"only → max 2 retries → orchestrator fallback",
|
|
1341
1398
|
]
|
|
@@ -1374,12 +1431,13 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
|
|
|
1374
1431
|
)
|
|
1375
1432
|
if pipeline_mode != "lite":
|
|
1376
1433
|
mission += (
|
|
1377
|
-
" When you spawn subagents, wait for each to finish "
|
|
1378
|
-
"(run_in_background=false)."
|
|
1434
|
+
" When you spawn normal work subagents, wait for each to finish "
|
|
1435
|
+
"(run_in_background=false). The log-size monitor is the only "
|
|
1436
|
+
"allowed persistent background subagent."
|
|
1379
1437
|
)
|
|
1380
1438
|
if pipeline_mode == "full":
|
|
1381
1439
|
mission += (
|
|
1382
|
-
" Do NOT spawn agents in background and exit — "
|
|
1440
|
+
" Do NOT spawn work agents in background and exit — "
|
|
1383
1441
|
"that kills the session."
|
|
1384
1442
|
)
|
|
1385
1443
|
mission += "\n\n" + tier_desc
|
|
@@ -1397,6 +1455,12 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
|
|
|
1397
1455
|
sections.append(("context-budget-rules",
|
|
1398
1456
|
load_section(sections_dir, "context-budget-rules.md")))
|
|
1399
1457
|
|
|
1458
|
+
# --- Log Size Awareness (only disabled when MAX_LOG_SIZE is explicitly 0) ---
|
|
1459
|
+
max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
|
|
1460
|
+
if max_log_size != 0 and os.path.isfile(os.path.join(sections_dir, "log-size-awareness.md")):
|
|
1461
|
+
sections.append(("log-size-awareness",
|
|
1462
|
+
load_section(sections_dir, "log-size-awareness.md")))
|
|
1463
|
+
|
|
1400
1464
|
# --- Directory Convention (tier-specific) ---
|
|
1401
1465
|
if pipeline_mode == "lite":
|
|
1402
1466
|
dc_file = "directory-convention-lite.md"
|
|
@@ -1866,7 +1930,16 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1866
1930
|
pass # Keep default 3000 on any error
|
|
1867
1931
|
dev_url = f"http://localhost:{dev_port}"
|
|
1868
1932
|
|
|
1933
|
+
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
|
+
|
|
1869
1941
|
replacements = {
|
|
1942
|
+
"{{LOG_SIZE_AWARENESS}}": load_log_size_section(script_dir),
|
|
1870
1943
|
"{{RUN_ID}}": args.run_id,
|
|
1871
1944
|
"{{SESSION_ID}}": args.session_id,
|
|
1872
1945
|
"{{FEATURE_ID}}": args.feature_id,
|
|
@@ -1911,6 +1984,24 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1911
1984
|
"{{COVERAGE_TARGET}}": coverage_target,
|
|
1912
1985
|
"{{DEV_PORT}}": dev_port,
|
|
1913
1986
|
"{{DEV_URL}}": dev_url,
|
|
1987
|
+
"{{MAX_LOG_SIZE}}": str(max_log_size),
|
|
1988
|
+
"{{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
|
+
"{{SESSION_LOG_PATH}}": os.path.join(
|
|
1999
|
+
".prizmkit", "state", "features", args.feature_id,
|
|
2000
|
+
"sessions", args.session_id, "logs", "session.log",
|
|
2001
|
+
),
|
|
2002
|
+
"{{LOG_MONITOR_SCRIPT}}": os.path.join(
|
|
2003
|
+
".prizmkit", "dev-pipeline", "scripts", "monitor-log.sh",
|
|
2004
|
+
),
|
|
1914
2005
|
}
|
|
1915
2006
|
|
|
1916
2007
|
return replacements, effective_resume, browser_enabled, browser_tool
|
|
@@ -23,6 +23,8 @@ from utils import enrich_global_context, load_json_file, read_platform_conventio
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
DEFAULT_MAX_RETRIES = 3
|
|
26
|
+
DEFAULT_MAX_LOG_SIZE = 2097152
|
|
27
|
+
DEFAULT_LOG_MONITOR_POLL_INTERVAL = 30
|
|
26
28
|
|
|
27
29
|
LOGGER = setup_logging("generate-bugfix-prompt")
|
|
28
30
|
|
|
@@ -60,6 +62,57 @@ def read_text_file(path):
|
|
|
60
62
|
return None, "Cannot read file: {}".format(str(e))
|
|
61
63
|
|
|
62
64
|
|
|
65
|
+
|
|
66
|
+
def _format_bytes(n):
|
|
67
|
+
"""Format a byte count to a human-readable string."""
|
|
68
|
+
if n >= 1048576:
|
|
69
|
+
return "{}MB".format(n // 1048576)
|
|
70
|
+
elif n >= 1024:
|
|
71
|
+
return "{}KB".format(n // 1024)
|
|
72
|
+
return "{}B".format(n)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _parse_non_negative_int_env(name, default):
|
|
76
|
+
"""Parse a non-negative integer environment variable."""
|
|
77
|
+
value = os.environ.get(name, str(default))
|
|
78
|
+
try:
|
|
79
|
+
parsed = int(value)
|
|
80
|
+
except ValueError:
|
|
81
|
+
raise ValueError("{} must be a non-negative integer".format(name))
|
|
82
|
+
if parsed < 0:
|
|
83
|
+
raise ValueError("{} must be a non-negative integer".format(name))
|
|
84
|
+
return parsed
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _parse_positive_int_env(name, default):
|
|
88
|
+
"""Parse a positive integer environment variable."""
|
|
89
|
+
value = os.environ.get(name, str(default))
|
|
90
|
+
try:
|
|
91
|
+
parsed = int(value)
|
|
92
|
+
except ValueError:
|
|
93
|
+
raise ValueError("{} must be a positive integer".format(name))
|
|
94
|
+
if parsed <= 0:
|
|
95
|
+
raise ValueError("{} must be a positive integer".format(name))
|
|
96
|
+
return parsed
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def load_log_size_section(script_dir):
|
|
100
|
+
"""Load the optional log-size awareness prompt section."""
|
|
101
|
+
max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
|
|
102
|
+
if max_log_size == 0:
|
|
103
|
+
return ""
|
|
104
|
+
|
|
105
|
+
section_path = os.path.join(
|
|
106
|
+
script_dir, "..", "templates", "sections", "log-size-awareness.md",
|
|
107
|
+
)
|
|
108
|
+
if not os.path.isfile(section_path):
|
|
109
|
+
return ""
|
|
110
|
+
|
|
111
|
+
content, err = read_text_file(section_path)
|
|
112
|
+
if err:
|
|
113
|
+
return ""
|
|
114
|
+
return content
|
|
115
|
+
|
|
63
116
|
def find_bug(bugs, bug_id):
|
|
64
117
|
"""Find and return the bug dict matching the given ID."""
|
|
65
118
|
for bug in bugs:
|
|
@@ -304,7 +357,34 @@ def build_replacements(args, bug, global_context, script_dir):
|
|
|
304
357
|
browser_enabled = True
|
|
305
358
|
browser_verify_steps = " # (reproduce bug and verify fix)"
|
|
306
359
|
|
|
360
|
+
max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
|
|
361
|
+
if max_log_size == 0:
|
|
362
|
+
poll_interval = DEFAULT_LOG_MONITOR_POLL_INTERVAL
|
|
363
|
+
else:
|
|
364
|
+
poll_interval = _parse_positive_int_env(
|
|
365
|
+
"LOG_MONITOR_POLL_INTERVAL", DEFAULT_LOG_MONITOR_POLL_INTERVAL,
|
|
366
|
+
)
|
|
367
|
+
|
|
307
368
|
replacements = {
|
|
369
|
+
"{{LOG_SIZE_AWARENESS}}": load_log_size_section(script_dir),
|
|
370
|
+
"{{MAX_LOG_SIZE}}": str(max_log_size),
|
|
371
|
+
"{{MAX_LOG_SIZE_HUMAN}}": _format_bytes(max_log_size),
|
|
372
|
+
"{{LOG_MONITOR_POLL_INTERVAL}}": str(poll_interval),
|
|
373
|
+
"{{LOG_MONITOR_STATUS_FILE}}": os.path.join(
|
|
374
|
+
".prizmkit", "state", "bugfix", "bugs", args.bug_id,
|
|
375
|
+
"sessions", args.session_id, "logs", "log-monitor-status.txt",
|
|
376
|
+
),
|
|
377
|
+
"{{LOG_MONITOR_BASELINE_FILE}}": os.path.join(
|
|
378
|
+
".prizmkit", "state", "bugfix", "bugs", args.bug_id,
|
|
379
|
+
"sessions", args.session_id, "logs", "log-monitor-baseline.txt",
|
|
380
|
+
),
|
|
381
|
+
"{{SESSION_LOG_PATH}}": os.path.join(
|
|
382
|
+
".prizmkit", "state", "bugfix", "bugs", args.bug_id,
|
|
383
|
+
"sessions", args.session_id, "logs", "session.log",
|
|
384
|
+
),
|
|
385
|
+
"{{LOG_MONITOR_SCRIPT}}": os.path.join(
|
|
386
|
+
".prizmkit", "dev-pipeline", "scripts", "monitor-log.sh",
|
|
387
|
+
),
|
|
308
388
|
"{{RUN_ID}}": args.run_id,
|
|
309
389
|
"{{SESSION_ID}}": args.session_id,
|
|
310
390
|
"{{BUG_ID}}": args.bug_id,
|
|
@@ -420,9 +500,10 @@ def render_template(template_content, replacements, bug):
|
|
|
420
500
|
# Step 1: Process conditional blocks
|
|
421
501
|
content = process_conditional_blocks(template_content, bug)
|
|
422
502
|
|
|
423
|
-
# Step 2: Replace all {{PLACEHOLDER}} variables
|
|
424
|
-
for
|
|
425
|
-
|
|
503
|
+
# Step 2: Replace all {{PLACEHOLDER}} variables (two passes for nested sections)
|
|
504
|
+
for _pass in range(2):
|
|
505
|
+
for placeholder, value in replacements.items():
|
|
506
|
+
content = content.replace(placeholder, value)
|
|
426
507
|
|
|
427
508
|
return content
|
|
428
509
|
|
|
@@ -675,6 +756,8 @@ if __name__ == "__main__":
|
|
|
675
756
|
main()
|
|
676
757
|
except KeyboardInterrupt:
|
|
677
758
|
emit_failure("generate-bugfix-prompt interrupted")
|
|
759
|
+
except ValueError as exc:
|
|
760
|
+
emit_failure(str(exc))
|
|
678
761
|
except SystemExit:
|
|
679
762
|
raise
|
|
680
763
|
except Exception as exc:
|
|
@@ -23,6 +23,8 @@ from utils import enrich_global_context, load_json_file, read_platform_conventio
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
DEFAULT_MAX_RETRIES = 3
|
|
26
|
+
DEFAULT_MAX_LOG_SIZE = 2097152
|
|
27
|
+
DEFAULT_LOG_MONITOR_POLL_INTERVAL = 30
|
|
26
28
|
|
|
27
29
|
LOGGER = setup_logging("generate-refactor-prompt")
|
|
28
30
|
|
|
@@ -151,6 +153,57 @@ def read_text_file(path):
|
|
|
151
153
|
return None, "Cannot read file: {}".format(str(e))
|
|
152
154
|
|
|
153
155
|
|
|
156
|
+
|
|
157
|
+
def _format_bytes(n):
|
|
158
|
+
"""Format a byte count to a human-readable string."""
|
|
159
|
+
if n >= 1048576:
|
|
160
|
+
return "{}MB".format(n // 1048576)
|
|
161
|
+
elif n >= 1024:
|
|
162
|
+
return "{}KB".format(n // 1024)
|
|
163
|
+
return "{}B".format(n)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _parse_non_negative_int_env(name, default):
|
|
167
|
+
"""Parse a non-negative integer environment variable."""
|
|
168
|
+
value = os.environ.get(name, str(default))
|
|
169
|
+
try:
|
|
170
|
+
parsed = int(value)
|
|
171
|
+
except ValueError:
|
|
172
|
+
raise ValueError("{} must be a non-negative integer".format(name))
|
|
173
|
+
if parsed < 0:
|
|
174
|
+
raise ValueError("{} must be a non-negative integer".format(name))
|
|
175
|
+
return parsed
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _parse_positive_int_env(name, default):
|
|
179
|
+
"""Parse a positive integer environment variable."""
|
|
180
|
+
value = os.environ.get(name, str(default))
|
|
181
|
+
try:
|
|
182
|
+
parsed = int(value)
|
|
183
|
+
except ValueError:
|
|
184
|
+
raise ValueError("{} must be a positive integer".format(name))
|
|
185
|
+
if parsed <= 0:
|
|
186
|
+
raise ValueError("{} must be a positive integer".format(name))
|
|
187
|
+
return parsed
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def load_log_size_section(script_dir):
|
|
191
|
+
"""Load the optional log-size awareness prompt section."""
|
|
192
|
+
max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
|
|
193
|
+
if max_log_size == 0:
|
|
194
|
+
return ""
|
|
195
|
+
|
|
196
|
+
section_path = os.path.join(
|
|
197
|
+
script_dir, "..", "templates", "sections", "log-size-awareness.md",
|
|
198
|
+
)
|
|
199
|
+
if not os.path.isfile(section_path):
|
|
200
|
+
return ""
|
|
201
|
+
|
|
202
|
+
content, err = read_text_file(section_path)
|
|
203
|
+
if err:
|
|
204
|
+
return ""
|
|
205
|
+
return content
|
|
206
|
+
|
|
154
207
|
def find_refactor(refactors, refactor_id):
|
|
155
208
|
"""Find and return the refactor dict matching the given ID."""
|
|
156
209
|
for refactor in refactors:
|
|
@@ -464,7 +517,34 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
|
|
|
464
517
|
browser_enabled = True
|
|
465
518
|
browser_verify_steps = " # (validate UI renders correctly and feature still works)"
|
|
466
519
|
|
|
520
|
+
max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
|
|
521
|
+
if max_log_size == 0:
|
|
522
|
+
poll_interval = DEFAULT_LOG_MONITOR_POLL_INTERVAL
|
|
523
|
+
else:
|
|
524
|
+
poll_interval = _parse_positive_int_env(
|
|
525
|
+
"LOG_MONITOR_POLL_INTERVAL", DEFAULT_LOG_MONITOR_POLL_INTERVAL,
|
|
526
|
+
)
|
|
527
|
+
|
|
467
528
|
replacements = {
|
|
529
|
+
"{{LOG_SIZE_AWARENESS}}": load_log_size_section(script_dir),
|
|
530
|
+
"{{MAX_LOG_SIZE}}": str(max_log_size),
|
|
531
|
+
"{{MAX_LOG_SIZE_HUMAN}}": _format_bytes(max_log_size),
|
|
532
|
+
"{{LOG_MONITOR_POLL_INTERVAL}}": str(poll_interval),
|
|
533
|
+
"{{LOG_MONITOR_STATUS_FILE}}": os.path.join(
|
|
534
|
+
".prizmkit", "state", "refactor", "refactors", args.refactor_id,
|
|
535
|
+
"sessions", args.session_id, "logs", "log-monitor-status.txt",
|
|
536
|
+
),
|
|
537
|
+
"{{LOG_MONITOR_BASELINE_FILE}}": os.path.join(
|
|
538
|
+
".prizmkit", "state", "refactor", "refactors", args.refactor_id,
|
|
539
|
+
"sessions", args.session_id, "logs", "log-monitor-baseline.txt",
|
|
540
|
+
),
|
|
541
|
+
"{{SESSION_LOG_PATH}}": os.path.join(
|
|
542
|
+
".prizmkit", "state", "refactor", "refactors", args.refactor_id,
|
|
543
|
+
"sessions", args.session_id, "logs", "session.log",
|
|
544
|
+
),
|
|
545
|
+
"{{LOG_MONITOR_SCRIPT}}": os.path.join(
|
|
546
|
+
".prizmkit", "dev-pipeline", "scripts", "monitor-log.sh",
|
|
547
|
+
),
|
|
468
548
|
"{{RUN_ID}}": args.run_id,
|
|
469
549
|
"{{SESSION_ID}}": args.session_id,
|
|
470
550
|
"{{REFACTOR_ID}}": args.refactor_id,
|
|
@@ -587,9 +667,10 @@ def render_template(template_content, replacements, resume_phase, refactor):
|
|
|
587
667
|
# Step 1: Process conditional blocks
|
|
588
668
|
content = process_conditional_blocks(template_content, resume_phase, refactor)
|
|
589
669
|
|
|
590
|
-
# Step 2: Replace all {{PLACEHOLDER}} variables
|
|
591
|
-
for
|
|
592
|
-
|
|
670
|
+
# Step 2: Replace all {{PLACEHOLDER}} variables (two passes for nested sections)
|
|
671
|
+
for _pass in range(2):
|
|
672
|
+
for placeholder, value in replacements.items():
|
|
673
|
+
content = content.replace(placeholder, value)
|
|
593
674
|
|
|
594
675
|
return content
|
|
595
676
|
|
|
@@ -753,6 +834,8 @@ if __name__ == "__main__":
|
|
|
753
834
|
main()
|
|
754
835
|
except KeyboardInterrupt:
|
|
755
836
|
emit_failure("generate-refactor-prompt interrupted")
|
|
837
|
+
except ValueError as exc:
|
|
838
|
+
emit_failure(str(exc))
|
|
756
839
|
except SystemExit:
|
|
757
840
|
raise
|
|
758
841
|
except Exception as exc:
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# monitor-log.sh — Background log size monitor for AI session subagent
|
|
3
|
+
#
|
|
4
|
+
# Polls session.log size, computes effective size (current - baseline),
|
|
5
|
+
# and writes a structured JSON status file. Intended to be run by a
|
|
6
|
+
# background subagent inside the AI CLI session.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# bash monitor-log.sh <session_log> <max_size> <poll_interval> <status_file> <baseline_file>
|
|
10
|
+
#
|
|
11
|
+
# Status file format (JSON, rewritten each poll cycle):
|
|
12
|
+
# {
|
|
13
|
+
# "status": "OK" | "COMPACT_NEEDED",
|
|
14
|
+
# "total_bytes": <current session.log size in bytes>,
|
|
15
|
+
# "baseline_bytes": <baseline size from last compact>,
|
|
16
|
+
# "effective_bytes": <total - baseline>,
|
|
17
|
+
# "threshold_bytes": <compact threshold>,
|
|
18
|
+
# "overage_bytes": <effective - threshold, 0 if not exceeded>,
|
|
19
|
+
# "usage_pct": <effective / threshold * 100, integer>
|
|
20
|
+
# }
|
|
21
|
+
|
|
22
|
+
SESSION_LOG="${1}"
|
|
23
|
+
MAX_SIZE="${2}"
|
|
24
|
+
POLL_INTERVAL="${3}"
|
|
25
|
+
STATUS_FILE="${4}"
|
|
26
|
+
BASELINE_FILE="${5}"
|
|
27
|
+
|
|
28
|
+
# Validate required args
|
|
29
|
+
if [[ -z "$SESSION_LOG" || -z "$MAX_SIZE" || -z "$POLL_INTERVAL" || -z "$STATUS_FILE" || -z "$BASELINE_FILE" ]]; then
|
|
30
|
+
echo "Usage: $0 <session_log> <max_size> <poll_interval> <status_file> <baseline_file>" >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Ensure status dir exists
|
|
35
|
+
mkdir -p "$(dirname "$STATUS_FILE")"
|
|
36
|
+
|
|
37
|
+
# Validate numeric args
|
|
38
|
+
if ! [[ "$MAX_SIZE" =~ ^[0-9]+$ ]]; then
|
|
39
|
+
echo "ERROR: max_size must be a non-negative integer" >&2
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
if ! [[ "$POLL_INTERVAL" =~ ^[0-9]+$ ]] || [[ "$POLL_INTERVAL" -le 0 ]]; then
|
|
43
|
+
echo "ERROR: poll_interval must be a positive integer" >&2
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
echo "[monitor-log] Starting log monitor: max=${MAX_SIZE} interval=${POLL_INTERVAL}s" >&2
|
|
48
|
+
echo "[monitor-log] Watching: $SESSION_LOG" >&2
|
|
49
|
+
|
|
50
|
+
while true; do
|
|
51
|
+
# Read current log size
|
|
52
|
+
if [[ -f "$SESSION_LOG" ]]; then
|
|
53
|
+
size=$(wc -c < "$SESSION_LOG" 2>/dev/null | tr -d ' ')
|
|
54
|
+
else
|
|
55
|
+
size=0
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Read baseline (resets after each /compact)
|
|
59
|
+
baseline=0
|
|
60
|
+
if [[ -f "$BASELINE_FILE" ]]; then
|
|
61
|
+
baseline=$(cat "$BASELINE_FILE" 2>/dev/null | tr -d ' ')
|
|
62
|
+
if ! [[ "$baseline" =~ ^[0-9]+$ ]]; then
|
|
63
|
+
baseline=0
|
|
64
|
+
fi
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Effective size = growth since last compact
|
|
68
|
+
effective=$((size - baseline))
|
|
69
|
+
if [[ $effective -lt 0 ]]; then
|
|
70
|
+
effective=0
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Determine status (MAX_SIZE=0 disables monitoring)
|
|
74
|
+
if [[ $MAX_SIZE -gt 0 && $effective -gt $MAX_SIZE ]]; then
|
|
75
|
+
status="COMPACT_NEEDED"
|
|
76
|
+
overage=$((effective - MAX_SIZE))
|
|
77
|
+
echo "[monitor-log] COMPACT_NEEDED: effective=${effective} overage=${overage} (threshold=${MAX_SIZE})" >&2
|
|
78
|
+
else
|
|
79
|
+
status="OK"
|
|
80
|
+
overage=0
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# Usage percentage (effective / threshold * 100)
|
|
84
|
+
if [[ $MAX_SIZE -gt 0 ]]; then
|
|
85
|
+
usage_pct=$((effective * 100 / MAX_SIZE))
|
|
86
|
+
else
|
|
87
|
+
usage_pct=0
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# Write structured JSON status
|
|
91
|
+
cat > "$STATUS_FILE" << EOF
|
|
92
|
+
{
|
|
93
|
+
"status": "${status}",
|
|
94
|
+
"total_bytes": ${size},
|
|
95
|
+
"baseline_bytes": ${baseline},
|
|
96
|
+
"effective_bytes": ${effective},
|
|
97
|
+
"threshold_bytes": ${MAX_SIZE},
|
|
98
|
+
"overage_bytes": ${overage},
|
|
99
|
+
"usage_pct": ${usage_pct}
|
|
100
|
+
}
|
|
101
|
+
EOF
|
|
102
|
+
|
|
103
|
+
sleep "$POLL_INTERVAL"
|
|
104
|
+
done
|
|
@@ -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 subagents
|
|
15
|
+
**Tier 1 — Single Agent**: You handle everything directly. No normal work subagents; the log-size monitor is the only allowed persistent background subagent. No TeamCreate.
|
|
16
16
|
|
|
17
17
|
### Feature Description
|
|
18
18
|
|
|
@@ -54,6 +54,9 @@ You are running in **headless non-interactive mode** with a FINITE context windo
|
|
|
54
54
|
|
|
55
55
|
---
|
|
56
56
|
|
|
57
|
+
{{LOG_SIZE_AWARENESS}}
|
|
58
|
+
---
|
|
59
|
+
|
|
57
60
|
## PrizmKit Directory Convention
|
|
58
61
|
|
|
59
62
|
```
|
|
@@ -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 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). The log-size monitor is the only allowed persistent background subagent.
|
|
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
|
|
|
@@ -60,6 +60,9 @@ You are running in **headless non-interactive mode** with a FINITE context windo
|
|
|
60
60
|
|
|
61
61
|
---
|
|
62
62
|
|
|
63
|
+
{{LOG_SIZE_AWARENESS}}
|
|
64
|
+
---
|
|
65
|
+
|
|
63
66
|
## PrizmKit Directory Convention
|
|
64
67
|
|
|
65
68
|
```
|
|
@@ -601,7 +604,7 @@ If you encounter an unrecoverable error, context overflow, or are about to exit
|
|
|
601
604
|
- Build context-snapshot.md FIRST; all subagents read it instead of re-reading source files
|
|
602
605
|
- context-snapshot.md is append-only: orchestrator writes Sections 1-4, Dev appends Implementation Log, Reviewer appends Review Notes
|
|
603
606
|
- Gate checks enforce Implementation Log and Review Notes are written before proceeding
|
|
604
|
-
- Do NOT use `run_in_background=true` when spawning subagents
|
|
607
|
+
- Do NOT use `run_in_background=true` when spawning normal work subagents; the log-size monitor is the only allowed persistent background subagent
|
|
605
608
|
- **NEVER delete, modify, or touch any file under `.prizmkit/state/`** — those are pipeline runtime files managed by the runner
|
|
606
609
|
- NEVER run `rm -rf`, `git clean`, or any destructive filesystem operations
|
|
607
610
|
- `/prizmkit-committer` is mandatory, and must not be replaced with manual git commit commands
|