prizmkit 1.1.74 → 1.1.77

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 (29) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/.env.example +1 -0
  3. package/bundled/dev-pipeline/lib/common.sh +53 -0
  4. package/bundled/dev-pipeline/lib/heartbeat.sh +29 -6
  5. package/bundled/dev-pipeline/run-bugfix.sh +5 -0
  6. package/bundled/dev-pipeline/run-feature.sh +5 -0
  7. package/bundled/dev-pipeline/run-recovery.sh +6 -0
  8. package/bundled/dev-pipeline/run-refactor.sh +5 -0
  9. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +229 -7
  10. package/bundled/dev-pipeline-windows/lib/common.ps1 +23 -0
  11. package/bundled/dev-pipeline-windows/lib/heartbeat.sh +439 -0
  12. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +7 -0
  13. package/bundled/dev-pipeline-windows/run-recovery.ps1 +11 -0
  14. package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +229 -7
  15. package/bundled/skills/_metadata.json +1 -1
  16. package/bundled/skills/app-planner/SKILL.md +13 -10
  17. package/bundled/skills/app-planner/references/rules/backend/question-manifest.json +46 -0
  18. package/bundled/skills/app-planner/references/rules/database/question-manifest.json +39 -0
  19. package/bundled/skills/app-planner/references/rules/frontend/question-manifest.json +51 -0
  20. package/bundled/skills/app-planner/references/rules/mobile/question-manifest.json +47 -0
  21. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +1 -0
  22. package/bundled/skills/feature-pipeline-launcher/SKILL.md +1 -0
  23. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +1 -0
  24. package/bundled/skills-windows/app-planner/SKILL.md +13 -10
  25. package/bundled/skills-windows/app-planner/references/rules/backend/question-manifest.json +46 -0
  26. package/bundled/skills-windows/app-planner/references/rules/database/question-manifest.json +39 -0
  27. package/bundled/skills-windows/app-planner/references/rules/frontend/question-manifest.json +51 -0
  28. package/bundled/skills-windows/app-planner/references/rules/mobile/question-manifest.json +47 -0
  29. package/package.json +1 -1
@@ -137,7 +137,10 @@ class ProgressTracker:
137
137
  self.event_format = ""
138
138
  self.active_subagent_count = 0
139
139
  self.subagent_status_counts = Counter()
140
+ self._subagent_spawn_count = 0
140
141
  self.codex_child_thread_ids = set()
142
+ self.cb_session_id = ""
143
+ self.cb_cwd = ""
141
144
  self.claude_session_id = ""
142
145
  self.claude_cwd = ""
143
146
  self.claude_task_states = {}
@@ -147,6 +150,7 @@ class ProgressTracker:
147
150
  self.last_child_activity_at = ""
148
151
  self._codex_child_session_paths = {}
149
152
  self._claude_child_session_files = []
153
+ self._cb_child_session_files = []
150
154
  self._last_child_scan_at = 0.0
151
155
  self._last_claude_fallback_scan_at = 0.0
152
156
  self._last_claude_fallback_scan_key = ""
@@ -158,10 +162,13 @@ class ProgressTracker:
158
162
  def process_event(self, event):
159
163
  """Process a single stream-json event and update state.
160
164
 
161
- Supports two formats:
165
+ Supports three formats:
162
166
  1. Claude API raw stream: message_start, content_block_start, content_block_delta, etc.
163
167
  2. Claude Code verbose stream-json: assistant, user, tool_result, system, etc.
164
168
  (produced by claude/claude-internal --verbose --output-format stream-json)
169
+ 3. CodeBuddy stream-json: message/function_call/function_call_result events
170
+ (produced by cbc --print -y --output-format stream-json — message type
171
+ with sessionId/cwd metadata, function_call for tool invocations).
165
172
  """
166
173
  event_type = event.get("type", "")
167
174
 
@@ -233,6 +240,93 @@ class ProgressTracker:
233
240
 
234
241
  return
235
242
 
243
+ # ── CodeBuddy stream-json format ──────────────────────────────
244
+ # cbc --print -y --output-format stream-json emits:
245
+ # message (role=user/assistant), function_call, function_call_result,
246
+ # file-history-snapshot, error. The first user message carries
247
+ # sessionId and cwd metadata; later function_call items carry the
248
+ # same fields.
249
+ # NOTE: cbc emits tool invocations as EITHER tool_use blocks inside a
250
+ # message (role=assistant) event OR as standalone function_call events,
251
+ # but not both for the same invocation. The two handlers are
252
+ # complementary rather than redundant, so no deduplication is needed.
253
+ # Detect via event_type=="message" with a "sessionId" field.
254
+ # Must run before the Claude Code assistant / tool_result branches
255
+ # because those also accept those types but use different internals.
256
+ if event_type == "message" and isinstance(event.get("sessionId"), str):
257
+ self.event_format = self.event_format or "stream-json"
258
+ role = event.get("role", "")
259
+ sid = event.get("sessionId", "")
260
+ cwd = event.get("cwd", "")
261
+ if sid and not self.cb_session_id:
262
+ self.cb_session_id = sid.strip()
263
+ if cwd and not self.cb_cwd:
264
+ self.cb_cwd = cwd.strip()
265
+ if role == "assistant":
266
+ self.message_count += 1
267
+ self.is_active = True
268
+ content = event.get("content", [])
269
+ if isinstance(content, list):
270
+ for block in content:
271
+ block_type = block.get("type", "")
272
+ if block_type == "tool_use":
273
+ tool_name = block.get("name", "unknown")
274
+ self.current_tool = tool_name
275
+ self.tool_call_counts[tool_name] += 1
276
+ self.total_tool_calls += 1
277
+ tool_input = block.get("input", {})
278
+ if isinstance(tool_input, dict):
279
+ self._extract_tool_summary_from_dict(tool_input)
280
+ self._detect_phase(
281
+ json.dumps(tool_input, ensure_ascii=False)[:500]
282
+ )
283
+ # Track CodeBuddy Agent/Task tool invocations as
284
+ # potential sub-agent spawns (Gap 3 fix).
285
+ self._record_cb_agent_tool_call(tool_name, tool_input)
286
+ elif block_type == "text":
287
+ text = block.get("text", "")
288
+ if text.strip():
289
+ self.last_text_snippet = text.strip()[:120]
290
+ self._detect_phase(text)
291
+ self._detect_terminal_error(
292
+ text, require_error_context=True
293
+ )
294
+ return
295
+
296
+ # CodeBuddy function_call / function_call_result in stream-json
297
+ if event_type in ("function_call", "function_call_result"):
298
+ self.event_format = self.event_format or "stream-json"
299
+ sid = event.get("sessionId", "")
300
+ cwd = event.get("cwd", "")
301
+ if sid and not self.cb_session_id:
302
+ self.cb_session_id = sid.strip()
303
+ if cwd and not self.cb_cwd:
304
+ self.cb_cwd = cwd.strip()
305
+ if event_type == "function_call":
306
+ tool_name = event.get("name", "unknown")
307
+ self.current_tool = tool_name
308
+ self.tool_call_counts[tool_name] += 1
309
+ self.total_tool_calls += 1
310
+ self.is_active = True
311
+ # Extract summary from arguments
312
+ raw_args = event.get("arguments", "")
313
+ if isinstance(raw_args, str) and raw_args.strip():
314
+ self._extract_tool_summary(raw_args)
315
+ self._detect_phase(raw_args[:500])
316
+ # Track Agent/Task tool invocations as sub-agent spawns
317
+ self._record_cb_agent_tool_call(tool_name, raw_args)
318
+ elif event_type == "function_call_result":
319
+ self.current_tool = None
320
+ # Check result text for phase / error hints
321
+ result_text = event.get("output") or ""
322
+ if result_text and isinstance(result_text, str):
323
+ stripped = result_text.strip()
324
+ if stripped:
325
+ self.last_text_snippet = stripped[:120]
326
+ self._detect_phase(stripped)
327
+ self._detect_terminal_error(stripped, require_error_context=True)
328
+ return
329
+
236
330
  # ── Claude Code verbose format ──────────────────────────────
237
331
  if event_type == "assistant":
238
332
  self.event_format = self.event_format or "stream-json"
@@ -259,7 +353,10 @@ class ProgressTracker:
259
353
  self._detect_phase(text)
260
354
  self._detect_terminal_error(text, require_error_context=True)
261
355
 
262
- elif event_type == "tool_result" or event_type == "user":
356
+ elif event_type == "tool_result" or (
357
+ event_type == "user"
358
+ and not isinstance(event.get("sessionId"), str)
359
+ ):
263
360
  # tool_result contains output from tool execution
264
361
  self.event_format = self.event_format or "stream-json"
265
362
  self.is_active = True
@@ -269,11 +366,11 @@ class ProgressTracker:
269
366
  # B) Nested user events: event["message"]["content"][] has type=="tool_result" items
270
367
  content_candidates = []
271
368
 
272
- # Format A: top-level tool_result
369
+ # Format A: top-level tool_result (Claude Code)
273
370
  if event_type == "tool_result":
274
371
  content_candidates.append(str(event.get("content", "")))
275
372
 
276
- # Format B: nested inside user event
373
+ # Format B: nested inside user event (Claude Code, NOT CodeBuddy)
277
374
  if event_type == "user":
278
375
  message = event.get("message", {})
279
376
  content_list = message.get("content", [])
@@ -465,15 +562,60 @@ class ProgressTracker:
465
562
  self._current_tool_input_parts = []
466
563
 
467
564
  elif event_type == "error":
565
+ self.event_format = self.event_format or "stream-json"
468
566
  error_msg = event.get("error", {}).get("message", "Unknown error")
567
+ errors = event.get("errors") or []
568
+ if isinstance(errors, list) and errors:
569
+ error_msg = "; ".join(str(e) for e in errors[:3])
469
570
  self.errors.append(error_msg)
470
571
  self._detect_terminal_error(str(error_msg))
471
572
 
573
+ # ── CodeBuddy file-history-snapshot (ignore) ─────────────────
574
+ elif event_type == "file-history-snapshot":
575
+ return
576
+
472
577
  # Check for subagent indicator
473
578
  if event.get("parent_tool_use_id"):
474
579
  # This is a sub-agent event; tool name is still tracked normally
475
580
  pass
476
581
 
582
+ def _record_cb_agent_tool_call(self, tool_name, raw_args):
583
+ """Record a CodeBuddy Agent/Task* tool invocation for sub-agent tracking.
584
+
585
+ CodeBuddy spawns sub-agents via:
586
+ - Agent(prompt=..., run_in_background=True/False) — synchronous or bg fork
587
+ - TaskCreate(subject=..., description=...) — scheduled task
588
+
589
+ (TaskUpdate/TaskOutput exist but are lifecycle-only — they track
590
+ existing tasks rather than creating new sub-agents, so we don't
591
+ count them as spawns.)
592
+
593
+ We don't get child session ids from these tool calls in the stream,
594
+ but recording the count lets the heartbeat code in heartbeat.sh apply
595
+ an extended stale-kill window just as it does for Codex spawn_agent.
596
+ """
597
+ if tool_name not in ("Agent", "TaskCreate"):
598
+ return
599
+ # Both dict (from message events) and str/JSON (from function_call events)
600
+ # are supported.
601
+ if isinstance(raw_args, dict):
602
+ args = raw_args
603
+ elif isinstance(raw_args, str):
604
+ try:
605
+ args = json.loads(raw_args)
606
+ except (json.JSONDecodeError, TypeError):
607
+ return
608
+ else:
609
+ return
610
+ # For Agent tool, subagent_type or run_in_background hints at delegation
611
+ if tool_name == "Agent":
612
+ subagent_type = args.get("subagent_type", "")
613
+ prompt = args.get("prompt", "")
614
+ if subagent_type or prompt:
615
+ self._subagent_spawn_count += 1
616
+ elif tool_name == "TaskCreate":
617
+ self._subagent_spawn_count += 1
618
+
477
619
  def _record_terminal_result(self, text="", is_error=False, api_error_status=None, api_error_code=""):
478
620
  """Record a Claude Code terminal result event."""
479
621
  terminal_text = str(text or "")
@@ -757,17 +899,85 @@ class ProgressTracker:
757
899
  return []
758
900
  return matches
759
901
 
902
+ def _cb_projects_dir(self):
903
+ """Return the CodeBuddy projects directory for transcript lookup.
904
+
905
+ CodeBuddy stores per-project session transcripts and sub-agent data
906
+ under ~/.codebuddy/projects/{projectDir}/{sessionId}/.
907
+ """
908
+ cb_config = os.environ.get("CODEBUDDY_CONFIG_DIR")
909
+ if cb_config:
910
+ return Path(cb_config).expanduser() / "projects"
911
+ cb_home = os.environ.get("CODEBUDDY_HOME")
912
+ if cb_home:
913
+ return Path(cb_home).expanduser() / "projects"
914
+ return Path.home() / ".codebuddy" / "projects"
915
+
916
+ def _cb_project_key(self):
917
+ """Encode cwd the same way CodeBuddy stores project subdirs.
918
+
919
+ CodeBuddy uses the same sanitisation as Claude Code (\, /, : → -)
920
+ but strips the leading '-' so "/Users/test/MyProject" becomes
921
+ "Users-test-MyProject".
922
+ """
923
+ cwd = self.cb_cwd
924
+ if not cwd:
925
+ return ""
926
+ return cwd.replace("\\", "-").replace("/", "-").replace(":", "").lstrip("-")
927
+
928
+ def _find_cb_child_session_files(self):
929
+ """Find CodeBuddy subagent transcript data for this parent session.
930
+
931
+ CodeBuddy sub-agents store tool-results/{callId}.txt files; conversation
932
+ transcripts are not (as of 2026) written as agent-*.jsonl files in the
933
+ subagents/ directory. We track the tool-results .txt files as a proxy
934
+ for child activity so the heartbeat monitor can extend the stale-kill
935
+ window while sub-agents are running.
936
+ """
937
+ if not self.cb_session_id:
938
+ return []
939
+
940
+ projects_dir = self._cb_projects_dir()
941
+ if not projects_dir.exists():
942
+ return []
943
+
944
+ project_key = self._cb_project_key()
945
+ if not project_key:
946
+ return []
947
+
948
+ subagents_dir = (
949
+ projects_dir / project_key / self.cb_session_id / "subagents"
950
+ )
951
+ if not subagents_dir.exists():
952
+ return []
953
+
954
+ # Collect all files under each agent-{id} subdirectory. These are
955
+ # currently tool-results/{callId}.txt files, but the glob also picks
956
+ # up future agent-*.jsonl transcripts should CodeBuddy add them.
957
+ try:
958
+ return sorted(
959
+ p for p in subagents_dir.glob("**/*")
960
+ if p.is_file()
961
+ )
962
+ except OSError:
963
+ return []
964
+
760
965
  def refresh_child_session_activity(self, force=False):
761
966
  """Refresh child transcript file stats.
762
967
 
763
968
  The heartbeat monitor uses this activity signature to treat subagent
764
969
  transcript growth as real progress while the parent session is blocked
765
- waiting for a child agent/tool result. Supports Codex child threads and
766
- Claude Code in-process teammate transcripts.
970
+ waiting for a child agent/tool result. Supports Codex child threads,
971
+ Claude Code in-process teammate transcripts, and CodeBuddy sub-agent
972
+ file activity under subagents/.
767
973
  """
768
974
  previous_signature = self.child_activity_signature
769
975
 
770
- if not self.codex_child_thread_ids and not self.claude_session_id:
976
+ if (
977
+ not self.codex_child_thread_ids
978
+ and not self.claude_session_id
979
+ and not self.cb_session_id
980
+ ):
771
981
  self.child_session_files = []
772
982
  self.child_total_bytes = 0
773
983
  self.child_activity_signature = ""
@@ -788,6 +998,7 @@ class ProgressTracker:
788
998
  if found:
789
999
  self._codex_child_session_paths[thread_id] = found
790
1000
  self._claude_child_session_files = self._find_claude_child_session_files()
1001
+ self._cb_child_session_files = self._find_cb_child_session_files()
791
1002
  self._last_child_scan_at = now
792
1003
 
793
1004
  files = []
@@ -826,6 +1037,14 @@ class ProgressTracker:
826
1037
  for path in self._claude_child_session_files:
827
1038
  add_file("claude", path.stem, path)
828
1039
 
1040
+ for path in self._cb_child_session_files:
1041
+ # Identifier for CodeBuddy sub-agent files: use the parent
1042
+ # directory name (agent-{id}) so heartbeat can distinguish
1043
+ # activity across different sub-agent instances.
1044
+ parent_name = path.parent.name if hasattr(path, "parent") else ""
1045
+ identifier = parent_name if parent_name.startswith("agent-") else path.name
1046
+ add_file("codebuddy", identifier, path)
1047
+
829
1048
  self.child_session_files = files
830
1049
  self.child_total_bytes = total_bytes
831
1050
  self.child_activity_signature = "|".join(signature_parts)
@@ -861,11 +1080,14 @@ class ProgressTracker:
861
1080
  "total_tool_calls": self.total_tool_calls,
862
1081
  "active_subagent_count": self.active_subagent_count,
863
1082
  "subagent_states": subagent_states,
1083
+ "subagent_spawn_count": self._subagent_spawn_count,
864
1084
  "child_thread_ids": sorted(self.codex_child_thread_ids),
865
1085
  "child_session_files": self.child_session_files,
866
1086
  "child_total_bytes": self.child_total_bytes,
867
1087
  "child_activity_signature": self.child_activity_signature,
868
1088
  "last_child_activity_at": self.last_child_activity_at,
1089
+ "cb_session_id": self.cb_session_id,
1090
+ "cb_cwd": self.cb_cwd,
869
1091
  "last_text_snippet": self.last_text_snippet,
870
1092
  "last_result_is_error": self.last_result_is_error,
871
1093
  "api_error_status": self.api_error_status,
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.74",
2
+ "version": "1.1.77",
3
3
  "skills": {
4
4
  "prizm-kit": {
5
5
  "description": "Full-lifecycle dev toolkit. Covers spec-driven development, Prizm context docs, code quality, debugging, deployment, and knowledge management.",
@@ -332,22 +332,23 @@ For each selected layer, run the rules Q&A workflow. This follows the 4-phase ru
332
332
 
333
333
  **Phase A — Load layer resources:**
334
334
  - Read `${SKILL_DIR}/references/rules/<layer>/fixed-rules.md` — industry-consensus rules injected without asking
335
- - Read `${SKILL_DIR}/references/rules/<layer>/question-bank.md` — interactive questions organized in groups (G1→G10)
335
+ - Read `${SKILL_DIR}/references/rules/<layer>/question-bank.md` — interactive questions organized in groups (G1→G10): authoritative source for question text, options, "Recommended" markers, and Notes
336
+ - Read `${SKILL_DIR}/references/rules/<layer>/question-manifest.json` — machine-readable structure for this layer. **This is your asking checklist.** It lists, per question: `group`, `required`, `maps_to` (which template placeholders the answer fills), and conditional fields (`required_if`, `auto_derived_when`, `options_vary_by`). Its `groups[]` carries each group's `quick_mode` flag, and `template_placeholders` is the expected-set for the Phase D self-check. Use the manifest to track coverage; use question-bank.md for the actual wording you present to the user. If a question id in one file is absent from the other, trust question-bank.md for content and note the drift.
336
337
 
337
338
  **Phase B — Interactive Q&A:**
338
339
  - Ask questions one group at a time (max 3 questions per message), as defined in question-bank.md
339
340
  - Each question shows a "Recommended" option to reduce decision cost
340
- - **Quick mode**: ask only the following groups per layer. All other groups adopt recommended defaults silently.
341
- - **Frontend**: G1 (Tech Stack), G2 (Styling), G3 (State & Data Fetching), G7 (Testing & Quality). Skip: G4 (Design System), G5 (Responsive), G6 (i18n), G8 (AI Vibecoding), G9 (Performance).
342
- - **Backend**: G1 (Language & Runtime), G2 (Framework & Architecture), G3 (API Style), G4 (Data Layer), G7 (Testing & Quality). Skip: G5 (Auth), G6 (Infrastructure), G8 (Observability), G9 (AI Constraints).
343
- - **Database**: G1 (Database Selection), G2 (Data Modeling), G3 (Migration), G7 (AI Constraints). Skip: G4 (Index & Performance), G5 (Security & Compliance), G6 (High Availability & Ops).
344
- - **Mobile**: G1 (Platform & Language), G2 (Architecture), G3 (UI Framework), G4 (Navigation & State), G7 (Testing). Skip: G5 (Networking & Data), G6 (Platform Features), G8 (App Distribution), G9 (Performance & Accessibility), G10 (AI Constraints).
345
- - **Full mode**: ask all groups in order (G1→G9 for frontend/backend, G1→G7 for database, G1→G10 for mobile)
341
+ - **Quick mode**: ask only the groups whose `quick_mode` is `true` in `question-manifest.json`. All other groups adopt recommended defaults silently. (The manifest is the single source of truth for quick-mode membership — do not hardcode group lists here.)
342
+ - **Full mode**: ask all groups in manifest order (`groups[]`).
343
+ - **Conditional questions** (driven by the manifest's per-question fields):
344
+ - `required_if: "<expr>"` ask this question only when the expression over prior answers holds (e.g. Q14 `required_if: Q13 != D`). If the condition is false, the question is legitimately skipped not a coverage gap.
345
+ - `auto_derived_when: "<expr>"` when this holds, fill the mapped placeholder(s) from the prior answer without asking (e.g. backend Q6 `auto_derived_when: Q5 in [GraphQL, gRPC]`). Count as satisfied, not skipped.
346
+ - `options_vary_by: "<Qid>"` the question is still asked; only its option list depends on that prior answer (e.g. framework/ORM options vary by language). Never affects whether the question is required.
346
347
  - Shortcut commands work at any point:
347
348
  - `recommended` / `default` → adopt all recommended for current group
348
349
  - `all recommended` → adopt all recommended for all remaining groups
349
350
  - `skip` → mark current group as "Not required at this stage"
350
- - Record answers in memory after each group.
351
+ - Record answers in memory after each group. Track which manifest questions are answered, auto-derived, or conditionally skipped so Phase D can verify coverage.
351
352
 
352
353
  **Phase C — Auto-derivation:**
353
354
  - Read `${SKILL_DIR}/references/rules/<layer>/derivation-rules.md`
@@ -357,8 +358,10 @@ For each selected layer, run the rules Q&A workflow. This follows the 4-phase ru
357
358
  **Phase D — Render and write:**
358
359
  - Read `${SKILL_DIR}/references/rules/<layer>/template.md`
359
360
  - Fill all template placeholders with accumulated content from Phases A+B+C
360
- - **Post-render self-check**: scan for residual `{{ ` or ` }}` — count must be 0 before writing
361
- - Generate Appendix A (Deny List) and Appendix B (Recommended Tools) per template instructions
361
+ - **Post-render self-check** (driven by `question-manifest.json` `template_placeholders`):
362
+ 1. **Coverage pass** for every placeholder in `from_questions`, confirm it traces to an answered question OR an `auto_derived_when` path OR a conditionally-skipped question (`required_if` false). If a `from_questions` placeholder has no source, you skipped a required question — go back and ask it before writing.
363
+ 2. **Residual pass** — scan the rendered document for any residual `{{ ` or ` }}`; count must be 0. Placeholders in `from_fixed_rules` / `auto_generated` / `metadata` are filled from fixed-rules.md, Phase D generation, and project metadata respectively — they are NOT expected to come from Q&A, but they MUST still be rendered (no residual braces).
364
+ - Generate Appendix A (Deny List) and Appendix B (Recommended Tools) per template instructions — these fill the `auto_generated` placeholders
362
365
  - Create `.prizmkit/rules/` directory if it doesn't exist
363
366
  - Write `.prizmkit/rules/<layer>-rules.md`
364
367
 
@@ -0,0 +1,46 @@
1
+ {
2
+ "schema_version": "app-planner-question-manifest-v1",
3
+ "layer": "backend",
4
+ "total_questions": 20,
5
+ "_doc": "Machine-readable structure for backend question-bank.md. Question TEXT/options/Recommended/Notes are authoritative in question-bank.md; this file carries ONLY structure: group membership, quick-mode, question->placeholder mapping, and conditional logic. Keep Qids and group ids in sync with question-bank.md. The AI reads this as its asking checklist (Phase A) and self-check expected-set (Phase D).",
6
+ "_conditions": "required_if / auto_derived_when use simple expressions over recorded answers, e.g. 'Q5 == RESTful', 'Q13 != D', 'Q5 in [GraphQL, gRPC]'. options_vary_by names a prior answer that changes the OPTION LIST only (question is still asked; never affects required).",
7
+ "groups": [
8
+ { "id": "G1", "topic": "Language & Runtime", "quick_mode": true },
9
+ { "id": "G2", "topic": "Framework & Architecture", "quick_mode": true },
10
+ { "id": "G3", "topic": "API Style", "quick_mode": true },
11
+ { "id": "G4", "topic": "Data Layer", "quick_mode": true },
12
+ { "id": "G5", "topic": "Auth", "quick_mode": false },
13
+ { "id": "G6", "topic": "Infrastructure", "quick_mode": false },
14
+ { "id": "G7", "topic": "Testing & Quality", "quick_mode": true },
15
+ { "id": "G8", "topic": "Observability", "quick_mode": false },
16
+ { "id": "G9", "topic": "AI Constraints", "quick_mode": false }
17
+ ],
18
+ "questions": [
19
+ { "id": "Q1", "group": "G1", "topic": "Programming Language", "maps_to": ["language", "tech_stack_rules"], "required": true },
20
+ { "id": "Q2", "group": "G1", "topic": "Runtime Version", "maps_to": ["runtime_version"], "required": true },
21
+ { "id": "Q3", "group": "G2", "topic": "Web Framework", "maps_to": ["framework", "tech_stack_rules"], "required": true, "options_vary_by": "Q1", "note_open_ended_when": "Q1 == Custom" },
22
+ { "id": "Q4", "group": "G2", "topic": "Architecture Pattern", "maps_to": ["architecture", "arch_rules"], "required": true },
23
+ { "id": "Q5", "group": "G3", "topic": "API Style", "maps_to": ["api_style", "api_rules"], "required": true },
24
+ { "id": "Q6", "group": "G3", "topic": "API Documentation", "maps_to": ["api_docs"], "required": true, "required_if": "Q5 == RESTful", "auto_derived_when": "Q5 in [GraphQL, gRPC]" },
25
+ { "id": "Q7", "group": "G4", "topic": "ORM / Data Access", "maps_to": ["orm", "data_rules"], "required": true, "options_vary_by": "Q1", "note_open_ended_when": "Q1 == Custom" },
26
+ { "id": "Q8", "group": "G4", "topic": "Database", "maps_to": ["database"], "required": true },
27
+ { "id": "Q9", "group": "G5", "topic": "Authentication Scheme", "maps_to": ["auth_scheme", "auth_rules"], "required": true },
28
+ { "id": "Q10", "group": "G6", "topic": "Caching", "maps_to": ["cache", "cache_rules"], "required": true },
29
+ { "id": "Q11", "group": "G6", "topic": "Message Queue", "maps_to": ["mq", "mq_rules"], "required": true },
30
+ { "id": "Q12", "group": "G6", "topic": "Containerization & Deployment", "maps_to": ["container", "deploy_rules"], "required": true },
31
+ { "id": "Q13", "group": "G7", "topic": "Test Coverage Requirements", "maps_to": ["test_requirement", "test_rules"], "required": true },
32
+ { "id": "Q14", "group": "G7", "topic": "Test Framework", "maps_to": ["test_framework"], "required": true, "required_if": "Q13 != D", "options_vary_by": "Q1" },
33
+ { "id": "Q15", "group": "G7", "topic": "Mock Strategy", "maps_to": ["mock_strategy", "test_rules"], "required": true },
34
+ { "id": "Q16", "group": "G8", "topic": "Logging", "maps_to": ["logging", "observability_rules"], "required": true },
35
+ { "id": "Q17", "group": "G8", "topic": "Monitoring & Tracing", "maps_to": ["monitoring", "observability_rules"], "required": true },
36
+ { "id": "Q18", "group": "G9", "topic": "AI Permission to Add Dependencies", "maps_to": ["ai_dependency_rule"], "required": true },
37
+ { "id": "Q19", "group": "G9", "topic": "AI Modifying Shared Modules", "maps_to": ["ai_breaking_change_rule"], "required": true },
38
+ { "id": "Q20", "group": "G9", "topic": "AI Modifying Configuration", "maps_to": ["ai_config_rule"], "required": true }
39
+ ],
40
+ "template_placeholders": {
41
+ "from_questions": ["language", "runtime_version", "framework", "architecture", "api_style", "api_docs", "orm", "database", "auth_scheme", "cache", "mq", "container", "test_requirement", "test_framework", "mock_strategy", "logging", "monitoring", "ai_dependency_rule", "ai_breaking_change_rule", "ai_config_rule", "tech_stack_rules", "arch_rules", "api_rules", "auth_rules", "data_rules", "cache_rules", "mq_rules", "deploy_rules", "observability_rules", "test_rules"],
42
+ "from_fixed_rules": ["FIXED_RULES_STRUCTURE", "FIXED_RULES_API", "FIXED_RULES_ERROR", "FIXED_RULES_SECURITY", "FIXED_RULES_RATE_LIMITING", "FIXED_RULES_DATABASE", "FIXED_RULES_OBSERVABILITY", "FIXED_RULES_TRACING", "FIXED_RULES_CONFIG", "FIXED_RULES_ASYNC", "FIXED_RULES_MESSAGE_DELIVERY", "FIXED_RULES_GRACEFUL_SHUTDOWN", "FIXED_RULES_TEST", "FIXED_RULES_AI_BASE"],
43
+ "auto_generated": ["deny_list_summary", "recommended_libs"],
44
+ "metadata": ["project_name", "generated_at"]
45
+ }
46
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "schema_version": "app-planner-question-manifest-v1",
3
+ "layer": "database",
4
+ "total_questions": 15,
5
+ "_doc": "Machine-readable structure for database question-bank.md. Question TEXT/options/Recommended/Notes are authoritative in question-bank.md; this file carries ONLY structure: group membership, quick-mode, question->placeholder mapping, and conditional logic. Keep Qids and group ids in sync with question-bank.md. The AI reads this as its asking checklist (Phase A) and self-check expected-set (Phase D).",
6
+ "_conditions": "required_if / auto_derived_when use simple expressions over recorded answers. options_vary_by names a prior answer that changes the OPTION LIST only (question is still asked; never affects required). A single answer may fill more than one placeholder (a TL;DR value + a rules-block), both listed in maps_to.",
7
+ "groups": [
8
+ { "id": "G1", "topic": "Database Selection", "quick_mode": true },
9
+ { "id": "G2", "topic": "Data Modeling", "quick_mode": true },
10
+ { "id": "G3", "topic": "Migration", "quick_mode": true },
11
+ { "id": "G4", "topic": "Index & Performance", "quick_mode": false },
12
+ { "id": "G5", "topic": "Security & Compliance", "quick_mode": false },
13
+ { "id": "G6", "topic": "High Availability & Ops", "quick_mode": false },
14
+ { "id": "G7", "topic": "AI Constraints", "quick_mode": true }
15
+ ],
16
+ "questions": [
17
+ { "id": "Q1", "group": "G1", "topic": "Primary Database", "maps_to": ["database", "tech_rules"], "required": true },
18
+ { "id": "Q2", "group": "G1", "topic": "Cache Layer", "maps_to": ["cache", "cache_rules"], "required": true },
19
+ { "id": "Q3", "group": "G2", "topic": "Primary Key Strategy", "maps_to": ["pk_strategy", "schema_rules"], "required": true },
20
+ { "id": "Q4", "group": "G2", "topic": "Soft Delete", "maps_to": ["soft_delete", "schema_rules"], "required": true },
21
+ { "id": "Q5", "group": "G2", "topic": "Normalization Level", "maps_to": ["normalization", "schema_rules"], "required": true },
22
+ { "id": "Q6", "group": "G3", "topic": "Migration Tool", "maps_to": ["migration_tool", "migration_rules"], "required": true },
23
+ { "id": "Q7", "group": "G4", "topic": "Query Complexity", "maps_to": ["workload_type", "performance_rules"], "required": true },
24
+ { "id": "Q8", "group": "G4", "topic": "Estimated Data Volume", "maps_to": ["data_scale", "performance_rules"], "required": true },
25
+ { "id": "Q9", "group": "G5", "topic": "Data Sensitivity Level", "maps_to": ["data_sensitivity", "security_rules"], "required": true },
26
+ { "id": "Q10", "group": "G5", "topic": "Audit Requirements", "maps_to": ["audit", "audit_rules"], "required": true, "multi_select": true },
27
+ { "id": "Q11", "group": "G6", "topic": "Deployment Method", "maps_to": ["deployment", "ops_rules"], "required": true },
28
+ { "id": "Q12", "group": "G6", "topic": "Backup Strategy", "maps_to": ["backup", "ops_rules"], "required": true },
29
+ { "id": "Q13", "group": "G6", "topic": "High Availability Requirements", "maps_to": ["ha", "ops_rules"], "required": true },
30
+ { "id": "Q14", "group": "G7", "topic": "AI Permission to Modify Database Structure", "maps_to": ["ai_ddl_rule", "ai_ddl_permission"], "required": true },
31
+ { "id": "Q15", "group": "G7", "topic": "AI Safety Constraints", "maps_to": ["ai_safety_rule", "ai_safety_constraint"], "required": true }
32
+ ],
33
+ "template_placeholders": {
34
+ "from_questions": ["database", "tech_rules", "cache", "cache_rules", "pk_strategy", "schema_rules", "soft_delete", "normalization", "migration_tool", "migration_rules", "workload_type", "performance_rules", "data_scale", "data_sensitivity", "security_rules", "audit", "audit_rules", "deployment", "ops_rules", "backup", "ha", "ai_ddl_rule", "ai_ddl_permission", "ai_safety_rule", "ai_safety_constraint"],
35
+ "from_fixed_rules": ["FIXED_RULES_AI_BASE", "FIXED_RULES_BACKUP", "FIXED_RULES_CONNECTION", "FIXED_RULES_INDEX", "FIXED_RULES_MIGRATION", "FIXED_RULES_PERFORMANCE", "FIXED_RULES_QUERY", "FIXED_RULES_SCHEMA", "FIXED_RULES_SECURITY", "FIXED_RULES_TRANSACTION", "FIXED_RULES_TYPES"],
36
+ "auto_generated": ["deny_list_summary", "recommended_libs"],
37
+ "metadata": ["project_name", "generated_at"]
38
+ }
39
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "schema_version": "app-planner-question-manifest-v1",
3
+ "layer": "frontend",
4
+ "total_questions": 25,
5
+ "_doc": "Machine-readable structure for frontend question-bank.md. Question TEXT/options/Recommended/Notes are authoritative in question-bank.md; this file carries ONLY structure: group membership, quick-mode, question->placeholder mapping, and conditional logic. Keep Qids and group ids in sync with question-bank.md. The AI reads this as its asking checklist (Phase A) and self-check expected-set (Phase D).",
6
+ "_conditions": "required_if / auto_derived_when use simple expressions over recorded answers, e.g. 'Q15 in [A, B]'. options_vary_by names a prior answer that changes the OPTION LIST/recommendation only (question is still asked; never affects required).",
7
+ "groups": [
8
+ { "id": "G1", "topic": "Tech Stack", "quick_mode": true },
9
+ { "id": "G2", "topic": "Styling", "quick_mode": true },
10
+ { "id": "G3", "topic": "State & Data Fetching", "quick_mode": true },
11
+ { "id": "G4", "topic": "Design System", "quick_mode": false },
12
+ { "id": "G5", "topic": "Responsive & Adaptation", "quick_mode": false },
13
+ { "id": "G6", "topic": "i18n & Accessibility", "quick_mode": false },
14
+ { "id": "G7", "topic": "Testing & Quality", "quick_mode": true },
15
+ { "id": "G8", "topic": "AI Vibecoding Constraints", "quick_mode": false },
16
+ { "id": "G9", "topic": "Performance Baseline", "quick_mode": false }
17
+ ],
18
+ "questions": [
19
+ { "id": "Q1", "group": "G1", "topic": "Frontend Framework", "maps_to": ["framework", "tech_stack_rules"], "required": true },
20
+ { "id": "Q2", "group": "G1", "topic": "Meta-Framework", "maps_to": ["meta_framework", "tech_stack_rules"], "required": true, "options_vary_by": "Q1" },
21
+ { "id": "Q3", "group": "G1", "topic": "Package Manager", "maps_to": ["package_manager", "tech_stack_rules"], "required": true },
22
+ { "id": "Q4", "group": "G2", "topic": "Styling Solution", "maps_to": ["style_solution", "style_specific_rules"], "required": true },
23
+ { "id": "Q4b", "group": "G2", "topic": "Font Loading Strategy", "maps_to": ["font_strategy", "performance_rules"], "required": true },
24
+ { "id": "Q5", "group": "G3", "topic": "Global State Library", "maps_to": ["state_lib", "state_rules"], "required": true, "options_vary_by": "Q1" },
25
+ { "id": "Q6", "group": "G3", "topic": "Server State Library", "maps_to": ["server_state_lib", "server_state_rules"], "required": true },
26
+ { "id": "Q7", "group": "G3", "topic": "API Type Source", "maps_to": ["api_type_source", "server_state_rules"], "required": true },
27
+ { "id": "Q8", "group": "G4", "topic": "Primary Design/Mockup Source", "maps_to": ["design_source"], "required": true },
28
+ { "id": "Q9", "group": "G4", "topic": "Token Naming Layers", "maps_to": ["token_layering", "token_layering_rules"], "required": true },
29
+ { "id": "Q10", "group": "G4", "topic": "Dark Mode", "maps_to": ["dark_mode", "dark_mode_rules"], "required": true },
30
+ { "id": "Q11", "group": "G5", "topic": "Adaptation Strategy", "maps_to": ["responsive_strategy", "breakpoint_rules"], "required": true },
31
+ { "id": "Q12", "group": "G5", "topic": "Breakpoint Scheme", "maps_to": ["breakpoint_scheme", "breakpoint_rules"], "required": true },
32
+ { "id": "Q13", "group": "G6", "topic": "Internationalization", "maps_to": ["i18n", "i18n_rules"], "required": true },
33
+ { "id": "Q14", "group": "G6", "topic": "Accessibility Target", "maps_to": ["a11y_level", "a11y_extra_rules"], "required": true },
34
+ { "id": "Q15", "group": "G7", "topic": "Test Coverage Requirements", "maps_to": ["test_coverage", "test_rules"], "required": true },
35
+ { "id": "Q16", "group": "G7", "topic": "Unit Test Framework", "maps_to": ["unit_test_framework", "test_rules"], "required": true, "required_if": "Q15 in [A, B]" },
36
+ { "id": "Q17", "group": "G7", "topic": "E2E Framework", "maps_to": ["e2e_framework", "test_rules"], "required": true, "required_if": "Q15 in [A, C]" },
37
+ { "id": "Q18", "group": "G8", "topic": "AI Component Index Sync", "maps_to": ["ai_index_rule"], "required": true },
38
+ { "id": "Q19", "group": "G8", "topic": "AI Permission to Add Dependencies", "maps_to": ["ai_dependency_rule"], "required": true },
39
+ { "id": "Q20", "group": "G8", "topic": "AI Impact Analysis Before Modifying Shared Components", "maps_to": ["ai_breaking_change_rule"], "required": true },
40
+ { "id": "Q21", "group": "G8", "topic": "AI Permission to Modify Config Files", "maps_to": ["ai_config_rule"], "required": true },
41
+ { "id": "Q22", "group": "G8", "topic": "AI Single File Generation Limit", "maps_to": ["ai_max_lines"], "required": true },
42
+ { "id": "Q23", "group": "G9", "topic": "First Screen LCP Target", "maps_to": ["lcp_target", "performance_rules"], "required": true },
43
+ { "id": "Q24", "group": "G9", "topic": "Single Chunk Size Limit", "maps_to": ["bundle_size", "performance_rules"], "required": true }
44
+ ],
45
+ "template_placeholders": {
46
+ "from_questions": ["framework", "meta_framework", "package_manager", "style_solution", "style_specific_rules", "font_strategy", "performance_rules", "state_lib", "state_rules", "server_state_lib", "server_state_rules", "api_type_source", "design_source", "token_layering", "token_layering_rules", "dark_mode", "dark_mode_rules", "responsive_strategy", "breakpoint_rules", "breakpoint_scheme", "i18n", "i18n_rules", "a11y_level", "a11y_extra_rules", "test_coverage", "test_rules", "unit_test_framework", "e2e_framework", "ai_index_rule", "ai_dependency_rule", "ai_breaking_change_rule", "ai_config_rule", "ai_max_lines", "lcp_target", "bundle_size", "tech_stack_rules"],
47
+ "from_fixed_rules": ["FIXED_RULES_A11Y", "FIXED_RULES_AI_BASE", "FIXED_RULES_COMPONENT_CONTRACT", "FIXED_RULES_DENY_LIST", "FIXED_RULES_ERROR_HANDLING", "FIXED_RULES_GIT", "FIXED_RULES_I18N_BASELINE", "FIXED_RULES_NAMING", "FIXED_RULES_PERFORMANCE", "FIXED_RULES_SECURITY", "FIXED_RULES_TYPESCRIPT"],
48
+ "auto_generated": ["deny_list_summary", "recommended_libs"],
49
+ "metadata": ["project_name", "generated_at", "project_scope"]
50
+ }
51
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "schema_version": "app-planner-question-manifest-v1",
3
+ "layer": "mobile",
4
+ "total_questions": 20,
5
+ "_doc": "Machine-readable structure for mobile question-bank.md. Question TEXT/options/Recommended/Notes are authoritative in question-bank.md; this file carries ONLY structure: group membership, quick-mode, question->placeholder mapping, and conditional logic. Keep Qids and group ids in sync with question-bank.md. The AI reads this as its asking checklist (Phase A) and self-check expected-set (Phase D).",
6
+ "_conditions": "required_if / auto_derived_when use simple expressions over recorded answers, e.g. 'Q12 in [A, B]'. options_vary_by names a prior answer that changes the OPTION LIST only (question is still asked; never affects required). dual_native_repeat_when notes Q1=E (both native) repeats the question per platform.",
7
+ "groups": [
8
+ { "id": "G1", "topic": "Platform & Language", "quick_mode": true },
9
+ { "id": "G2", "topic": "Architecture", "quick_mode": true },
10
+ { "id": "G3", "topic": "UI Framework", "quick_mode": true },
11
+ { "id": "G4", "topic": "Navigation & State", "quick_mode": true },
12
+ { "id": "G5", "topic": "Networking & Data", "quick_mode": false },
13
+ { "id": "G6", "topic": "Platform Features", "quick_mode": false },
14
+ { "id": "G7", "topic": "Testing", "quick_mode": true },
15
+ { "id": "G8", "topic": "App Distribution", "quick_mode": false },
16
+ { "id": "G9", "topic": "Performance & Accessibility", "quick_mode": false },
17
+ { "id": "G10", "topic": "AI Constraints", "quick_mode": false }
18
+ ],
19
+ "questions": [
20
+ { "id": "Q1", "group": "G1", "topic": "Target Platform", "maps_to": ["platform", "tech_stack_rules"], "required": true },
21
+ { "id": "Q2", "group": "G1", "topic": "Minimum OS Version", "maps_to": ["min_os_version", "tech_stack_rules"], "required": true },
22
+ { "id": "Q3", "group": "G2", "topic": "Architecture Pattern", "maps_to": ["architecture", "arch_rules"], "required": true },
23
+ { "id": "Q4", "group": "G3", "topic": "UI Framework", "maps_to": ["ui_framework", "ui_rules"], "required": true, "options_vary_by": "Q1", "dual_native_repeat_when": "Q1 == E" },
24
+ { "id": "Q5", "group": "G4", "topic": "Navigation Strategy", "maps_to": ["navigation", "navigation_rules"], "required": true },
25
+ { "id": "Q6", "group": "G4", "topic": "State Management", "maps_to": ["state_management", "state_rules"], "required": true, "options_vary_by": "Q1", "dual_native_repeat_when": "Q1 == E" },
26
+ { "id": "Q7", "group": "G5", "topic": "Networking Library", "maps_to": ["networking", "networking_rules"], "required": true, "options_vary_by": "Q1", "dual_native_repeat_when": "Q1 == E" },
27
+ { "id": "Q8", "group": "G5", "topic": "Local Persistence", "maps_to": ["persistence", "persistence_rules"], "required": true, "options_vary_by": "Q1", "dual_native_repeat_when": "Q1 == E" },
28
+ { "id": "Q9", "group": "G6", "topic": "Push Notifications", "maps_to": ["push_notifications", "platform_features_rules"], "required": true },
29
+ { "id": "Q10", "group": "G6", "topic": "Background Tasks", "maps_to": ["background_tasks", "platform_features_rules"], "required": true },
30
+ { "id": "Q11", "group": "G6", "topic": "Permissions Strategy", "maps_to": ["permissions_strategy"], "required": true },
31
+ { "id": "Q12", "group": "G7", "topic": "Test Coverage Requirements", "maps_to": ["test_coverage", "test_rules"], "required": true },
32
+ { "id": "Q13", "group": "G7", "topic": "Unit Test Framework", "maps_to": ["unit_test_framework"], "required": true, "required_if": "Q12 in [A, B]", "options_vary_by": "Q1", "dual_native_repeat_when": "Q1 == E" },
33
+ { "id": "Q14", "group": "G7", "topic": "UI/E2E Test Framework", "maps_to": ["e2e_framework"], "required": true, "required_if": "Q12 in [A, C]", "options_vary_by": "Q1", "dual_native_repeat_when": "Q1 == E" },
34
+ { "id": "Q15", "group": "G8", "topic": "Distribution Method", "maps_to": ["distribution", "distribution_rules"], "required": true },
35
+ { "id": "Q16", "group": "G9", "topic": "Performance Targets", "maps_to": ["performance_target", "performance_rules"], "required": true },
36
+ { "id": "Q17", "group": "G9", "topic": "Accessibility Targets", "maps_to": ["a11y_target", "a11y_rules"], "required": true },
37
+ { "id": "Q18", "group": "G10", "topic": "AI Permission to Add Dependencies", "maps_to": ["ai_dependency_rule"], "required": true },
38
+ { "id": "Q19", "group": "G10", "topic": "AI Impact Analysis Before Modifying Shared Code", "maps_to": ["ai_breaking_change_rule"], "required": true },
39
+ { "id": "Q20", "group": "G10", "topic": "AI Platform-Specific Code Generation", "maps_to": ["ai_platform_rule"], "required": true }
40
+ ],
41
+ "template_placeholders": {
42
+ "from_questions": ["platform", "tech_stack_rules", "min_os_version", "architecture", "arch_rules", "ui_framework", "ui_rules", "navigation", "navigation_rules", "state_management", "state_rules", "networking", "networking_rules", "persistence", "persistence_rules", "push_notifications", "platform_features_rules", "background_tasks", "permissions_strategy", "test_coverage", "test_rules", "unit_test_framework", "e2e_framework", "distribution", "distribution_rules", "performance_target", "performance_rules", "a11y_target", "a11y_rules", "ai_dependency_rule", "ai_breaking_change_rule", "ai_platform_rule"],
43
+ "from_fixed_rules": ["FIXED_RULES_A11Y", "FIXED_RULES_AI_BASE", "FIXED_RULES_DISTRIBUTION", "FIXED_RULES_NAVIGATION", "FIXED_RULES_NETWORKING", "FIXED_RULES_PERFORMANCE", "FIXED_RULES_PERSISTENCE", "FIXED_RULES_PLATFORM_FEATURES", "FIXED_RULES_SECURITY", "FIXED_RULES_STATE", "FIXED_RULES_STRUCTURE", "FIXED_RULES_TEST", "FIXED_RULES_UI"],
44
+ "auto_generated": ["deny_list_summary", "recommended_libs"],
45
+ "metadata": ["project_name", "generated_at"]
46
+ }
47
+ }
@@ -189,6 +189,7 @@ Detect user intent from their message, then follow the corresponding workflow:
189
189
  | Variable | Default | Purpose |
190
190
  |----------|---------|---------|
191
191
  | `MODEL` | (none) | AI model override (e.g. `claude-opus-4.6`) |
192
+ | `PRIZMKIT_EFFORT` | (none) | AI reasoning effort: `low`\|`medium`\|`high`\|`xhigh`\|`max` (max = Claude Code only) |
192
193
  | `AUTO_PUSH` | `0` | Auto-push to remote after successful bug fix (`1` to enable) |
193
194
  | `DEV_BRANCH` | auto-generated | Custom dev branch name (default: `bugfix/pipeline-{run_id}`) |
194
195
  | `HEARTBEAT_INTERVAL` | `30` | Heartbeat log interval in seconds |