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.
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/README.md +837 -385
- package/bundled/dev-pipeline/retry-feature.sh +43 -5
- package/bundled/dev-pipeline/run.sh +38 -6
- package/bundled/dev-pipeline/scripts/detect-models.sh +150 -0
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +2 -0
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +72 -15
- package/bundled/dev-pipeline/scripts/update-feature-status.py +8 -11
- package/bundled/dev-pipeline/scripts/validate-feature-models.py +56 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +26 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +26 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +26 -0
- package/bundled/dev-pipeline/templates/feature-list-schema.json +4 -0
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/templates/hooks/prizm-post-merge.sh +6 -0
- package/package.json +1 -1
- package/src/scaffold.js +35 -0
- package/src/upgrade.js +2 -0
|
@@ -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
|
|
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 "$
|
|
239
|
-
MODEL_FLAG="--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
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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":
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
177
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
self.detected_phases
|
|
182
|
-
|
|
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
|
-
|
|
483
|
-
fs["
|
|
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"] = "
|
|
518
|
-
summary["
|
|
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:
|