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.
- 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/rules/general/agent-operational-rules.md +0 -38
- package/bundled/rules/general/cohesive-modeling.md +31 -18
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/app-planner/SKILL.md +2 -2
- package/bundled/skills/app-planner/references/project-state-detection.md +2 -2
- package/bundled/skills/bug-planner/SKILL.md +1 -2
- package/bundled/skills/bug-planner/assets/bug-confirmation-template.md +3 -3
- package/bundled/skills/prizm-kit/SKILL.md +1 -1
- package/bundled/skills/prizmkit-deploy/SKILL.md +8 -8
- package/bundled/skills/prizmkit-deploy/references/database-setup.md +4 -4
- package/bundled/skills/prizmkit-deploy/references/deployment-modes.md +7 -7
- package/bundled/skills/prizmkit-deploy/references/dns-setup.md +7 -7
- package/bundled/skills/prizmkit-deploy/references/firewall-setup.md +5 -5
- package/bundled/skills/prizmkit-deploy/references/ssh-execution-flow.md +1 -1
- package/bundled/skills/prizmkit-deploy/references/ssl-setup.md +4 -4
- package/bundled/skills/prizmkit-prizm-docs/assets/prizm-docs-format.md +1 -1
- package/bundled/skills/prizmkit-test/SKILL.md +2 -2
- package/bundled/skills/recovery-workflow/evals/evals.json +3 -3
- package/bundled/skills-windows/bug-planner/SKILL.md +1 -2
- package/bundled/skills-windows/bug-planner/assets/bug-confirmation-template.md +3 -3
- package/bundled/skills-windows/prizm-kit/SKILL.md +1 -1
- package/bundled/skills-windows/prizmkit-deploy/SKILL.md +8 -8
- package/bundled/skills-windows/prizmkit-deploy/references/database-setup.md +4 -4
- package/bundled/skills-windows/prizmkit-deploy/references/deployment-modes.md +7 -7
- package/bundled/skills-windows/prizmkit-deploy/references/dns-setup.md +7 -7
- package/bundled/skills-windows/prizmkit-deploy/references/firewall-setup.md +5 -5
- package/bundled/skills-windows/prizmkit-deploy/references/ssh-execution-flow.md +1 -1
- package/bundled/skills-windows/prizmkit-deploy/references/ssl-setup.md +4 -4
- package/bundled/skills-windows/prizmkit-prizm-docs/assets/prizm-docs-format.md +1 -1
- package/bundled/skills-windows/prizmkit-test/SKILL.md +2 -2
- package/bundled/skills-windows/recovery-workflow/evals/evals.json +3 -3
- package/package.json +1 -1
- /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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
427
|
-
|
|
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
|
|
594
|
-
|
|
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:
|