prizmkit 1.0.76 → 1.0.78

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.
@@ -170,6 +170,9 @@ python3 "$SCRIPTS_DIR/update-feature-status.py" \
170
170
  log_warn "Failed to clean feature artifacts (continuing with fresh session only)"
171
171
  }
172
172
 
173
+ # Auto-detect available models (quiet, non-blocking)
174
+ bash "$SCRIPT_DIR/scripts/detect-models.sh" --quiet 2>/dev/null || true
175
+
173
176
  # ============================================================
174
177
  # Generate bootstrap prompt
175
178
  # ============================================================
@@ -182,7 +185,7 @@ mkdir -p "$SESSION_DIR/logs"
182
185
  BOOTSTRAP_PROMPT="$SESSION_DIR/bootstrap-prompt.md"
183
186
 
184
187
  log_info "Generating bootstrap prompt..."
185
- python3 "$SCRIPTS_DIR/generate-bootstrap-prompt.py" \
188
+ GEN_OUTPUT=$(python3 "$SCRIPTS_DIR/generate-bootstrap-prompt.py" \
186
189
  --feature-list "$FEATURE_LIST" \
187
190
  --feature-id "$FEATURE_ID" \
188
191
  --session-id "$SESSION_ID" \
@@ -190,7 +193,11 @@ python3 "$SCRIPTS_DIR/generate-bootstrap-prompt.py" \
190
193
  --retry-count 0 \
191
194
  --resume-phase "null" \
192
195
  --state-dir "$STATE_DIR" \
193
- --output "$BOOTSTRAP_PROMPT" >/dev/null 2>&1
196
+ --output "$BOOTSTRAP_PROMPT" 2>/dev/null) || {
197
+ log_error "Failed to generate bootstrap prompt"
198
+ exit 1
199
+ }
200
+ FEATURE_MODEL=$(echo "$GEN_OUTPUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('model',''))" 2>/dev/null || echo "")
194
201
 
195
202
  # ============================================================
196
203
  # Run single AI CLI session
@@ -234,9 +241,12 @@ python3 "$SCRIPTS_DIR/update-feature-status.py" \
234
241
  --action start >/dev/null 2>&1 || true
235
242
 
236
243
  # Spawn AI CLI session
244
+ # Spawn AI CLI session — model priority: feature.model > $MODEL env > none
245
+ EFFECTIVE_MODEL="${FEATURE_MODEL:-${MODEL:-}}"
237
246
  MODEL_FLAG=""
238
- if [[ -n "${MODEL:-}" ]]; then
239
- MODEL_FLAG="--model $MODEL"
247
+ if [[ -n "$EFFECTIVE_MODEL" ]]; then
248
+ MODEL_FLAG="--model $EFFECTIVE_MODEL"
249
+ log_info "Model: $EFFECTIVE_MODEL"
240
250
  fi
241
251
 
242
252
  unset CLAUDECODE 2>/dev/null || true
@@ -331,8 +341,36 @@ elif [[ -f "$SESSION_STATUS_FILE" ]]; then
331
341
  SESSION_STATUS=$(python3 "$SCRIPTS_DIR/check-session-status.py" \
332
342
  --status-file "$SESSION_STATUS_FILE" 2>/dev/null) || SESSION_STATUS="crashed"
333
343
  else
344
+ # No session-status.json found. Check if session produced git commits
345
+ # (e.g., context window exhausted after committing but before writing status).
334
346
  log_warn "Session ended without status file"
335
- SESSION_STATUS="crashed"
347
+ PROJECT_ROOT_CHECK="$(cd "$SCRIPT_DIR/.." && pwd)"
348
+ RECENT_FEATURE_COMMITS=""
349
+ if git -C "$PROJECT_ROOT_CHECK" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
350
+ # Use session directory creation time as lower bound for commit search
351
+ SESSION_START_ISO=$(python3 -c "
352
+ import os, sys
353
+ from datetime import datetime, timezone
354
+ p = sys.argv[1]
355
+ if os.path.isdir(p):
356
+ ts = os.path.getctime(p)
357
+ print(datetime.fromtimestamp(ts, tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'))
358
+ else:
359
+ print('')
360
+ " "$SESSION_DIR" 2>/dev/null) || SESSION_START_ISO=""
361
+ if [[ -n "$SESSION_START_ISO" ]]; then
362
+ RECENT_FEATURE_COMMITS=$(git -C "$PROJECT_ROOT_CHECK" log --since="$SESSION_START_ISO" --oneline --grep="$FEATURE_ID" 2>/dev/null | head -5)
363
+ fi
364
+ fi
365
+
366
+ if [[ -n "$RECENT_FEATURE_COMMITS" ]]; then
367
+ log_warn "Found commits from this session:"
368
+ echo "$RECENT_FEATURE_COMMITS" | sed 's/^/ - /'
369
+ log_warn "Treating as commit_missing (artifacts preserved for retry)"
370
+ SESSION_STATUS="commit_missing"
371
+ else
372
+ SESSION_STATUS="crashed"
373
+ fi
336
374
  fi
337
375
 
338
376
  if [[ "$SESSION_STATUS" == "success" ]]; then
@@ -90,6 +90,7 @@ spawn_and_wait_session() {
90
90
  local bootstrap_prompt="$4"
91
91
  local session_dir="$5"
92
92
  local max_retries="$6"
93
+ local feature_model="${7:-}"
93
94
 
94
95
  local session_log="$session_dir/logs/session.log"
95
96
  local progress_json="$session_dir/logs/progress.json"
@@ -108,8 +109,9 @@ spawn_and_wait_session() {
108
109
  fi
109
110
 
110
111
  local model_flag=""
111
- if [[ -n "$MODEL" ]]; then
112
- model_flag="--model $MODEL"
112
+ local effective_model="${feature_model:-$MODEL}"
113
+ if [[ -n "$effective_model" ]]; then
114
+ model_flag="--model $effective_model"
113
115
  fi
114
116
 
115
117
  # Unset CLAUDECODE to prevent "nested session" error when launched from
@@ -648,7 +650,13 @@ sys.exit(1)
648
650
  fi
649
651
 
650
652
  log_info "Generating bootstrap prompt..."
651
- python3 "$SCRIPTS_DIR/generate-bootstrap-prompt.py" "${prompt_args[@]}" >/dev/null 2>&1
653
+ local gen_output
654
+ gen_output=$(python3 "$SCRIPTS_DIR/generate-bootstrap-prompt.py" "${prompt_args[@]}" 2>/dev/null) || {
655
+ log_error "Failed to generate bootstrap prompt for $feature_id"
656
+ return 1
657
+ }
658
+ local feature_model
659
+ feature_model=$(echo "$gen_output" | python3 -c "import json,sys; print(json.load(sys.stdin).get('model',''))" 2>/dev/null || echo "")
652
660
 
653
661
  # Dry-Run: Print info and exit
654
662
  if [[ "$dry_run" == true ]]; then
@@ -664,6 +672,13 @@ sys.exit(1)
664
672
  else
665
673
  log_info "Mode: auto-detect (from complexity)"
666
674
  fi
675
+ if [[ -n "$feature_model" ]]; then
676
+ log_info "Feature Model: $feature_model"
677
+ elif [[ -n "${MODEL:-}" ]]; then
678
+ log_info "Model (env): $MODEL"
679
+ else
680
+ log_info "Model: (CLI default)"
681
+ fi
667
682
  echo ""
668
683
  log_info "Bootstrap prompt written to:"
669
684
  echo " $bootstrap_prompt"
@@ -736,7 +751,7 @@ sys.exit(1)
736
751
 
737
752
  spawn_and_wait_session \
738
753
  "$feature_id" "$feature_list" "$session_id" \
739
- "$bootstrap_prompt" "$session_dir" 999
754
+ "$bootstrap_prompt" "$session_dir" 999 "$feature_model"
740
755
  local session_status="$_SPAWN_RESULT"
741
756
 
742
757
  # Auto-push after successful session
@@ -850,6 +865,14 @@ main() {
850
865
  log_info "Resuming existing pipeline..."
851
866
  fi
852
867
 
868
+ # Auto-detect available models + validate feature model fields
869
+ bash "$SCRIPT_DIR/scripts/detect-models.sh" --quiet 2>/dev/null || true
870
+ if [[ -f ".prizmkit/available-models.json" ]]; then
871
+ python3 "$SCRIPTS_DIR/validate-feature-models.py" \
872
+ --feature-list "$feature_list" \
873
+ --models-file ".prizmkit/available-models.json" 2>&1 | head -5 || true
874
+ fi
875
+
853
876
  # Print header
854
877
  echo ""
855
878
  echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
@@ -992,7 +1015,13 @@ for f in data.get('stuck_features', []):
992
1015
  fi
993
1016
  fi
994
1017
 
995
- python3 "$SCRIPTS_DIR/generate-bootstrap-prompt.py" "${main_prompt_args[@]}" >/dev/null 2>&1
1018
+ local gen_output
1019
+ gen_output=$(python3 "$SCRIPTS_DIR/generate-bootstrap-prompt.py" "${main_prompt_args[@]}" 2>/dev/null) || {
1020
+ log_error "Failed to generate bootstrap prompt for $feature_id"
1021
+ continue
1022
+ }
1023
+ local feature_model
1024
+ feature_model=$(echo "$gen_output" | python3 -c "import json,sys; print(json.load(sys.stdin).get('model',''))" 2>/dev/null || echo "")
996
1025
 
997
1026
  # Update current session tracking (atomic write via temp file)
998
1027
  python3 -c "
@@ -1020,11 +1049,14 @@ os.replace(tmp, target)
1020
1049
 
1021
1050
  # Spawn session and wait
1022
1051
  log_info "Spawning AI CLI session: $session_id"
1052
+ if [[ -n "$feature_model" ]]; then
1053
+ log_info "Feature model: $feature_model"
1054
+ fi
1023
1055
  _SPAWN_RESULT=""
1024
1056
 
1025
1057
  spawn_and_wait_session \
1026
1058
  "$feature_id" "$feature_list" "$session_id" \
1027
- "$bootstrap_prompt" "$session_dir" "$MAX_RETRIES"
1059
+ "$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$feature_model"
1028
1060
  local session_status="$_SPAWN_RESULT"
1029
1061
 
1030
1062
  # Auto-push after successful session
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # ============================================================
5
+ # detect-models.sh - CLI model detection adapter
6
+ #
7
+ # Detects available AI models for the current CLI platform.
8
+ # Usage: ./detect-models.sh [--quiet]
9
+ # Writes: $PROJECT_ROOT/.prizmkit/available-models.json
10
+ # ============================================================
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ source "$SCRIPT_DIR/../lib/common.sh" && prizm_detect_cli_and_platform
14
+
15
+ # --- Parse flags ---
16
+ QUIET=false
17
+ for arg in "$@"; do
18
+ case "$arg" in
19
+ --quiet) QUIET=true ;;
20
+ *) log_warn "Unknown argument: $arg" ;;
21
+ esac
22
+ done
23
+
24
+ # --- Project root ---
25
+ PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
26
+
27
+ # --- CLI version ---
28
+ CLI_VERSION="$($CLI_CMD -v 2>&1 | head -1 | sed 's/^[^0-9]*//')" || CLI_VERSION="unknown"
29
+
30
+ # --- Platform-specific detection ---
31
+ MODELS_JSON="[]"
32
+ DEFAULT_MODEL=""
33
+ MODEL_SWITCH_SUPPORTED="false"
34
+ DETECTION_METHOD="unknown"
35
+
36
+ case "$PLATFORM" in
37
+ codebuddy)
38
+ DETECTION_METHOD="probe"
39
+ MODEL_SWITCH_SUPPORTED="true"
40
+
41
+ PROBE_OUTPUT="$(echo "x" | $CLI_CMD --print -y --model _probe_ 2>&1)" || true
42
+
43
+ MODELS_JSON="$(python3 -c "
44
+ import sys, re, json
45
+
46
+ output = sys.stdin.read()
47
+ models = []
48
+
49
+ # Look for 'Currently supported models' line and extract model names
50
+ for line in output.splitlines():
51
+ if 'supported model' in line.lower():
52
+ # Extract everything after the colon/are:
53
+ match = re.search(r'(?:are|:)\s*(.+)', line, re.IGNORECASE)
54
+ if match:
55
+ raw = match.group(1)
56
+ # Split by comma, clean up
57
+ for m in raw.split(','):
58
+ m = m.strip().rstrip('.')
59
+ if m:
60
+ models.append(m)
61
+
62
+ # Fallback: look for model-like identifiers anywhere
63
+ if not models:
64
+ models = re.findall(r'claude-[\w.-]+', output)
65
+ models = list(dict.fromkeys(models)) # dedupe preserving order
66
+
67
+ print(json.dumps(models))
68
+ " <<< "$PROBE_OUTPUT")" || MODELS_JSON="[]"
69
+
70
+ # Set default_model to first model if available
71
+ DEFAULT_MODEL="$(python3 -c "
72
+ import json, sys
73
+ models = json.loads(sys.stdin.read())
74
+ print(models[0] if models else '')
75
+ " <<< "$MODELS_JSON")" || DEFAULT_MODEL=""
76
+ ;;
77
+
78
+ claude)
79
+ DETECTION_METHOD="self-report"
80
+ MODEL_SWITCH_SUPPORTED="false"
81
+ MODELS_JSON="[]"
82
+
83
+ DEFAULT_MODEL="$($CLI_CMD -p "Reply with ONLY your model identifier, nothing else" \
84
+ --dangerously-skip-permissions --no-session-persistence 2>&1 \
85
+ | head -1 | tr -d '[:space:]')" || DEFAULT_MODEL="unknown"
86
+ ;;
87
+
88
+ *)
89
+ DETECTION_METHOD="help-parse"
90
+ MODEL_SWITCH_SUPPORTED="false"
91
+
92
+ HELP_OUTPUT="$($CLI_CMD --help 2>&1)" || HELP_OUTPUT=""
93
+
94
+ MODELS_JSON="$(python3 -c "
95
+ import sys, re, json
96
+
97
+ output = sys.stdin.read()
98
+ models = re.findall(r'claude-[\w.-]+', output)
99
+ models = list(dict.fromkeys(models)) # dedupe preserving order
100
+ print(json.dumps(models))
101
+ " <<< "$HELP_OUTPUT")" || MODELS_JSON="[]"
102
+ ;;
103
+ esac
104
+
105
+ # --- Write output JSON atomically ---
106
+ mkdir -p "$PROJECT_ROOT/.prizmkit"
107
+
108
+ OUTPUT_FILE="$PROJECT_ROOT/.prizmkit/available-models.json"
109
+ TMP_FILE="$(mktemp "${OUTPUT_FILE}.XXXXXX")"
110
+
111
+ # Determine cli short name
112
+ CLI_SHORT="$(basename "$CLI_CMD")"
113
+
114
+ python3 -c "
115
+ import json, sys
116
+ from datetime import datetime, timezone
117
+
118
+ data = {
119
+ 'cli': sys.argv[1],
120
+ 'cli_version': sys.argv[2],
121
+ 'platform': sys.argv[3],
122
+ 'models': json.loads(sys.argv[4]),
123
+ 'default_model': sys.argv[5],
124
+ 'model_switch_supported': sys.argv[6] == 'true',
125
+ 'detection_method': sys.argv[7],
126
+ 'updated_at': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
127
+ }
128
+
129
+ with open(sys.argv[8], 'w') as f:
130
+ json.dump(data, f, indent=2)
131
+ f.write('\n')
132
+ " "$CLI_SHORT" "$CLI_VERSION" "$PLATFORM" "$MODELS_JSON" "$DEFAULT_MODEL" \
133
+ "$MODEL_SWITCH_SUPPORTED" "$DETECTION_METHOD" "$TMP_FILE"
134
+
135
+ mv "$TMP_FILE" "$OUTPUT_FILE"
136
+
137
+ # --- Summary ---
138
+ if [[ "$QUIET" != "true" ]]; then
139
+ MODEL_COUNT="$(python3 -c "import json; print(len(json.loads(open('$OUTPUT_FILE').read())['models']))")"
140
+ log_info "Model detection complete"
141
+ log_info " CLI: $CLI_SHORT ($CLI_VERSION)"
142
+ log_info " Platform: $PLATFORM"
143
+ log_info " Method: $DETECTION_METHOD"
144
+ log_info " Models: $MODEL_COUNT detected"
145
+ if [[ -n "$DEFAULT_MODEL" ]]; then
146
+ log_info " Default: $DEFAULT_MODEL"
147
+ fi
148
+ log_info " Switch: $MODEL_SWITCH_SUPPORTED"
149
+ log_info " Written to: $OUTPUT_FILE"
150
+ fi
@@ -630,9 +630,11 @@ def main():
630
630
  emit_failure(err)
631
631
 
632
632
  # Success
633
+ feature_model = feature.get("model", "")
633
634
  output = {
634
635
  "success": True,
635
636
  "output_path": os.path.abspath(args.output),
637
+ "model": feature_model,
636
638
  }
637
639
  print(json.dumps(output, indent=2, ensure_ascii=False))
638
640
  sys.exit(0)
@@ -25,15 +25,45 @@ from collections import Counter
25
25
  from datetime import datetime, timezone
26
26
 
27
27
 
28
- # Phase keywords for detection
28
+ # Ordered pipeline phases — index defines forward-only progression.
29
+ # Phase detection is monotonic: once a phase is reached, earlier phases
30
+ # cannot be re-entered (prevents false positives from file content mentions).
31
+ PHASE_ORDER = ["specify", "plan", "analyze", "implement", "code-review", "retrospective", "commit"]
32
+
33
+ # Keywords for phase detection.
34
+ # "strong" keywords are skill invocations — high confidence, but still
35
+ # forward-only (a mention of prizmkit-plan while in commit phase is noise).
36
+ # "weak" keywords are contextual hints — also forward-only, used when no
37
+ # strong keyword matches.
29
38
  PHASE_KEYWORDS = {
30
- "specify": ["prizmkit-specify", "spec.md", "specification", "gathering requirements"],
31
- "plan": ["prizmkit-plan", "plan.md", "architecture", "design plan", "task checklist", "task breakdown"],
32
- "analyze": ["prizmkit-analyze", "cross-check", "consistency", "analyzing"],
33
- "implement": ["prizmkit-implement", "implement", "TDD", "coding", "writing code"],
34
- "code-review": ["prizmkit-code-review", "code review", "review verdict", "reviewing"],
35
- "retrospective": ["prizmkit-retrospective", "retrospective", "knowledge distillation", "TRAPS", ".prizm-docs/ sync", "structural sync"],
36
- "commit": ["prizmkit-committer", "git commit", "feat(", "fix(", "committing"],
39
+ "specify": {
40
+ "strong": ["prizmkit-specify"],
41
+ "weak": ["spec.md", "specification", "gathering requirements"],
42
+ },
43
+ "plan": {
44
+ "strong": ["prizmkit-plan"],
45
+ "weak": ["plan.md", "architecture", "task checklist", "task breakdown"],
46
+ },
47
+ "analyze": {
48
+ "strong": ["prizmkit-analyze"],
49
+ "weak": ["cross-check", "consistency analysis", "analyzing"],
50
+ },
51
+ "implement": {
52
+ "strong": ["prizmkit-implement"],
53
+ "weak": ["implement", "writing code", "TDD", "coding"],
54
+ },
55
+ "code-review": {
56
+ "strong": ["prizmkit-code-review"],
57
+ "weak": ["code review", "review verdict", "reviewing"],
58
+ },
59
+ "retrospective": {
60
+ "strong": ["prizmkit-retrospective"],
61
+ "weak": ["retrospective", "structural sync", "knowledge distillation", ".prizm-docs/ sync", "memory sedimentation"],
62
+ },
63
+ "commit": {
64
+ "strong": ["prizmkit-committer"],
65
+ "weak": ["git commit", "committing", "feat(", "fix("],
66
+ },
37
67
  }
38
68
 
39
69
 
@@ -171,15 +201,42 @@ class ProgressTracker:
171
201
  pass
172
202
 
173
203
  def _detect_phase(self, text):
174
- """Detect pipeline phase from text content."""
204
+ """Detect pipeline phase from text content.
205
+
206
+ Uses monotonic (forward-only) progression to avoid false positives.
207
+ Strong keywords (skill invocations) always trigger; weak keywords
208
+ only trigger for phases at or ahead of the current position.
209
+ """
175
210
  text_lower = text.lower()
176
- for phase, keywords in PHASE_KEYWORDS.items():
177
- for keyword in keywords:
211
+ current_idx = (
212
+ PHASE_ORDER.index(self.current_phase)
213
+ if self.current_phase in PHASE_ORDER
214
+ else -1
215
+ )
216
+
217
+ for phase, kw_groups in PHASE_KEYWORDS.items():
218
+ phase_idx = PHASE_ORDER.index(phase)
219
+
220
+ # Check strong keywords — high confidence but still forward-only
221
+ for keyword in kw_groups.get("strong", []):
178
222
  if keyword.lower() in text_lower:
179
- self.current_phase = phase
180
- if phase not in self.detected_phases:
181
- self.detected_phases.append(phase)
182
- return
223
+ if phase_idx >= current_idx:
224
+ self.current_phase = phase
225
+ if phase not in self.detected_phases:
226
+ self.detected_phases.append(phase)
227
+ return
228
+ # Backward strong match — skip this phase but do NOT
229
+ # early-return, so forward weak matches can still fire.
230
+ break
231
+
232
+ # Check weak keywords — only trigger for forward phases
233
+ if phase_idx >= current_idx:
234
+ for keyword in kw_groups.get("weak", []):
235
+ if keyword.lower() in text_lower:
236
+ self.current_phase = phase
237
+ if phase not in self.detected_phases:
238
+ self.detected_phases.append(phase)
239
+ return
183
240
 
184
241
  def _extract_tool_summary(self, raw_input):
185
242
  """Extract a human-readable summary from tool input JSON string."""
@@ -462,15 +462,11 @@ def action_update(args, feature_list_path, state_dir):
462
462
  error_out("Failed to update feature-list.json: {}".format(err))
463
463
  return
464
464
  else:
465
+ # crashed / failed / timed_out — preserve all artifacts for debugging.
466
+ # Previous behavior deleted everything via cleanup_feature_artifacts(),
467
+ # making it impossible to diagnose why the session failed.
465
468
  fs["retry_count"] = fs.get("retry_count", 0) + 1
466
469
 
467
- cleaned = cleanup_feature_artifacts(
468
- feature_list_path=feature_list_path,
469
- state_dir=state_dir,
470
- feature_id=feature_id,
471
- project_root=args.project_root,
472
- )
473
-
474
470
  if fs["retry_count"] >= max_retries:
475
471
  fs["status"] = "failed"
476
472
  target_status = "failed"
@@ -479,8 +475,9 @@ def action_update(args, feature_list_path, state_dir):
479
475
  target_status = "pending"
480
476
 
481
477
  fs["resume_from_phase"] = None
482
- fs["sessions"] = []
483
- fs["last_session_id"] = None
478
+ # Keep sessions list and last_session_id for debugging
479
+ # fs["sessions"] = []
480
+ # fs["last_session_id"] = None
484
481
 
485
482
  err = update_feature_in_list(feature_list_path, feature_id, target_status)
486
483
  if err:
@@ -514,8 +511,8 @@ def action_update(args, feature_list_path, state_dir):
514
511
  summary["degraded_reason"] = session_status
515
512
  summary["restart_policy"] = "finalization_retry"
516
513
  elif session_status != "success":
517
- summary["restart_policy"] = "full_restart"
518
- summary["cleanup_performed"] = cleaned
514
+ summary["restart_policy"] = "preserve_and_retry"
515
+ summary["artifacts_preserved"] = True
519
516
 
520
517
  print(json.dumps(summary, indent=2, ensure_ascii=False))
521
518
 
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env python3
2
+ """Validate model fields in feature-list.json against available-models.json."""
3
+
4
+ import argparse
5
+ import json
6
+ import os
7
+ import sys
8
+
9
+
10
+ def validate_feature(feature, models_data):
11
+ """Check a single feature's model field and print warnings if needed."""
12
+ feature_id = feature.get("id", "unknown")
13
+ model = feature.get("model")
14
+ if not model:
15
+ return
16
+
17
+ model_switch_supported = models_data.get("model_switch_supported", True)
18
+ cli = models_data.get("cli", "unknown")
19
+ available_models = models_data.get("models", [])
20
+
21
+ if not model_switch_supported:
22
+ print(
23
+ f"\u26a0 {feature_id}: CLI '{cli}' does not support --model switching "
24
+ f"(model field '{model}' will be ignored)"
25
+ )
26
+ elif available_models and model not in available_models:
27
+ print(f"\u26a0 {feature_id}: Model '{model}' not in available models list")
28
+
29
+
30
+ def main():
31
+ parser = argparse.ArgumentParser(
32
+ description="Validate model fields in feature-list.json against available-models.json"
33
+ )
34
+ parser.add_argument("--feature-list", required=True, help="Path to feature-list.json")
35
+ parser.add_argument("--models-file", required=True, help="Path to available-models.json")
36
+ args = parser.parse_args()
37
+
38
+ if not os.path.exists(args.models_file):
39
+ sys.exit(0)
40
+
41
+ with open(args.feature_list) as f:
42
+ feature_data = json.load(f)
43
+
44
+ with open(args.models_file) as f:
45
+ models_data = json.load(f)
46
+
47
+ features = feature_data if isinstance(feature_data, list) else feature_data.get("features", [])
48
+
49
+ for feature in features:
50
+ validate_feature(feature, models_data)
51
+ for sub in feature.get("sub_features", []):
52
+ validate_feature(sub, models_data)
53
+
54
+
55
+ if __name__ == "__main__":
56
+ main()
@@ -31,6 +31,20 @@ You are the **session orchestrator**. Implement Feature {{FEATURE_ID}}: "{{FEATU
31
31
 
32
32
  {{GLOBAL_CONTEXT}}
33
33
 
34
+ ## ⚠️ Context Budget Rules (CRITICAL — read before any phase)
35
+
36
+ You are running in headless mode with a FINITE context window. Exceeding it will crash the session and lose all work. Follow these rules strictly:
37
+
38
+ 1. **context-snapshot.md is your single source of truth** — After Phase 1 builds it, ALWAYS read context-snapshot.md instead of re-reading individual source files
39
+ 2. **Never re-read your own writes** — After you create/modify a file, do NOT read it back to verify. Trust your write was correct.
40
+ 3. **Stay focused** — Do NOT explore code unrelated to this feature. No curiosity-driven reads.
41
+ 4. **One task at a time** — In Phase 3 (implement), complete and test one task before starting the next.
42
+ 5. **Minimize tool output** — When running commands, use `| head -20` or `| tail -20` to limit output. Never dump entire test suites or logs.
43
+ 6. **Write session-status.json early** — Write a preliminary status file at the START of Phase 3, not just at the end.
44
+ 7. **Incremental commits when possible** — If a feature has multiple independent tasks, commit after each completed task rather than one big commit at the end.
45
+
46
+ ---
47
+
34
48
  ## PrizmKit Directory Convention
35
49
 
36
50
  ```
@@ -108,6 +122,18 @@ If plan.md missing, write it directly:
108
122
 
109
123
  ### Phase 3: Implement
110
124
 
125
+ **Before starting implementation**, write a preliminary session-status.json to `{{SESSION_STATUS_PATH}}`:
126
+ ```json
127
+ {
128
+ "status": "partial",
129
+ "current_phase": 3,
130
+ "feature_id": "{{FEATURE_ID}}",
131
+ "session_id": "{{SESSION_ID}}",
132
+ "started_at": "<current ISO timestamp>"
133
+ }
134
+ ```
135
+ This ensures the pipeline sees a "partial" status even if the session crashes mid-implementation.
136
+
111
137
  For each task in plan.md Tasks section:
112
138
  1. Read the relevant section from `context-snapshot.md` (no need to re-read individual files)
113
139
  2. Write/edit the code
@@ -31,6 +31,20 @@ You are the **session orchestrator**. Implement Feature {{FEATURE_ID}}: "{{FEATU
31
31
 
32
32
  {{GLOBAL_CONTEXT}}
33
33
 
34
+ ## ⚠️ Context Budget Rules (CRITICAL — read before any phase)
35
+
36
+ You are running in headless mode with a FINITE context window. Exceeding it will crash the session and lose all work. Follow these rules strictly:
37
+
38
+ 1. **context-snapshot.md is your single source of truth** — After Phase 1 builds it, ALWAYS read context-snapshot.md instead of re-reading individual source files
39
+ 2. **Never re-read your own writes** — After you create/modify a file, do NOT read it back to verify. Trust your write was correct.
40
+ 3. **Stay focused** — Do NOT explore code unrelated to this feature. No curiosity-driven reads.
41
+ 4. **One task at a time** — In Phase 3 (implement), complete and test one task before starting the next.
42
+ 5. **Minimize tool output** — When running commands, use `| head -20` or `| tail -20` to limit output. Never dump entire test suites or logs.
43
+ 6. **Write session-status.json early** — Write a preliminary status file at the START of Phase 3, not just at the end.
44
+ 7. **Incremental commits when possible** — If a feature has multiple independent tasks, commit after each completed task rather than one big commit at the end.
45
+
46
+ ---
47
+
34
48
  ## PrizmKit Directory Convention
35
49
 
36
50
  ```
@@ -118,6 +132,18 @@ If either missing, write them yourself:
118
132
 
119
133
  ### Phase 3: Implement — Dev Subagent
120
134
 
135
+ **Before spawning Dev**, write a preliminary session-status.json to `{{SESSION_STATUS_PATH}}`:
136
+ ```json
137
+ {
138
+ "status": "partial",
139
+ "current_phase": 3,
140
+ "feature_id": "{{FEATURE_ID}}",
141
+ "session_id": "{{SESSION_ID}}",
142
+ "started_at": "<current ISO timestamp>"
143
+ }
144
+ ```
145
+ This ensures the pipeline sees a "partial" status even if the session crashes mid-implementation.
146
+
121
147
  Spawn Dev subagent (Agent tool, subagent_type="prizm-dev-team-dev", run_in_background=false).
122
148
 
123
149
  Prompt: