prizmkit 1.1.62 → 1.1.64

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "frameworkVersion": "1.1.62",
3
- "bundledAt": "2026-06-08T16:36:59.712Z",
4
- "bundledFrom": "280ca81"
2
+ "frameworkVersion": "1.1.64",
3
+ "bundledAt": "2026-06-08T19:11:15.565Z",
4
+ "bundledFrom": "f45ecfb"
5
5
  }
@@ -20,6 +20,7 @@ project_doc_fallback_filenames = ["CLAUDE.md", "CODEBUDDY.md"]
20
20
 
21
21
  [agents]
22
22
  max_depth = 1
23
+ job_max_runtime_seconds = 840
23
24
  `;
24
25
 
25
26
  await writeFile(configPath, configToml);
@@ -369,7 +369,23 @@ prizm_start_ai_session() {
369
369
  "$CLI_CMD" "${claude_args[@]}" > "$log_path" 2>&1 &
370
370
  ;;
371
371
  codex)
372
- local codex_args=(--ask-for-approval never --sandbox danger-full-access exec --cd "$PROJECT_ROOT" --skip-git-repo-check)
372
+ local codex_args=(--ask-for-approval never --sandbox danger-full-access)
373
+ local codex_subagent_timeout="${CODEX_SUBAGENT_TIMEOUT_SECONDS:-}"
374
+ if [[ -z "$codex_subagent_timeout" ]]; then
375
+ local outer_stale_threshold="${STALE_KILL_THRESHOLD:-900}"
376
+ if [[ "$outer_stale_threshold" =~ ^[0-9]+$ && "$outer_stale_threshold" -gt 120 ]]; then
377
+ codex_subagent_timeout=$((outer_stale_threshold - 60))
378
+ else
379
+ codex_subagent_timeout=840
380
+ fi
381
+ fi
382
+ if [[ "$codex_subagent_timeout" =~ ^[0-9]+$ && "$codex_subagent_timeout" -gt 0 ]]; then
383
+ codex_args+=(--config "agents.job_max_runtime_seconds=$codex_subagent_timeout")
384
+ fi
385
+ codex_args+=(exec --cd "$PROJECT_ROOT" --skip-git-repo-check)
386
+ if [[ "$USE_STREAM_JSON" == "true" ]]; then
387
+ codex_args+=(--json)
388
+ fi
373
389
  if [[ -n "$model" ]]; then
374
390
  codex_args+=(--model "$model")
375
391
  fi
@@ -413,7 +429,23 @@ prizm_run_ai_session() {
413
429
  "$CLI_CMD" "${claude_args[@]}" > "$log_path" 2>&1
414
430
  ;;
415
431
  codex)
416
- local codex_args=(--ask-for-approval never --sandbox danger-full-access exec --cd "$PROJECT_ROOT" --skip-git-repo-check)
432
+ local codex_args=(--ask-for-approval never --sandbox danger-full-access)
433
+ local codex_subagent_timeout="${CODEX_SUBAGENT_TIMEOUT_SECONDS:-}"
434
+ if [[ -z "$codex_subagent_timeout" ]]; then
435
+ local outer_stale_threshold="${STALE_KILL_THRESHOLD:-900}"
436
+ if [[ "$outer_stale_threshold" =~ ^[0-9]+$ && "$outer_stale_threshold" -gt 120 ]]; then
437
+ codex_subagent_timeout=$((outer_stale_threshold - 60))
438
+ else
439
+ codex_subagent_timeout=840
440
+ fi
441
+ fi
442
+ if [[ "$codex_subagent_timeout" =~ ^[0-9]+$ && "$codex_subagent_timeout" -gt 0 ]]; then
443
+ codex_args+=(--config "agents.job_max_runtime_seconds=$codex_subagent_timeout")
444
+ fi
445
+ codex_args+=(exec --cd "$PROJECT_ROOT" --skip-git-repo-check)
446
+ if [[ "$USE_STREAM_JSON" == "true" ]]; then
447
+ codex_args+=(--json)
448
+ fi
417
449
  if [[ -n "$model" ]]; then
418
450
  codex_args+=(--model "$model")
419
451
  fi
@@ -441,10 +473,12 @@ prizm_detect_subagents() {
441
473
  [[ -f "$session_log" ]] || return 0
442
474
 
443
475
  local count=0
444
- if [[ "$USE_STREAM_JSON" == "true" ]]; then
476
+ if [[ "$USE_STREAM_JSON" == "true" && "${STREAM_JSON_FORMAT:-}" == "codex-json" ]]; then
477
+ count=$(grep -c '"tool"[[:space:]]*:[[:space:]]*"spawn_agent"' "$session_log" 2>/dev/null) || true
478
+ elif [[ "$USE_STREAM_JSON" == "true" ]]; then
445
479
  count=$(grep -c '"name"[[:space:]]*:[[:space:]]*"Agent"' "$session_log" 2>/dev/null) || true
446
480
  else
447
- count=$(grep -cE '(Tool: Agent|"tool":\s*"Agent"|tool_use.*Agent|subagent_type)' "$session_log" 2>/dev/null) || true
481
+ count=$(grep -cE '(Tool: Agent|"tool":\s*"Agent"|"tool":\s*"spawn_agent"|tool_use.*Agent|subagent_type|collab: SpawnAgent)' "$session_log" 2>/dev/null) || true
448
482
  fi
449
483
 
450
484
  count=${count:-0}
@@ -88,17 +88,22 @@ start_heartbeat() {
88
88
  local stale_mins=$((stale_seconds / 60))
89
89
  echo -e " ${RED}[HEARTBEAT]${NC} ${mins}m${secs}s | log: ${size_display} | ${RED}STALE-KILL: no progress for ${stale_mins}m (threshold: ${stale_kill_threshold}s)${NC}"
90
90
  echo -e " ${RED}[HEARTBEAT]${NC} Killing AI CLI process $cli_pid (stale session)..."
91
+ # Write the marker before killing. Some CLIs exit quickly, and the
92
+ # parent runner may stop this heartbeat process immediately after
93
+ # wait(1) returns.
94
+ local _marker_dir
95
+ _marker_dir="$(dirname "$session_log")"
96
+ echo "{\"killed_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\", \"reason\": \"stale_session\", \"stale_seconds\": $stale_seconds, \"threshold\": $stale_kill_threshold}" > "$_marker_dir/stale-kill.json" 2>/dev/null || true
91
97
  kill -TERM "$cli_pid" 2>/dev/null || true
92
98
  # Give process 10s to exit gracefully, then force kill
93
- sleep 10
99
+ local stale_kill_grace_seconds="${STALE_KILL_GRACE_SECONDS:-10}"
100
+ if [[ $stale_kill_grace_seconds -gt 0 ]]; then
101
+ sleep "$stale_kill_grace_seconds"
102
+ fi
94
103
  if kill -0 "$cli_pid" 2>/dev/null; then
95
104
  echo -e " ${RED}[HEARTBEAT]${NC} Process still alive after SIGTERM, sending SIGKILL..."
96
105
  kill -9 "$cli_pid" 2>/dev/null || true
97
106
  fi
98
- # Write stale-kill marker so spawn_and_wait_session knows this wasn't a crash
99
- local _marker_dir
100
- _marker_dir="$(dirname "$session_log")"
101
- echo "{\"killed_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\", \"reason\": \"stale_session\", \"stale_seconds\": $stale_seconds, \"threshold\": $stale_kill_threshold}" > "$_marker_dir/stale-kill.json" 2>/dev/null || true
102
107
  break
103
108
  fi
104
109
 
@@ -200,7 +205,7 @@ stop_progress_parser() {
200
205
  fi
201
206
  }
202
207
 
203
- # Detect whether the AI CLI supports --output-format stream-json.
208
+ # Detect whether the AI CLI supports structured JSON progress.
204
209
  # Sets USE_STREAM_JSON to "true" or "false".
205
210
  #
206
211
  # Arguments:
@@ -208,10 +213,35 @@ stop_progress_parser() {
208
213
  detect_stream_json_support() {
209
214
  local cli_cmd="$1"
210
215
  USE_STREAM_JSON="false"
216
+ STREAM_JSON_FORMAT=""
217
+
218
+ local session_platform=""
219
+ session_platform="$(_prizm_known_platform_from_cli "$cli_cmd" 2>/dev/null || true)"
220
+ if [[ -z "$session_platform" ]]; then
221
+ case "$(_prizm_normalize_platform "${PRIZMKIT_PLATFORM:-${PLATFORM:-}}")" in
222
+ codex|claude|codebuddy)
223
+ session_platform="$(_prizm_normalize_platform "${PRIZMKIT_PLATFORM:-${PLATFORM:-}}")"
224
+ ;;
225
+ esac
226
+ fi
211
227
 
212
228
  # CodeBuddy (cbc) always supports stream-json
213
- if [[ "$cli_cmd" == "cbc" ]]; then
229
+ if [[ "$session_platform" == "codebuddy" || "$cli_cmd" == "cbc" ]]; then
214
230
  USE_STREAM_JSON="true"
231
+ STREAM_JSON_FORMAT="stream-json"
232
+ export USE_STREAM_JSON STREAM_JSON_FORMAT
233
+ return 0
234
+ fi
235
+
236
+ # Codex uses `codex exec --json` JSONL, not `--output-format stream-json`.
237
+ if [[ "$session_platform" == "codex" ]]; then
238
+ local codex_help
239
+ codex_help=$("$cli_cmd" exec --help 2>&1) || true
240
+ if echo "$codex_help" | grep -q -- "--json"; then
241
+ USE_STREAM_JSON="true"
242
+ STREAM_JSON_FORMAT="codex-json"
243
+ fi
244
+ export USE_STREAM_JSON STREAM_JSON_FORMAT
215
245
  return 0
216
246
  fi
217
247
 
@@ -222,5 +252,7 @@ detect_stream_json_support() {
222
252
 
223
253
  if echo "$help_output" | grep -q "stream-json"; then
224
254
  USE_STREAM_JSON="true"
255
+ STREAM_JSON_FORMAT="stream-json"
225
256
  fi
257
+ export USE_STREAM_JSON STREAM_JSON_FORMAT
226
258
  }
@@ -164,31 +164,8 @@ spawn_and_wait_session() {
164
164
  session_status="timed_out"
165
165
  elif [[ "$was_stale_killed" == true ]]; then
166
166
  log_warn "Session stale-killed (no progress for ${STALE_KILL_THRESHOLD}s)"
167
- local has_commits=""
168
- if git -C "$project_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
169
- has_commits=$(git -C "$project_root" log "${default_branch}..HEAD" --oneline 2>/dev/null | head -1)
170
- fi
171
- if [[ -n "$has_commits" ]]; then
172
- log_info "Stale-killed session has commits — treating as success"
173
- session_status="success"
174
- else
175
- local uncommitted=""
176
- uncommitted=$(git -C "$project_root" status --porcelain 2>/dev/null | head -1 || true)
177
- if [[ -n "$uncommitted" ]]; then
178
- log_warn "Stale-killed session has uncommitted changes — auto-committing..."
179
- git -C "$project_root" add -A 2>/dev/null || true
180
- if git -C "$project_root" commit --no-verify -m "chore($bug_id): auto-commit session work (stale-killed)" 2>/dev/null; then
181
- log_info "Auto-commit succeeded"
182
- session_status="success"
183
- else
184
- log_warn "Auto-commit failed — no changes to commit"
185
- session_status="crashed"
186
- fi
187
- else
188
- log_warn "Stale-killed session produced no commits and no changes"
189
- session_status="crashed"
190
- fi
191
- fi
167
+ log_warn "Stale-killed sessions are treated as failed; dev branch is preserved for inspection"
168
+ session_status="crashed"
192
169
  elif [[ $exit_code -ne 0 ]]; then
193
170
  log_warn "Session exited with code $exit_code"
194
171
  session_status="crashed"
@@ -174,32 +174,8 @@ spawn_and_wait_session() {
174
174
  session_status="timed_out"
175
175
  elif [[ "$was_stale_killed" == true ]]; then
176
176
  log_warn "Session stale-killed (no progress for ${STALE_KILL_THRESHOLD}s)"
177
- # Treat stale-killed as potentially successful check for commits
178
- local has_commits=""
179
- if git -C "$project_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
180
- has_commits=$(git -C "$project_root" log "${default_branch}..HEAD" --oneline 2>/dev/null | head -1)
181
- fi
182
- if [[ -n "$has_commits" ]]; then
183
- log_info "Stale-killed session has commits — treating as success"
184
- session_status="success"
185
- else
186
- local uncommitted=""
187
- uncommitted=$(git -C "$project_root" status --porcelain 2>/dev/null | head -1 || true)
188
- if [[ -n "$uncommitted" ]]; then
189
- log_warn "Stale-killed session has uncommitted changes — auto-committing..."
190
- git -C "$project_root" add -A 2>/dev/null || true
191
- if git -C "$project_root" commit --no-verify -m "chore($feature_id): auto-commit session work (stale-killed)" 2>/dev/null; then
192
- log_info "Auto-commit succeeded"
193
- session_status="success"
194
- else
195
- log_warn "Auto-commit failed — no changes to commit"
196
- session_status="crashed"
197
- fi
198
- else
199
- log_warn "Stale-killed session produced no commits and no changes"
200
- session_status="crashed"
201
- fi
202
- fi
177
+ log_warn "Stale-killed sessions are treated as failed; dev branch is preserved for inspection"
178
+ session_status="crashed"
203
179
  elif [[ $exit_code -ne 0 ]]; then
204
180
  log_warn "Session exited with code $exit_code"
205
181
  session_status="crashed"
@@ -166,31 +166,8 @@ spawn_and_wait_session() {
166
166
  session_status="timed_out"
167
167
  elif [[ "$was_stale_killed" == true ]]; then
168
168
  log_warn "Session stale-killed (no progress for ${STALE_KILL_THRESHOLD}s)"
169
- local has_commits=""
170
- if git -C "$project_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
171
- has_commits=$(git -C "$project_root" log "${default_branch}..HEAD" --oneline 2>/dev/null | head -1)
172
- fi
173
- if [[ -n "$has_commits" ]]; then
174
- log_info "Stale-killed session has commits — treating as success"
175
- session_status="success"
176
- else
177
- local uncommitted=""
178
- uncommitted=$(git -C "$project_root" status --porcelain 2>/dev/null | head -1 || true)
179
- if [[ -n "$uncommitted" ]]; then
180
- log_warn "Stale-killed session has uncommitted changes — auto-committing..."
181
- git -C "$project_root" add -A 2>/dev/null || true
182
- if git -C "$project_root" commit --no-verify -m "chore($refactor_id): auto-commit session work (stale-killed)" 2>/dev/null; then
183
- log_info "Auto-commit succeeded"
184
- session_status="success"
185
- else
186
- log_warn "Auto-commit failed — no changes to commit"
187
- session_status="crashed"
188
- fi
189
- else
190
- log_warn "Stale-killed session produced no commits and no changes"
191
- session_status="crashed"
192
- fi
193
- fi
169
+ log_warn "Stale-killed sessions are treated as failed; dev branch is preserved for inspection"
170
+ session_status="crashed"
194
171
  elif [[ $exit_code -ne 0 ]]; then
195
172
  log_warn "Session exited with code $exit_code"
196
173
  session_status="crashed"
@@ -73,6 +73,9 @@ class ProgressTracker:
73
73
  self.last_text_snippet = ""
74
74
  self.is_active = True
75
75
  self.errors = []
76
+ self.event_format = ""
77
+ self.active_subagent_count = 0
78
+ self.subagent_status_counts = Counter()
76
79
  self._text_buffer = ""
77
80
  self._in_tool_use = False
78
81
  self._current_tool_input_parts = []
@@ -87,8 +90,72 @@ class ProgressTracker:
87
90
  """
88
91
  event_type = event.get("type", "")
89
92
 
93
+ # ── Codex exec --json JSONL format ──────────────────────────
94
+ if event_type in (
95
+ "thread.started", "turn.started", "turn.completed",
96
+ "turn.failed", "item.started", "item.completed", "error",
97
+ ):
98
+ self.event_format = "codex-json"
99
+ self.is_active = True
100
+
101
+ if event_type == "turn.started":
102
+ self.message_count += 1
103
+
104
+ elif event_type in ("item.started", "item.completed"):
105
+ item = event.get("item", {})
106
+ item_type = item.get("type", "")
107
+
108
+ if item_type == "agent_message":
109
+ text = item.get("text", "")
110
+ if text.strip():
111
+ self.last_text_snippet = text.strip()[:120]
112
+ self._detect_phase(text)
113
+
114
+ elif item_type == "collab_tool_call":
115
+ tool_name = item.get("tool", "collab")
116
+ if event_type == "item.started":
117
+ self.current_tool = tool_name
118
+ self.tool_call_counts[tool_name] += 1
119
+ self.total_tool_calls += 1
120
+ elif item.get("status") == "completed":
121
+ self.current_tool = None
122
+ self._extract_tool_summary_from_dict(item)
123
+ self._update_subagent_status_counts(
124
+ item.get("agents_states", {})
125
+ )
126
+
127
+ prompt = item.get("prompt")
128
+ if prompt:
129
+ self._detect_phase(prompt)
130
+
131
+ else:
132
+ tool_name = item.get("tool") or item.get("name")
133
+ if tool_name:
134
+ if event_type == "item.started":
135
+ self.current_tool = tool_name
136
+ self.tool_call_counts[tool_name] += 1
137
+ self.total_tool_calls += 1
138
+ elif item.get("status") == "completed":
139
+ self.current_tool = None
140
+ self._extract_tool_summary_from_dict(item)
141
+
142
+ elif event_type == "turn.completed":
143
+ self.current_tool = None
144
+
145
+ elif event_type == "turn.failed":
146
+ error = event.get("error") or event.get("message") or "Codex turn failed"
147
+ self.errors.append(str(error))
148
+ self.current_tool = None
149
+
150
+ elif event_type == "error":
151
+ error = event.get("error") or event.get("message") or "Unknown error"
152
+ self.errors.append(str(error))
153
+
154
+ return
155
+
90
156
  # ── Claude Code verbose format ──────────────────────────────
91
157
  if event_type == "assistant":
158
+ self.event_format = self.event_format or "stream-json"
92
159
  self.message_count += 1
93
160
  self.is_active = True
94
161
  message = event.get("message", {})
@@ -113,16 +180,19 @@ class ProgressTracker:
113
180
 
114
181
  elif event_type == "tool_result" or event_type == "user":
115
182
  # tool_result contains output from tool execution
183
+ self.event_format = self.event_format or "stream-json"
116
184
  self.is_active = True
117
185
 
118
186
  elif event_type == "system":
119
187
  # System events (hooks, init, etc.) — track but don't count as messages
188
+ self.event_format = self.event_format or "stream-json"
120
189
  subtype = event.get("subtype", "")
121
190
  if subtype == "init":
122
191
  self.is_active = True
123
192
 
124
193
  # ── Claude API raw stream format ────────────────────────────
125
194
  elif event_type == "message_start":
195
+ self.event_format = self.event_format or "stream-json"
126
196
  self.message_count += 1
127
197
  self.is_active = True
128
198
 
@@ -256,14 +326,38 @@ class ProgressTracker:
256
326
  elif "prompt" in data:
257
327
  self.current_tool_input_summary = str(data["prompt"])[:100]
258
328
 
329
+ def _update_subagent_status_counts(self, agents_states):
330
+ """Track Codex subagent state counts from collab_tool_call items."""
331
+ counts = Counter()
332
+ active = 0
333
+ if isinstance(agents_states, dict):
334
+ for state in agents_states.values():
335
+ if not isinstance(state, dict):
336
+ continue
337
+ status = str(state.get("status", "unknown"))
338
+ counts[status] += 1
339
+ if status not in ("completed", "failed", "cancelled", "canceled"):
340
+ active += 1
341
+ message = state.get("message")
342
+ if message:
343
+ self.last_text_snippet = str(message).strip()[:120]
344
+ self._detect_phase(str(message))
345
+ self.subagent_status_counts = counts
346
+ self.active_subagent_count = active
347
+
259
348
  def to_dict(self):
260
349
  """Export current state as a dictionary for JSON serialization."""
261
350
  tool_calls = [
262
351
  {"name": name, "count": count}
263
352
  for name, count in self.tool_call_counts.most_common()
264
353
  ]
354
+ subagent_states = [
355
+ {"status": status, "count": count}
356
+ for status, count in self.subagent_status_counts.most_common()
357
+ ]
265
358
  return {
266
359
  "updated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
360
+ "event_format": self.event_format,
267
361
  "message_count": self.message_count,
268
362
  "current_tool": self.current_tool,
269
363
  "current_tool_input_summary": self.current_tool_input_summary,
@@ -271,6 +365,8 @@ class ProgressTracker:
271
365
  "detected_phases": self.detected_phases,
272
366
  "tool_calls": tool_calls,
273
367
  "total_tool_calls": self.total_tool_calls,
368
+ "active_subagent_count": self.active_subagent_count,
369
+ "subagent_states": subagent_states,
274
370
  "last_text_snippet": self.last_text_snippet,
275
371
  "is_active": self.is_active,
276
372
  "errors": self.errors[-10:], # Keep last 10 errors
@@ -21,6 +21,17 @@
21
21
  # MAX_RETRIES=3 # Max retry attempts per task before marking failed
22
22
  # SESSION_TIMEOUT=0 # Session timeout in seconds (0 = no limit)
23
23
  # VERBOSE=1 # Verbose logging (1=on, 0=off)
24
+ # HEARTBEAT_INTERVAL=30 # Poll interval for session progress/stale checks
25
+ # STALE_KILL_THRESHOLD=900 # Auto-kill session after N seconds without log progress (0 = disabled)
26
+ # STALE_KILL_GRACE_SECONDS=10 # Grace period after stale-kill before force-stopping the job
27
+ # CODEX_SUBAGENT_TIMEOUT_SECONDS=840 # Codex subagent max runtime; defaults to stale threshold - 60
28
+ # LOG_CLEANUP_ENABLED=1 # Run periodic session log cleanup
29
+ # LOG_RETENTION_DAYS=14 # Delete session logs older than N days
30
+ # LOG_MAX_TOTAL_MB=1024 # Keep total logs under N MB via oldest-first cleanup
31
+ # STOP_ON_FAILURE=0 # Stop after the first failed task (1=stop, 0=continue)
32
+ # ENABLE_DEPLOY=0 # Start a deploy session after all tasks complete successfully
33
+ # DEV_BRANCH= # Optional custom dev branch name for each task
34
+ # AUTO_PUSH=0 # Push original branch after successful merge (1=enabled)
24
35
 
25
36
  # ─── Feature Pipeline Only ────────────────────────────────────────────
26
37
  # ENABLE_CRITIC=false # Enable adversarial critic review (true/false)
@@ -345,6 +345,17 @@ pending, in_progress, completed, failed, skipped
345
345
  | `MAX_RETRIES` | integer | (not specified) | Retry attempts per task |
346
346
  | `SESSION_TIMEOUT` | integer | 0 | 0 = no limit |
347
347
  | `VERBOSE` | integer | (not specified) | 1=on, 0=off |
348
+ | `HEARTBEAT_INTERVAL` | integer | 30 | Poll interval for session progress/stale checks |
349
+ | `STALE_KILL_THRESHOLD` | integer | 900 | Auto-kill after N seconds without log progress; 0 disables |
350
+ | `STALE_KILL_GRACE_SECONDS` | integer | 10 | Grace period after stale-kill before force-stopping |
351
+ | `CODEX_SUBAGENT_TIMEOUT_SECONDS` | integer | 840 | Codex subagent max runtime |
352
+ | `LOG_CLEANUP_ENABLED` | boolean | 1 | Periodic session log cleanup |
353
+ | `LOG_RETENTION_DAYS` | integer | 14 | Delete session logs older than N days |
354
+ | `LOG_MAX_TOTAL_MB` | integer | 1024 | Keep total logs under N MB |
355
+ | `STOP_ON_FAILURE` | boolean | 0 | Stop after the first failed task |
356
+ | `ENABLE_DEPLOY` | boolean | 0 | Start deploy session after all tasks complete |
357
+ | `DEV_BRANCH` | string | auto-generated | Optional custom dev branch name |
358
+ | `AUTO_PUSH` | boolean | 0 | Push original branch after successful merge |
348
359
  | `ENABLE_CRITIC` | boolean | false | Adversarial review enable |
349
360
  | `PIPELINE_MODE` | string | auto-detect | lite/standard/full override |
350
361