prizmkit 1.1.95 → 1.1.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/run-bugfix.sh +5 -1
  3. package/bundled/dev-pipeline/run-feature.sh +5 -1
  4. package/bundled/dev-pipeline/run-refactor.sh +5 -1
  5. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +98 -7
  6. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +86 -3
  7. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +86 -3
  8. package/bundled/dev-pipeline/scripts/monitor-log.sh +104 -0
  9. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +4 -1
  10. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +5 -2
  11. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +5 -2
  12. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +3 -0
  13. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +3 -2
  14. package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +76 -0
  15. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +2 -0
  16. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +98 -7
  17. package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +86 -3
  18. package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +86 -3
  19. package/bundled/dev-pipeline-windows/scripts/monitor-log.ps1 +102 -0
  20. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +4 -1
  21. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +5 -2
  22. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +5 -2
  23. package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +3 -0
  24. package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +3 -2
  25. package/bundled/dev-pipeline-windows/templates/sections/log-size-awareness.md +78 -0
  26. package/bundled/rules/general/agent-operational-rules.md +0 -38
  27. package/bundled/rules/general/cohesive-modeling.md +31 -18
  28. package/bundled/skills/_metadata.json +1 -1
  29. package/bundled/skills/app-planner/SKILL.md +2 -2
  30. package/bundled/skills/app-planner/references/project-state-detection.md +2 -2
  31. package/bundled/skills/bug-planner/SKILL.md +1 -2
  32. package/bundled/skills/bug-planner/assets/bug-confirmation-template.md +3 -3
  33. package/bundled/skills/prizm-kit/SKILL.md +1 -1
  34. package/bundled/skills/prizmkit-deploy/SKILL.md +8 -8
  35. package/bundled/skills/prizmkit-deploy/references/database-setup.md +4 -4
  36. package/bundled/skills/prizmkit-deploy/references/deployment-modes.md +7 -7
  37. package/bundled/skills/prizmkit-deploy/references/dns-setup.md +7 -7
  38. package/bundled/skills/prizmkit-deploy/references/firewall-setup.md +5 -5
  39. package/bundled/skills/prizmkit-deploy/references/ssh-execution-flow.md +1 -1
  40. package/bundled/skills/prizmkit-deploy/references/ssl-setup.md +4 -4
  41. package/bundled/skills/prizmkit-prizm-docs/assets/prizm-docs-format.md +1 -1
  42. package/bundled/skills/prizmkit-test/SKILL.md +2 -2
  43. package/bundled/skills/recovery-workflow/evals/evals.json +3 -3
  44. package/bundled/skills-windows/bug-planner/SKILL.md +1 -2
  45. package/bundled/skills-windows/bug-planner/assets/bug-confirmation-template.md +3 -3
  46. package/bundled/skills-windows/prizm-kit/SKILL.md +1 -1
  47. package/bundled/skills-windows/prizmkit-deploy/SKILL.md +8 -8
  48. package/bundled/skills-windows/prizmkit-deploy/references/database-setup.md +4 -4
  49. package/bundled/skills-windows/prizmkit-deploy/references/deployment-modes.md +7 -7
  50. package/bundled/skills-windows/prizmkit-deploy/references/dns-setup.md +7 -7
  51. package/bundled/skills-windows/prizmkit-deploy/references/firewall-setup.md +5 -5
  52. package/bundled/skills-windows/prizmkit-deploy/references/ssh-execution-flow.md +1 -1
  53. package/bundled/skills-windows/prizmkit-deploy/references/ssl-setup.md +4 -4
  54. package/bundled/skills-windows/prizmkit-prizm-docs/assets/prizm-docs-format.md +1 -1
  55. package/bundled/skills-windows/prizmkit-test/SKILL.md +2 -2
  56. package/bundled/skills-windows/recovery-workflow/evals/evals.json +3 -3
  57. package/package.json +1 -1
  58. /package/bundled/skills/app-planner/{assets → references}/app-design-guide.md +0 -0
@@ -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). Do NOT spawn agents in background and exit — that kills the session.
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. 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
 
@@ -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
  ```
@@ -635,7 +638,7 @@ If you encounter an unrecoverable error, context overflow, or are about to exit
635
638
  - Tier 3: full team — Dev (implementation) → Reviewer (review) — spawn agents directly via Agent tool
636
639
  - context-snapshot.md is append-only: orchestrator writes Sections 1-4, Dev appends Implementation Log, Reviewer appends Review Notes
637
640
  - Gate checks enforce Implementation Log and Review Notes are written before proceeding
638
- - Do NOT use `run_in_background=true` when spawning agents
641
+ - Do NOT use `run_in_background=true` when spawning normal work agents; the log-size monitor is the only allowed persistent background subagent
639
642
  - Commit phase must use `/prizmkit-committer`; do NOT replace with manual git commit commands
640
643
  - **NEVER delete, modify, or touch any file under `.prizmkit/state/`** — those are pipeline runtime files managed by the runner
641
644
  - NEVER run `rm -rf`, `git clean`, or any destructive filesystem operations
@@ -13,6 +13,8 @@ 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**: The log-size monitor is the only allowed persistent background subagent. Any normal work subagent must be awaited with `run_in_background=false`.
17
+
16
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.
17
19
 
18
20
  ### Bug Description
@@ -51,6 +53,7 @@ You are the **bug fix session agent**. Fix Bug {{BUG_ID}}: "{{BUG_TITLE}}".
51
53
  4. **Minimize tool output** — Capture to temp file, scan head/tail, filter with grep/sed/awk. Never load full output.
52
54
  5. **No intermediate commits** — All changes committed once at the end via `/prizmkit-committer`.
53
55
 
56
+ {{LOG_SIZE_AWARENESS}}
54
57
  ## Bug Fix Artifacts Directory
55
58
 
56
59
  ```
@@ -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 subagents, you MUST **wait for each to finish** (run_in_background=false) before proceeding. Do NOT spawn an agent in the background and exit — that kills the session.
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. The log-size monitor is the only allowed persistent background subagent. 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
 
@@ -78,6 +78,7 @@ You are the **refactor session orchestrator**. Execute Refactor {{REFACTOR_ID}}:
78
78
 
79
79
  ## Execution Instructions
80
80
 
81
+ {{LOG_SIZE_AWARENESS}}
81
82
  **YOU are the orchestrator. Execute each phase by spawning the appropriate team agent with run_in_background=false.**
82
83
 
83
84
  **Agent spawn failure policy (all Agent tool calls)**:
@@ -336,4 +337,4 @@ Write to: `{{SESSION_STATUS_PATH}}`
336
337
  - **Commit with** `refactor(<scope>):` prefix, NOT `feat:` or `fix:`
337
338
  - **Run full retrospective** (Job 1 + Job 2) — refactoring changes code structure
338
339
  - ALWAYS write session-status.json before exiting
339
- - Do NOT use `run_in_background=true` when spawning agents
340
+ - Do NOT use `run_in_background=true` when spawning normal work agents; the log-size monitor is the only allowed persistent background subagent
@@ -0,0 +1,76 @@
1
+ ## Session Log Budget Monitoring (PROACTIVE)
2
+
3
+ Your session log grows with every tool call and subagent spawn.
4
+ To prevent context exhaustion, **spawn a background monitoring subagent at session start**.
5
+
6
+ ### How to Set Up the Monitor
7
+
8
+ 1. **AS THE VERY FIRST ACTION** (before Phase 0), spawn a monitoring subagent
9
+ with `run_in_background: true` and this task:
10
+
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
+ ```
15
+
16
+ The subagent MUST use `run_in_background: true`. It writes a JSON status
17
+ file every {{LOG_MONITOR_POLL_INTERVAL}} seconds.
18
+
19
+ 2. **BEFORE EACH MAJOR PHASE** (Phase 0, 1, 2, ...), read the status:
20
+
21
+ ```bash
22
+ cat {{LOG_MONITOR_STATUS_FILE}} 2>/dev/null || echo '{"status":"OK"}'
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
64
+
65
+ ### Log Growth Reduction Tips
66
+
67
+ - **Re-read context-snapshot.md instead of source files** — already in context
68
+ - **Filter command output** — pipe to `tail -20` or `grep`, never full output
69
+ - **Limit subagent prompts** — shorter, focused prompts = shorter transcripts
70
+ - **Checkpoint often** — update durable workflow checkpoint artifacts before `/compact`
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
@@ -50,6 +50,8 @@ function Invoke-PrizmPipeline {
50
50
  Write-Host ""
51
51
  Write-Host "Environment Variables:"
52
52
  Write-Host " PRIZMKIT_EFFORT AI reasoning effort (low|medium|high|xhigh|max; max is Claude Code only)"
53
+ Write-Host " MAX_LOG_SIZE Session log size threshold for compact warning (default: 2097152 = 2MB, set 0 to disable)"
54
+ Write-Host " LOG_MONITOR_POLL_INTERVAL Log monitor poll interval in seconds (default: 30)"
53
55
  $global:PRIZM_EXIT_CODE = 0
54
56
  return
55
57
  }
@@ -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
 
@@ -157,6 +159,54 @@ def compute_feature_slug(feature_id, title):
157
159
  return "{}-{}".format(numeric, slug)
158
160
 
159
161
 
162
+ def _format_bytes(n):
163
+ """Format a byte count to a human-readable string."""
164
+ if n >= 1048576:
165
+ return "{}MB".format(n // 1048576)
166
+ elif n >= 1024:
167
+ return "{}KB".format(n // 1024)
168
+ return "{}B".format(n)
169
+
170
+
171
+ def _parse_non_negative_int_env(name, default):
172
+ """Parse a non-negative integer environment variable."""
173
+ value = os.environ.get(name, str(default))
174
+ try:
175
+ parsed = int(value)
176
+ except ValueError:
177
+ emit_failure("{} must be a non-negative integer".format(name))
178
+ if parsed < 0:
179
+ emit_failure("{} must be a non-negative integer".format(name))
180
+ return parsed
181
+
182
+
183
+ def _parse_positive_int_env(name, default):
184
+ """Parse a positive integer environment variable."""
185
+ value = os.environ.get(name, str(default))
186
+ try:
187
+ parsed = int(value)
188
+ except ValueError:
189
+ emit_failure("{} must be a positive integer".format(name))
190
+ if parsed <= 0:
191
+ emit_failure("{} must be a positive integer".format(name))
192
+ return parsed
193
+
194
+
195
+
196
+ def load_log_size_section(script_dir):
197
+ """Load the optional log-size awareness prompt section."""
198
+ max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
199
+ if max_log_size == 0:
200
+ return ""
201
+
202
+ section_path = os.path.join(
203
+ script_dir, "..", "templates", "sections", "log-size-awareness.md",
204
+ )
205
+ content, err = read_text_file(section_path)
206
+ if err:
207
+ return ""
208
+ return content
209
+
160
210
  def format_acceptance_criteria(criteria):
161
211
  """Format acceptance criteria as a markdown bullet list."""
162
212
  if not criteria:
@@ -1310,7 +1360,8 @@ def _tier_header(pipeline_mode):
1310
1360
  "lite": (
1311
1361
  "# Dev-Pipeline Session Bootstrap — Tier 1 (Single Agent)\n",
1312
1362
  "**Tier 1 — Single Agent**: You handle everything directly. "
1313
- "No subagents, no TeamCreate.\n",
1363
+ "No normal work subagents; the log-size monitor is the only "
1364
+ "allowed persistent background subagent. No TeamCreate.\n",
1314
1365
  ),
1315
1366
  "standard": (
1316
1367
  "# Dev-Pipeline Session Bootstrap — Tier 2 (Dual Agent)\n",
@@ -1347,7 +1398,9 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
1347
1398
 
1348
1399
  if pipeline_mode == "lite":
1349
1400
  specific = [
1350
- "- Tier 1: you handle everything directly — no subagents needed",
1401
+ "- Tier 1: you handle everything directly — no normal work "
1402
+ "subagents needed; the log-size monitor is the only allowed "
1403
+ "persistent background subagent",
1351
1404
  ]
1352
1405
  elif pipeline_mode == "standard":
1353
1406
  specific = [
@@ -1359,7 +1412,9 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
1359
1412
  "appends Review Notes",
1360
1413
  "- Gate checks enforce Implementation Log and Review Notes are "
1361
1414
  "written before proceeding",
1362
- "- Do NOT use `run_in_background=true` when spawning subagents",
1415
+ "- Do NOT use `run_in_background=true` when spawning normal work "
1416
+ "subagents; the log-size monitor is the only allowed persistent "
1417
+ "background subagent",
1363
1418
  "- On timeout: check snapshot + git diff HEAD → model:lite → "
1364
1419
  "remaining steps only → max 2 retries per phase → "
1365
1420
  "orchestrator fallback",
@@ -1373,7 +1428,9 @@ def _tier_reminders(pipeline_mode, critic_enabled=False):
1373
1428
  "appends Review Notes",
1374
1429
  "- Gate checks enforce Implementation Log and Review Notes are "
1375
1430
  "written before proceeding",
1376
- "- Do NOT use `run_in_background=true` when spawning agents",
1431
+ "- Do NOT use `run_in_background=true` when spawning normal work "
1432
+ "agents; the log-size monitor is the only allowed persistent "
1433
+ "background subagent",
1377
1434
  "- On timeout: check snapshot → model:lite → remaining steps "
1378
1435
  "only → max 2 retries → orchestrator fallback",
1379
1436
  ]
@@ -1412,12 +1469,13 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
1412
1469
  )
1413
1470
  if pipeline_mode != "lite":
1414
1471
  mission += (
1415
- " When you spawn subagents, wait for each to finish "
1416
- "(run_in_background=false)."
1472
+ " When you spawn normal work subagents, wait for each to finish "
1473
+ "(run_in_background=false). The log-size monitor is the only "
1474
+ "allowed persistent background subagent."
1417
1475
  )
1418
1476
  if pipeline_mode == "full":
1419
1477
  mission += (
1420
- " Do NOT spawn agents in background and exit — "
1478
+ " Do NOT spawn work agents in background and exit — "
1421
1479
  "that kills the session."
1422
1480
  )
1423
1481
  mission += "\n\n" + tier_desc
@@ -1435,6 +1493,12 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
1435
1493
  sections.append(("context-budget-rules",
1436
1494
  load_section(sections_dir, "context-budget-rules.md")))
1437
1495
 
1496
+ # --- Log Size Awareness (only disabled when MAX_LOG_SIZE is explicitly 0) ---
1497
+ max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
1498
+ if max_log_size != 0 and os.path.isfile(os.path.join(sections_dir, "log-size-awareness.md")):
1499
+ sections.append(("log-size-awareness",
1500
+ load_section(sections_dir, "log-size-awareness.md")))
1501
+
1438
1502
  # --- Directory Convention (tier-specific) ---
1439
1503
  if pipeline_mode == "lite":
1440
1504
  dc_file = "directory-convention-lite.md"
@@ -1907,7 +1971,16 @@ def build_replacements(args, feature, features, global_context, script_dir):
1907
1971
  pass # Keep default 3000 on any error
1908
1972
  dev_url = f"http://localhost:{dev_port}"
1909
1973
 
1974
+ max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
1975
+ if max_log_size == 0:
1976
+ poll_interval = DEFAULT_LOG_MONITOR_POLL_INTERVAL
1977
+ else:
1978
+ poll_interval = _parse_positive_int_env(
1979
+ "LOG_MONITOR_POLL_INTERVAL", DEFAULT_LOG_MONITOR_POLL_INTERVAL,
1980
+ )
1981
+
1910
1982
  replacements = {
1983
+ "{{LOG_SIZE_AWARENESS}}": load_log_size_section(script_dir),
1911
1984
  "{{RUN_ID}}": args.run_id,
1912
1985
  "{{SESSION_ID}}": args.session_id,
1913
1986
  "{{FEATURE_ID}}": args.feature_id,
@@ -1953,6 +2026,24 @@ def build_replacements(args, feature, features, global_context, script_dir):
1953
2026
  "{{COVERAGE_TARGET}}": coverage_target,
1954
2027
  "{{DEV_PORT}}": dev_port,
1955
2028
  "{{DEV_URL}}": dev_url,
2029
+ "{{MAX_LOG_SIZE}}": str(max_log_size),
2030
+ "{{MAX_LOG_SIZE_HUMAN}}": _format_bytes(max_log_size),
2031
+ "{{LOG_MONITOR_POLL_INTERVAL}}": str(poll_interval),
2032
+ "{{LOG_MONITOR_STATUS_FILE}}": os.path.join(
2033
+ ".prizmkit", "state", "features", args.feature_id,
2034
+ "sessions", args.session_id, "logs", "log-monitor-status.txt",
2035
+ ),
2036
+ "{{LOG_MONITOR_BASELINE_FILE}}": os.path.join(
2037
+ ".prizmkit", "state", "features", args.feature_id,
2038
+ "sessions", args.session_id, "logs", "log-monitor-baseline.txt",
2039
+ ),
2040
+ "{{SESSION_LOG_PATH}}": os.path.join(
2041
+ ".prizmkit", "state", "features", args.feature_id,
2042
+ "sessions", args.session_id, "logs", "session.log",
2043
+ ),
2044
+ "{{LOG_MONITOR_SCRIPT}}": os.path.join(
2045
+ ".prizmkit", "dev-pipeline", "scripts", "monitor-log.ps1",
2046
+ ),
1956
2047
  }
1957
2048
 
1958
2049
  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
 
@@ -61,6 +63,57 @@ def read_text_file(path):
61
63
  return None, "Cannot read file: {}".format(str(e))
62
64
 
63
65
 
66
+
67
+ def _format_bytes(n):
68
+ """Format a byte count to a human-readable string."""
69
+ if n >= 1048576:
70
+ return "{}MB".format(n // 1048576)
71
+ elif n >= 1024:
72
+ return "{}KB".format(n // 1024)
73
+ return "{}B".format(n)
74
+
75
+
76
+ def _parse_non_negative_int_env(name, default):
77
+ """Parse a non-negative integer environment variable."""
78
+ value = os.environ.get(name, str(default))
79
+ try:
80
+ parsed = int(value)
81
+ except ValueError:
82
+ raise ValueError("{} must be a non-negative integer".format(name))
83
+ if parsed < 0:
84
+ raise ValueError("{} must be a non-negative integer".format(name))
85
+ return parsed
86
+
87
+
88
+ def _parse_positive_int_env(name, default):
89
+ """Parse a positive integer environment variable."""
90
+ value = os.environ.get(name, str(default))
91
+ try:
92
+ parsed = int(value)
93
+ except ValueError:
94
+ raise ValueError("{} must be a positive integer".format(name))
95
+ if parsed <= 0:
96
+ raise ValueError("{} must be a positive integer".format(name))
97
+ return parsed
98
+
99
+
100
+ def load_log_size_section(script_dir):
101
+ """Load the optional log-size awareness prompt section."""
102
+ max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
103
+ if max_log_size == 0:
104
+ return ""
105
+
106
+ section_path = os.path.join(
107
+ script_dir, "..", "templates", "sections", "log-size-awareness.md",
108
+ )
109
+ if not os.path.isfile(section_path):
110
+ return ""
111
+
112
+ content, err = read_text_file(section_path)
113
+ if err:
114
+ return ""
115
+ return content
116
+
64
117
  def find_bug(bugs, bug_id):
65
118
  """Find and return the bug dict matching the given ID."""
66
119
  for bug in bugs:
@@ -305,7 +358,34 @@ def build_replacements(args, bug, global_context, script_dir):
305
358
  browser_enabled = True
306
359
  browser_verify_steps = " # (reproduce bug and verify fix)"
307
360
 
361
+ max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
362
+ if max_log_size == 0:
363
+ poll_interval = DEFAULT_LOG_MONITOR_POLL_INTERVAL
364
+ else:
365
+ poll_interval = _parse_positive_int_env(
366
+ "LOG_MONITOR_POLL_INTERVAL", DEFAULT_LOG_MONITOR_POLL_INTERVAL,
367
+ )
368
+
308
369
  replacements = {
370
+ "{{LOG_SIZE_AWARENESS}}": load_log_size_section(script_dir),
371
+ "{{MAX_LOG_SIZE}}": str(max_log_size),
372
+ "{{MAX_LOG_SIZE_HUMAN}}": _format_bytes(max_log_size),
373
+ "{{LOG_MONITOR_POLL_INTERVAL}}": str(poll_interval),
374
+ "{{LOG_MONITOR_STATUS_FILE}}": os.path.join(
375
+ ".prizmkit", "state", "bugfix", "bugs", args.bug_id,
376
+ "sessions", args.session_id, "logs", "log-monitor-status.txt",
377
+ ),
378
+ "{{LOG_MONITOR_BASELINE_FILE}}": os.path.join(
379
+ ".prizmkit", "state", "bugfix", "bugs", args.bug_id,
380
+ "sessions", args.session_id, "logs", "log-monitor-baseline.txt",
381
+ ),
382
+ "{{SESSION_LOG_PATH}}": os.path.join(
383
+ ".prizmkit", "state", "bugfix", "bugs", args.bug_id,
384
+ "sessions", args.session_id, "logs", "session.log",
385
+ ),
386
+ "{{LOG_MONITOR_SCRIPT}}": os.path.join(
387
+ ".prizmkit", "dev-pipeline", "scripts", "monitor-log.ps1",
388
+ ),
309
389
  "{{RUN_ID}}": args.run_id,
310
390
  "{{SESSION_ID}}": args.session_id,
311
391
  "{{BUG_ID}}": args.bug_id,
@@ -422,9 +502,10 @@ def render_template(template_content, replacements, bug):
422
502
  # Step 1: Process conditional blocks
423
503
  content = process_conditional_blocks(template_content, bug)
424
504
 
425
- # Step 2: Replace all {{PLACEHOLDER}} variables
426
- for placeholder, value in replacements.items():
427
- content = content.replace(placeholder, value)
505
+ # Step 2: Replace all {{PLACEHOLDER}} variables (two passes for nested sections)
506
+ for _pass in range(2):
507
+ for placeholder, value in replacements.items():
508
+ content = content.replace(placeholder, value)
428
509
 
429
510
  return content
430
511
 
@@ -678,6 +759,8 @@ if __name__ == "__main__":
678
759
  main()
679
760
  except KeyboardInterrupt:
680
761
  emit_failure("generate-bugfix-prompt interrupted")
762
+ except ValueError as exc:
763
+ emit_failure(str(exc))
681
764
  except SystemExit:
682
765
  raise
683
766
  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
 
@@ -152,6 +154,57 @@ def read_text_file(path):
152
154
  return None, "Cannot read file: {}".format(str(e))
153
155
 
154
156
 
157
+
158
+ def _format_bytes(n):
159
+ """Format a byte count to a human-readable string."""
160
+ if n >= 1048576:
161
+ return "{}MB".format(n // 1048576)
162
+ elif n >= 1024:
163
+ return "{}KB".format(n // 1024)
164
+ return "{}B".format(n)
165
+
166
+
167
+ def _parse_non_negative_int_env(name, default):
168
+ """Parse a non-negative integer environment variable."""
169
+ value = os.environ.get(name, str(default))
170
+ try:
171
+ parsed = int(value)
172
+ except ValueError:
173
+ raise ValueError("{} must be a non-negative integer".format(name))
174
+ if parsed < 0:
175
+ raise ValueError("{} must be a non-negative integer".format(name))
176
+ return parsed
177
+
178
+
179
+ def _parse_positive_int_env(name, default):
180
+ """Parse a positive integer environment variable."""
181
+ value = os.environ.get(name, str(default))
182
+ try:
183
+ parsed = int(value)
184
+ except ValueError:
185
+ raise ValueError("{} must be a positive integer".format(name))
186
+ if parsed <= 0:
187
+ raise ValueError("{} must be a positive integer".format(name))
188
+ return parsed
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
+ if not os.path.isfile(section_path):
201
+ return ""
202
+
203
+ content, err = read_text_file(section_path)
204
+ if err:
205
+ return ""
206
+ return content
207
+
155
208
  def find_refactor(refactors, refactor_id):
156
209
  """Find and return the refactor dict matching the given ID."""
157
210
  for refactor in refactors:
@@ -465,7 +518,34 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
465
518
  browser_enabled = True
466
519
  browser_verify_steps = " # (validate UI renders correctly and feature still works)"
467
520
 
521
+ max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
522
+ if max_log_size == 0:
523
+ poll_interval = DEFAULT_LOG_MONITOR_POLL_INTERVAL
524
+ else:
525
+ poll_interval = _parse_positive_int_env(
526
+ "LOG_MONITOR_POLL_INTERVAL", DEFAULT_LOG_MONITOR_POLL_INTERVAL,
527
+ )
528
+
468
529
  replacements = {
530
+ "{{LOG_SIZE_AWARENESS}}": load_log_size_section(script_dir),
531
+ "{{MAX_LOG_SIZE}}": str(max_log_size),
532
+ "{{MAX_LOG_SIZE_HUMAN}}": _format_bytes(max_log_size),
533
+ "{{LOG_MONITOR_POLL_INTERVAL}}": str(poll_interval),
534
+ "{{LOG_MONITOR_STATUS_FILE}}": os.path.join(
535
+ ".prizmkit", "state", "refactor", "refactors", args.refactor_id,
536
+ "sessions", args.session_id, "logs", "log-monitor-status.txt",
537
+ ),
538
+ "{{LOG_MONITOR_BASELINE_FILE}}": os.path.join(
539
+ ".prizmkit", "state", "refactor", "refactors", args.refactor_id,
540
+ "sessions", args.session_id, "logs", "log-monitor-baseline.txt",
541
+ ),
542
+ "{{SESSION_LOG_PATH}}": os.path.join(
543
+ ".prizmkit", "state", "refactor", "refactors", args.refactor_id,
544
+ "sessions", args.session_id, "logs", "session.log",
545
+ ),
546
+ "{{LOG_MONITOR_SCRIPT}}": os.path.join(
547
+ ".prizmkit", "dev-pipeline", "scripts", "monitor-log.ps1",
548
+ ),
469
549
  "{{RUN_ID}}": args.run_id,
470
550
  "{{SESSION_ID}}": args.session_id,
471
551
  "{{REFACTOR_ID}}": args.refactor_id,
@@ -589,9 +669,10 @@ def render_template(template_content, replacements, resume_phase, refactor):
589
669
  # Step 1: Process conditional blocks
590
670
  content = process_conditional_blocks(template_content, resume_phase, refactor)
591
671
 
592
- # Step 2: Replace all {{PLACEHOLDER}} variables
593
- for placeholder, value in replacements.items():
594
- content = content.replace(placeholder, value)
672
+ # Step 2: Replace all {{PLACEHOLDER}} variables (two passes for nested sections)
673
+ for _pass in range(2):
674
+ for placeholder, value in replacements.items():
675
+ content = content.replace(placeholder, value)
595
676
 
596
677
  return content
597
678
 
@@ -756,6 +837,8 @@ if __name__ == "__main__":
756
837
  main()
757
838
  except KeyboardInterrupt:
758
839
  emit_failure("generate-refactor-prompt interrupted")
840
+ except ValueError as exc:
841
+ emit_failure(str(exc))
759
842
  except SystemExit:
760
843
  raise
761
844
  except Exception as exc: