prizmkit 1.0.29 → 1.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  {
2
- "frameworkVersion": "1.0.29",
3
- "bundledAt": "2026-03-17T08:36:23.833Z",
4
- "bundledFrom": "726da22"
2
+ "frameworkVersion": "1.0.35",
3
+ "bundledAt": "2026-03-17T13:12:38.845Z",
4
+ "bundledFrom": "2597a15"
5
5
  }
@@ -100,3 +100,4 @@ DEV-14: 若 `npm test` 中存在 pre-existing 失败,不得忽略——必须
100
100
  - 发送 COMPLETION_SIGNAL 标志所有任务完成
101
101
  - 发送 ESCALATION 上报接口歧义或任务阻塞
102
102
  - 接收 TASK_ASSIGNMENT 获取分配的工作
103
+
@@ -121,3 +121,4 @@ REV-10: 禁止使用 timeout 命令(macOS 不兼容)。运行测试时直接
121
121
  - 发送 COMPLETION_SIGNAL(含判定结果)标志完成
122
122
  - 发送 ISSUE_REPORT 报告 CRITICAL 发现
123
123
  - 接收 TASK_ASSIGNMENT 获取分配的工作
124
+
@@ -148,6 +148,11 @@ cmd_start() {
148
148
  log_info "Bug fix list: $bug_list"
149
149
  log_info "Log file: $LOG_FILE"
150
150
 
151
+ # Unset CLAUDECODE to allow spawning nested Claude Code sessions.
152
+ # When this daemon is launched from within a Claude Code session, the env var
153
+ # is inherited and blocks child claude processes with "nested sessions" error.
154
+ unset CLAUDECODE 2>/dev/null || true
155
+
151
156
  {
152
157
  echo ""
153
158
  echo "================================================================"
@@ -8,11 +8,11 @@ set -euo pipefail
8
8
  # log consolidation, and lifecycle commands.
9
9
  #
10
10
  # Usage:
11
- # ./launch-daemon.sh start [feature-list.json] [--env "KEY=VAL ..."]
11
+ # ./launch-daemon.sh start [feature-list.json] [--mode <mode>] [--env "KEY=VAL ..."]
12
12
  # ./launch-daemon.sh stop
13
13
  # ./launch-daemon.sh status
14
14
  # ./launch-daemon.sh logs [--lines N] [--follow]
15
- # ./launch-daemon.sh restart [feature-list.json] [--env "KEY=VAL ..."]
15
+ # ./launch-daemon.sh restart [feature-list.json] [--mode <mode>] [--env "KEY=VAL ..."]
16
16
  #
17
17
  # NOTE:
18
18
  # In AI skill sessions, always use this daemon wrapper.
@@ -92,6 +92,7 @@ clean_stale_pid() {
92
92
  cmd_start() {
93
93
  local feature_list=""
94
94
  local env_overrides=""
95
+ local mode_override=""
95
96
 
96
97
  # Parse arguments
97
98
  while [[ $# -gt 0 ]]; do
@@ -105,6 +106,23 @@ cmd_start() {
105
106
  env_overrides="$1"
106
107
  shift
107
108
  ;;
109
+ --mode)
110
+ shift
111
+ if [[ $# -eq 0 ]]; then
112
+ log_error "--mode requires a value (lite|standard|full|self-evolve)"
113
+ exit 1
114
+ fi
115
+ case "$1" in
116
+ lite|standard|full|self-evolve)
117
+ mode_override="$1"
118
+ ;;
119
+ *)
120
+ log_error "Invalid mode: $1 (must be lite, standard, full, or self-evolve)"
121
+ exit 1
122
+ ;;
123
+ esac
124
+ shift
125
+ ;;
108
126
  *)
109
127
  feature_list="$1"
110
128
  shift
@@ -152,8 +170,15 @@ cmd_start() {
152
170
 
153
171
  # Build environment prefix
154
172
  local env_cmd=""
173
+ local env_parts=""
155
174
  if [[ -n "$env_overrides" ]]; then
156
- env_cmd="env $env_overrides"
175
+ env_parts="$env_overrides"
176
+ fi
177
+ if [[ -n "$mode_override" ]]; then
178
+ env_parts="${env_parts:+$env_parts }PIPELINE_MODE=$mode_override"
179
+ fi
180
+ if [[ -n "$env_parts" ]]; then
181
+ env_cmd="env $env_parts"
157
182
  fi
158
183
 
159
184
  # Record start time
@@ -174,6 +199,9 @@ cmd_start() {
174
199
  log_info "Launching pipeline..."
175
200
  log_info "Feature list: $feature_list"
176
201
  log_info "Log file: $LOG_FILE"
202
+ if [[ -n "$mode_override" ]]; then
203
+ log_info "Mode: $mode_override"
204
+ fi
177
205
  if [[ -n "$env_overrides" ]]; then
178
206
  log_info "Environment overrides: $env_overrides"
179
207
  fi
@@ -184,6 +212,9 @@ cmd_start() {
184
212
  echo "================================================================"
185
213
  echo " Pipeline Daemon Started: $start_time"
186
214
  echo " Feature list: $feature_list"
215
+ if [[ -n "$mode_override" ]]; then
216
+ echo " Mode: $mode_override"
217
+ fi
187
218
  if [[ -n "$env_overrides" ]]; then
188
219
  echo " Environment: $env_overrides"
189
220
  fi
@@ -191,6 +222,11 @@ cmd_start() {
191
222
  echo ""
192
223
  } >> "$LOG_FILE"
193
224
 
225
+ # Unset CLAUDECODE to allow spawning nested Claude Code sessions.
226
+ # When this daemon is launched from within a Claude Code session, the env var
227
+ # is inherited and blocks child claude processes with "nested sessions" error.
228
+ unset CLAUDECODE 2>/dev/null || true
229
+
194
230
  # Launch with nohup + disown for full detachment
195
231
  if [[ -n "$env_cmd" ]]; then
196
232
  nohup $env_cmd "$RUN_SCRIPT" run "$feature_list" >> "$LOG_FILE" 2>&1 &
@@ -207,12 +243,13 @@ cmd_start() {
207
243
  # Write start metadata (atomic)
208
244
  python3 -c "
209
245
  import json, sys, os
210
- pid, started_at, feature_list, env_overrides, log_file, state_dir = int(sys.argv[1]), sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5], sys.argv[6]
246
+ pid, started_at, feature_list, env_overrides, log_file, state_dir, mode = int(sys.argv[1]), sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5], sys.argv[6], sys.argv[7]
211
247
  data = {
212
248
  'pid': pid,
213
249
  'started_at': started_at,
214
250
  'feature_list': feature_list,
215
251
  'env_overrides': env_overrides,
252
+ 'mode': mode,
216
253
  'log_file': log_file
217
254
  }
218
255
  target = os.path.join(state_dir, '.pipeline-meta.json')
@@ -220,7 +257,7 @@ tmp = target + '.tmp'
220
257
  with open(tmp, 'w') as f:
221
258
  json.dump(data, f, indent=2)
222
259
  os.replace(tmp, target)
223
- " "$pipeline_pid" "$start_time" "$feature_list" "$env_overrides" "$LOG_FILE" "$STATE_DIR" 2>/dev/null || true
260
+ " "$pipeline_pid" "$start_time" "$feature_list" "$env_overrides" "$LOG_FILE" "$STATE_DIR" "$mode_override" 2>/dev/null || true
224
261
 
225
262
  # Wait briefly and verify
226
263
  sleep 2
@@ -269,8 +306,9 @@ cmd_stop() {
269
306
 
270
307
  log_info "Stopping pipeline (PID: $pid)..."
271
308
 
272
- # Send SIGTERM for graceful shutdown (triggers run.sh cleanup trap)
273
- kill -TERM "$pid" 2>/dev/null || true
309
+ # Kill the entire process group to include child processes (claude-internal, etc.)
310
+ # First try SIGTERM to the process group (negative PID)
311
+ kill -TERM -- -"$pid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
274
312
 
275
313
  # Wait up to 30 seconds for graceful exit
276
314
  local waited=0
@@ -282,10 +320,10 @@ cmd_stop() {
282
320
  waited=$((waited + 1))
283
321
  done
284
322
 
285
- # Force kill if still alive
323
+ # Force kill if still alive (process group first, then individual)
286
324
  if kill -0 "$pid" 2>/dev/null; then
287
325
  log_warn "Process did not exit after 30s, sending SIGKILL..."
288
- kill -9 "$pid" 2>/dev/null || true
326
+ kill -9 -- -"$pid" 2>/dev/null || kill -9 "$pid" 2>/dev/null || true
289
327
  sleep 1
290
328
  fi
291
329
 
@@ -510,17 +548,23 @@ show_help() {
510
548
  Usage: launch-daemon.sh <command> [options]
511
549
 
512
550
  Commands:
513
- start [feature-list.json] [--env "K=V ..."] Start pipeline in background
551
+ start [feature-list.json] [--mode <mode>] [--env "K=V ..."] Start pipeline in background
514
552
  stop Gracefully stop pipeline
515
553
  status Check if pipeline is running
516
554
  logs [--lines N] [--follow] View pipeline logs
517
- restart [feature-list.json] [--env "K=V ..."] Stop + start pipeline
555
+ restart [feature-list.json] [--mode <mode>] [--env "K=V ..."] Stop + start pipeline
518
556
  help Show this help
519
557
 
558
+ Options:
559
+ --mode <lite|standard|full|self-evolve> Override pipeline mode for all features
560
+ --env "KEY=VAL ..." Set environment variables
561
+
520
562
  Examples:
521
563
  ./launch-daemon.sh start # Start with default feature-list.json
522
564
  ./launch-daemon.sh start my-features.json # Start with custom feature list
565
+ ./launch-daemon.sh start --mode self-evolve # Self-evolve mode (framework development)
523
566
  ./launch-daemon.sh start --env "MAX_RETRIES=5 SESSION_TIMEOUT=7200"
567
+ ./launch-daemon.sh start feature-list.json --mode self-evolve --env "VERBOSE=1"
524
568
  ./launch-daemon.sh status # Check if running (JSON on stdout)
525
569
  ./launch-daemon.sh logs --follow # Live log tailing
526
570
  ./launch-daemon.sh logs --lines 100 # Last 100 lines
@@ -84,18 +84,23 @@ spawn_and_wait_session() {
84
84
  model_flag="--model $MODEL"
85
85
  fi
86
86
 
87
+ # Unset CLAUDECODE to prevent "nested session" error when launched from
88
+ # within an existing Claude Code session (e.g. via launch-bugfix-daemon.sh).
89
+ unset CLAUDECODE 2>/dev/null || true
90
+
87
91
  case "$CLI_CMD" in
88
92
  *claude*)
93
+ # Claude Code: prompt via -p, --dangerously-skip-permissions for auto-accept
89
94
  "$CLI_CMD" \
90
- --print \
91
95
  -p "$(cat "$bootstrap_prompt")" \
92
- --yes \
96
+ --dangerously-skip-permissions \
93
97
  $verbose_flag \
94
98
  $stream_json_flag \
95
99
  $model_flag \
96
100
  > "$session_log" 2>&1 &
97
101
  ;;
98
102
  *)
103
+ # CodeBuddy (cbc) and others: prompt via stdin, -y for auto-accept
99
104
  "$CLI_CMD" \
100
105
  --print \
101
106
  -y \
@@ -198,6 +203,9 @@ cleanup() {
198
203
  echo ""
199
204
  log_warn "Received interrupt signal. Saving state..."
200
205
 
206
+ # Kill all child processes (claude-internal, heartbeat, progress parser, etc.)
207
+ kill 0 2>/dev/null || true
208
+
201
209
  if [[ -n "$BUG_LIST" && -f "$BUG_LIST" ]]; then
202
210
  python3 "$SCRIPTS_DIR/update-bug-status.py" \
203
211
  --bug-list "$BUG_LIST" \
@@ -537,7 +545,7 @@ bug_id, session_id, state_dir = sys.argv[1], sys.argv[2], sys.argv[3]
537
545
  data = {
538
546
  'bug_id': bug_id,
539
547
  'session_id': session_id,
540
- 'started_at': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
548
+ 'started_at': datetime.now(datetime.UTC).strftime('%Y-%m-%dT%H:%M:%SZ')
541
549
  }
542
550
  target = os.path.join(state_dir, 'current-session.json')
543
551
  tmp = target + '.tmp'
@@ -27,6 +27,7 @@ set -euo pipefail
27
27
  # LOG_CLEANUP_ENABLED Run periodic log cleanup (default: 1)
28
28
  # LOG_RETENTION_DAYS Delete logs older than N days (default: 14)
29
29
  # LOG_MAX_TOTAL_MB Keep total logs under N MB via oldest-first cleanup (default: 1024)
30
+ # PIPELINE_MODE Override mode for all features: lite|standard|full|self-evolve (used by daemon)
30
31
  # ============================================================
31
32
 
32
33
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -98,13 +99,16 @@ spawn_and_wait_session() {
98
99
  model_flag="--model $MODEL"
99
100
  fi
100
101
 
102
+ # Unset CLAUDECODE to prevent "nested session" error when launched from
103
+ # within an existing Claude Code session (e.g. via launch-daemon.sh).
104
+ unset CLAUDECODE 2>/dev/null || true
105
+
101
106
  case "$CLI_CMD" in
102
107
  *claude*)
103
- # Claude Code: prompt via -p argument, --yes for auto-accept
108
+ # Claude Code: prompt via -p argument, --dangerously-skip-permissions for auto-accept
104
109
  "$CLI_CMD" \
105
- --print \
106
110
  -p "$(cat "$bootstrap_prompt")" \
107
- --yes \
111
+ --dangerously-skip-permissions \
108
112
  $verbose_flag \
109
113
  $stream_json_flag \
110
114
  $model_flag \
@@ -247,6 +251,9 @@ cleanup() {
247
251
  echo ""
248
252
  log_warn "Received interrupt signal. Saving state..."
249
253
 
254
+ # Kill all child processes (claude-internal, heartbeat, progress parser, etc.)
255
+ kill 0 2>/dev/null || true
256
+
250
257
  if [[ -n "$FEATURE_LIST" && -f "$FEATURE_LIST" ]]; then
251
258
  python3 "$SCRIPTS_DIR/update-feature-status.py" \
252
259
  --feature-list "$FEATURE_LIST" \
@@ -326,11 +333,11 @@ run_one() {
326
333
  exit 1
327
334
  fi
328
335
  case "$1" in
329
- lite|standard|full)
336
+ lite|standard|full|self-evolve)
330
337
  mode_override="$1"
331
338
  ;;
332
339
  *)
333
- log_error "Invalid mode: $1 (must be lite, standard, or full)"
340
+ log_error "Invalid mode: $1 (must be lite, standard, full, or self-evolve)"
334
341
  exit 1
335
342
  ;;
336
343
  esac
@@ -403,6 +410,14 @@ run_one() {
403
410
  check_dependencies
404
411
  run_log_cleanup
405
412
 
413
+ # Auto-detect framework repo: if scripts/bundle.js exists, enable self-evolve mode
414
+ local project_root
415
+ project_root=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || echo "")
416
+ if [[ -z "$mode_override" && -n "$project_root" && -f "$project_root/scripts/bundle.js" ]]; then
417
+ log_info "Detected PrizmKit framework repo — auto-enabling self-evolve mode"
418
+ mode_override="self-evolve"
419
+ fi
420
+
406
421
  # Verify feature exists
407
422
  local feature_title
408
423
  feature_title=$(python3 -c "
@@ -586,6 +601,37 @@ sys.exit(1)
586
601
 
587
602
  echo ""
588
603
  if [[ "$session_status" == "success" ]]; then
604
+ # Self-evolve mode: run framework validation after successful session
605
+ if [[ "$mode_override" == "self-evolve" ]]; then
606
+ log_info "Self-evolve mode: running framework validation..."
607
+ if bash "$SCRIPTS_DIR/validate-framework.sh" 2>&1; then
608
+ log_success "Framework validation passed"
609
+ else
610
+ log_warn "Framework validation failed — review issues above"
611
+ session_status="framework_validation_failed"
612
+ fi
613
+
614
+ # Check for reload_needed marker in session status
615
+ local session_status_file="$session_dir/session-status.json"
616
+ if [[ -f "$session_status_file" ]]; then
617
+ local reload_needed
618
+ reload_needed=$(python3 -c "
619
+ import json, sys
620
+ with open(sys.argv[1]) as f:
621
+ data = json.load(f)
622
+ print(data.get('reload_needed', False))
623
+ " "$session_status_file" 2>/dev/null || echo "False")
624
+ if [[ "$reload_needed" == "True" ]]; then
625
+ echo ""
626
+ log_warn "╔══════════════════════════════════════════════════════════════╗"
627
+ log_warn "║ RELOAD NEEDED: This session modified pipeline skills or ║"
628
+ log_warn "║ templates that are used by the dev-pipeline itself. ║"
629
+ log_warn "║ Changes will take effect in the NEXT session. ║"
630
+ log_warn "╚══════════════════════════════════════════════════════════════╝"
631
+ fi
632
+ fi
633
+ fi
634
+
589
635
  log_success "════════════════════════════════════════════════════"
590
636
  log_success " $feature_id completed successfully!"
591
637
  log_success "════════════════════════════════════════════════════"
@@ -738,15 +784,36 @@ for f in data.get('stuck_features', []):
738
784
  mkdir -p "$session_dir/logs"
739
785
 
740
786
  local bootstrap_prompt="$session_dir/bootstrap-prompt.md"
741
- python3 "$SCRIPTS_DIR/generate-bootstrap-prompt.py" \
742
- --feature-list "$feature_list" \
743
- --feature-id "$feature_id" \
744
- --session-id "$session_id" \
745
- --run-id "$run_id" \
746
- --retry-count "$retry_count" \
747
- --resume-phase "$resume_phase" \
748
- --state-dir "$STATE_DIR" \
749
- --output "$bootstrap_prompt" >/dev/null 2>&1
787
+
788
+ local main_prompt_args=(
789
+ --feature-list "$feature_list"
790
+ --feature-id "$feature_id"
791
+ --session-id "$session_id"
792
+ --run-id "$run_id"
793
+ --retry-count "$retry_count"
794
+ --resume-phase "$resume_phase"
795
+ --state-dir "$STATE_DIR"
796
+ --output "$bootstrap_prompt"
797
+ )
798
+
799
+ # Support PIPELINE_MODE env var (set by launch-daemon.sh --mode)
800
+ if [[ -n "${PIPELINE_MODE:-}" ]]; then
801
+ main_prompt_args+=(--mode "$PIPELINE_MODE")
802
+ fi
803
+
804
+ # Auto-detect framework repo: if scripts/bundle.js exists, enable self-evolve mode
805
+ if [[ -z "${PIPELINE_MODE:-}" ]]; then
806
+ local _project_root
807
+ _project_root=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || echo "")
808
+ if [[ -n "$_project_root" && -f "$_project_root/scripts/bundle.js" ]]; then
809
+ if [[ $session_count -eq 0 ]]; then
810
+ log_info "Detected PrizmKit framework repo — auto-enabling self-evolve mode"
811
+ fi
812
+ main_prompt_args+=(--mode "self-evolve")
813
+ fi
814
+ fi
815
+
816
+ python3 "$SCRIPTS_DIR/generate-bootstrap-prompt.py" "${main_prompt_args[@]}" >/dev/null 2>&1
750
817
 
751
818
  # Update current session tracking (atomic write via temp file)
752
819
  python3 -c "
@@ -756,7 +823,7 @@ feature_id, session_id, state_dir = sys.argv[1], sys.argv[2], sys.argv[3]
756
823
  data = {
757
824
  'feature_id': feature_id,
758
825
  'session_id': session_id,
759
- 'started_at': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
826
+ 'started_at': datetime.now(datetime.UTC).strftime('%Y-%m-%dT%H:%M:%SZ')
760
827
  }
761
828
  target = os.path.join(state_dir, 'current-session.json')
762
829
  tmp = target + '.tmp'
@@ -806,7 +873,7 @@ show_help() {
806
873
  echo "Single Feature Options (run <feature-id>):"
807
874
  echo " --dry-run Generate bootstrap prompt only, don't spawn session"
808
875
  echo " --resume-phase N Override resume phase (default: auto-detect)"
809
- echo " --mode <lite|standard|full> Override pipeline mode (bypasses estimated_complexity)"
876
+ echo " --mode <lite|standard|full|self-evolve> Override pipeline mode (bypasses estimated_complexity)"
810
877
  echo " --clean Delete artifacts and reset before running"
811
878
  echo " --no-reset Skip feature status reset step"
812
879
  echo " --timeout N Session timeout in seconds (default: 0 = no limit)"
@@ -821,6 +888,7 @@ show_help() {
821
888
  echo " LOG_CLEANUP_ENABLED Run log cleanup before execution (default: 1)"
822
889
  echo " LOG_RETENTION_DAYS Delete logs older than N days (default: 14)"
823
890
  echo " LOG_MAX_TOTAL_MB Keep total logs under N MB (default: 1024)"
891
+ echo " PIPELINE_MODE Override mode for all features: lite|standard|full|self-evolve"
824
892
  echo ""
825
893
  echo "Examples:"
826
894
  echo " ./run.sh run # Run all features"
@@ -887,7 +955,7 @@ case "${1:-run}" in
887
955
  unset CLAUDECODE
888
956
  case "$CLI_CMD" in
889
957
  *claude*)
890
- "$CLI_CMD" --print -p "$test_prompt" --dangerously-skip-permissions --no-session-persistence $local_model_flag > "$tmpfile" 2>/dev/null
958
+ "$CLI_CMD" -p "$test_prompt" --dangerously-skip-permissions --no-session-persistence $local_model_flag > "$tmpfile" 2>/dev/null
891
959
  ;;
892
960
  *)
893
961
  echo "$test_prompt" | "$CLI_CMD" --print -y $local_model_flag > "$tmpfile" 2>/dev/null
@@ -84,7 +84,7 @@ def parse_args():
84
84
  )
85
85
  parser.add_argument(
86
86
  "--mode",
87
- choices=["lite", "standard", "full"],
87
+ choices=["lite", "standard", "full", "self-evolve"],
88
88
  default=None,
89
89
  help="Override pipeline mode (default: auto-detect from complexity)",
90
90
  )
@@ -276,14 +276,35 @@ def process_mode_blocks(content, pipeline_mode, init_done):
276
276
  """Process pipeline mode and init conditional blocks.
277
277
 
278
278
  Keeps the block matching the current mode, removes the others.
279
+ For self-evolve mode: keeps SELF_EVOLVE blocks AND FULL blocks
280
+ (since self-evolve is full + framework guardrails).
279
281
  """
282
+ is_self_evolve = pipeline_mode == "self-evolve"
283
+
284
+ # Step 1: Handle SELF_EVOLVE conditional blocks first
285
+ se_open = "{{IF_MODE_SELF_EVOLVE}}"
286
+ se_close = "{{END_IF_MODE_SELF_EVOLVE}}"
287
+ if is_self_evolve:
288
+ # Keep content, remove tags
289
+ content = content.replace(se_open + "\n", "")
290
+ content = content.replace(se_open, "")
291
+ content = content.replace(se_close + "\n", "")
292
+ content = content.replace(se_close, "")
293
+ else:
294
+ # Remove entire SELF_EVOLVE blocks
295
+ pattern = re.escape(se_open) + r".*?" + re.escape(se_close) + r"\n?"
296
+ content = re.sub(pattern, "", content, flags=re.DOTALL)
297
+
298
+ # Step 2: Handle lite/standard/full blocks
299
+ # self-evolve inherits full mode for the standard tier blocks
300
+ effective_mode = "full" if is_self_evolve else pipeline_mode
280
301
  modes = ["lite", "standard", "full"]
281
302
 
282
303
  for mode in modes:
283
304
  tag_open = "{{{{IF_MODE_{}}}}}".format(mode.upper())
284
305
  tag_close = "{{{{END_IF_MODE_{}}}}}".format(mode.upper())
285
306
 
286
- if mode == pipeline_mode:
307
+ if mode == effective_mode:
287
308
  # Keep content, remove tags
288
309
  content = content.replace(tag_open + "\n", "")
289
310
  content = content.replace(tag_open, "")
@@ -427,6 +448,8 @@ def build_replacements(args, feature, features, global_context, script_dir):
427
448
  else:
428
449
  pipeline_mode = determine_pipeline_mode(complexity)
429
450
 
451
+ is_self_evolve = pipeline_mode == "self-evolve"
452
+
430
453
  # Auto-detect resume: if all planning artifacts exist and resume_phase
431
454
  # is "null" (fresh start), skip to Phase 6
432
455
  effective_resume = args.resume_phase
@@ -468,6 +491,7 @@ def build_replacements(args, feature, features, global_context, script_dir):
468
491
  "{{HAS_PLAN}}": "true" if artifacts["has_plan"] else "false",
469
492
  "{{HAS_TASKS}}": "true" if artifacts["has_tasks"] else "false",
470
493
  "{{ARTIFACTS_COMPLETE}}": "true" if artifacts["all_complete"] else "false",
494
+ "{{IS_SELF_EVOLVE}}": "true" if is_self_evolve else "false",
471
495
  }
472
496
 
473
497
  return replacements, effective_resume
@@ -539,6 +563,7 @@ def main():
539
563
  "lite": "bootstrap-tier1.md",
540
564
  "standard": "bootstrap-tier2.md",
541
565
  "full": "bootstrap-tier3.md",
566
+ "self-evolve": "bootstrap-tier3.md",
542
567
  }
543
568
  _tier_file = _tier_file_map.get(_mode, "bootstrap-tier2.md")
544
569
  _tier_path = os.path.join(script_dir, "..", "templates", _tier_file)
@@ -124,9 +124,9 @@ def load_feature_status(state_dir, feature_id, feature_list_status=None):
124
124
  )
125
125
  if not os.path.isfile(status_path):
126
126
  now = now_iso()
127
- return {
127
+ data = {
128
128
  "feature_id": feature_id,
129
- "status": "pending",
129
+ "status": feature_list_status if feature_list_status else "pending",
130
130
  "retry_count": 0,
131
131
  "max_retries": 3,
132
132
  "sessions": [],
@@ -135,13 +135,14 @@ def load_feature_status(state_dir, feature_id, feature_list_status=None):
135
135
  "created_at": now,
136
136
  "updated_at": now,
137
137
  }
138
+ return data
138
139
  data, err = load_json_file(status_path)
139
140
  if err:
140
141
  # If we can't read it, treat as pending
141
142
  now = now_iso()
142
143
  data = {
143
144
  "feature_id": feature_id,
144
- "status": "pending",
145
+ "status": feature_list_status if feature_list_status else "pending",
145
146
  "retry_count": 0,
146
147
  "max_retries": 3,
147
148
  "sessions": [],
@@ -685,7 +686,12 @@ def _estimate_remaining_time(features, state_dir, counts, feature_list_data=None
685
686
 
686
687
 
687
688
  def action_status(feature_list_data, state_dir):
688
- """Print a formatted overview of all features and their status."""
689
+ """Print a formatted overview of all features and their status.
690
+
691
+ Status is read exclusively from feature-list.json (the single source of
692
+ truth). state_dir is only used for ETA estimation when session history
693
+ is available.
694
+ """
689
695
  features = feature_list_data.get("features", [])
690
696
  app_name = feature_list_data.get("app_name", "Unknown")
691
697
 
@@ -701,7 +707,7 @@ def action_status(feature_list_data, state_dir):
701
707
  }
702
708
  feature_lines = []
703
709
 
704
- # Build dependency info: feature_id -> list of dep_ids that are not completed
710
+ # Build status map from feature-list.json only
705
711
  status_map = {}
706
712
  for feature in features:
707
713
  if not isinstance(feature, dict):
@@ -709,8 +715,7 @@ def action_status(feature_list_data, state_dir):
709
715
  fid = feature.get("id")
710
716
  if not fid:
711
717
  continue
712
- fs = load_feature_status(state_dir, fid, feature.get("status"))
713
- status_map[fid] = fs.get("status", "pending")
718
+ status_map[fid] = feature.get("status", "pending")
714
719
 
715
720
  for feature in features:
716
721
  if not isinstance(feature, dict):
@@ -720,11 +725,7 @@ def action_status(feature_list_data, state_dir):
720
725
  if not fid:
721
726
  continue
722
727
 
723
- fs = load_feature_status(state_dir, fid, feature.get("status"))
724
- fstatus = fs.get("status", "pending")
725
- retry_count = fs.get("retry_count", 0)
726
- max_retries_val = fs.get("max_retries", 3)
727
- resume_phase = fs.get("resume_from_phase")
728
+ fstatus = feature.get("status", "pending")
728
729
 
729
730
  # Count statuses
730
731
  if fstatus in counts:
@@ -750,21 +751,7 @@ def action_status(feature_list_data, state_dir):
750
751
 
751
752
  # Build detail suffix
752
753
  detail = ""
753
- if fstatus == "in_progress":
754
- parts = []
755
- if retry_count > 0:
756
- parts.append("retry {}/{}".format(retry_count, max_retries_val))
757
- if resume_phase is not None:
758
- parts.append("CP-{}".format(resume_phase))
759
- if parts:
760
- detail = " ({})".format(", ".join(parts))
761
- elif fstatus == "failed":
762
- detail = " (failed after {} retries)".format(retry_count)
763
- elif fstatus == "commit_missing":
764
- detail = " (commit missing, retry {}/{})".format(retry_count, max_retries_val)
765
- elif fstatus == "docs_missing":
766
- detail = " (docs missing, retry {}/{})".format(retry_count, max_retries_val)
767
- elif fstatus == "pending":
754
+ if fstatus == "pending":
768
755
  # Check if blocked by dependencies
769
756
  deps = feature.get("dependencies", [])
770
757
  blocking = [
@@ -31,6 +31,40 @@ You are the **session orchestrator**. Implement Feature {{FEATURE_ID}}: "{{FEATU
31
31
 
32
32
  {{GLOBAL_CONTEXT}}
33
33
 
34
+ {{IF_MODE_SELF_EVOLVE}}
35
+ ## Framework Self-Development Context
36
+
37
+ **You are developing the PrizmKit framework itself.** This is NOT a regular project — you are modifying the tool that powers this pipeline. Extra guardrails apply.
38
+
39
+ ### Framework Structure
40
+
41
+ ```
42
+ core/skills/ — Skill definitions (each has _metadata.json)
43
+ core/agents/ — Agent .md definitions (YAML frontmatter required)
44
+ core/team/ — Team config (dev repo only, NOT installed)
45
+ dev-pipeline/ — Pipeline scripts + templates (installed with --pipeline)
46
+ templates/ — Bootstrap prompt templates (tier1/2/3)
47
+ scripts/ — Python/bash pipeline scripts
48
+ create-prizmkit/ — npm package / CLI installer
49
+ bundled/ — Pre-bundled assets (auto-generated, NEVER edit directly)
50
+ tests/ — Validation + unit tests
51
+ ```
52
+
53
+ ### 5 Key Invariants (MUST be preserved)
54
+
55
+ 1. **Skill ↔ _metadata.json 1:1 mapping**: Every directory in `core/skills/` MUST have a `_metadata.json`. Every `_metadata.json` must reference an existing skill directory.
56
+ 2. **Template variables resolve completely**: All `{{PLACEHOLDER}}` in `dev-pipeline/templates/` must be resolvable by `generate-bootstrap-prompt.py`. No unresolved placeholders in output.
57
+ 3. **Agent YAML frontmatter is valid**: Every `.md` in `core/agents/` must have valid YAML frontmatter with required fields (name, description, tools).
58
+ 4. **Bundle is generated, never hand-edited**: `create-prizmkit/bundled/` is auto-generated by `scripts/bundle.js`. Manual edits will be overwritten.
59
+ 5. **CI must pass**: `npm run ci` (validate-all + bundle + verify-bundle + eslint + vitest) must pass after every change.
60
+
61
+ ### Version Isolation
62
+
63
+ LLM context is frozen at prompt time. Modifying a skill source file during this session will NOT change the behavior of that skill within this session. The real risk is structural inconsistency.
64
+
65
+ **When you modify any file in `dev-pipeline/scripts/`, `dev-pipeline/templates/`, or `core/skills/` that this pipeline actively uses**: set `reload_needed: true` in `session-status.json`. The pipeline runner will warn the operator after session completion.
66
+ {{END_IF_MODE_SELF_EVOLVE}}
67
+
34
68
  ## PrizmKit Directory Convention
35
69
 
36
70
  ```
@@ -182,6 +216,17 @@ grep -c '^\- \[ \]' .prizmkit/specs/{{FEATURE_SLUG}}/tasks.md 2>/dev/null || ech
182
216
 
183
217
  Spawn Dev agent (Task tool, subagent_type="prizm-dev-team-dev", run_in_background=false).
184
218
 
219
+ {{IF_MODE_SELF_EVOLVE}}
220
+ **Framework Self-Evolve — Dev Extra Instructions**:
221
+ Append the following to the Dev agent prompt:
222
+ > "FRAMEWORK RULES (self-evolve mode):
223
+ > - If you modify any file in `core/skills/`, also update `_metadata.json` in the same skill directory.
224
+ > - If you modify `dev-pipeline/templates/*.md`, verify all `{{PLACEHOLDER}}` markers have matching entries in `generate-bootstrap-prompt.py`.
225
+ > - Before marking implementation complete, run `node tests/validate-all.js` and fix any failures.
226
+ > - NEVER directly modify files in `create-prizmkit/bundled/` — those are auto-generated by `scripts/bundle.js`.
227
+ > - If you modify any file in `dev-pipeline/scripts/` or `dev-pipeline/templates/` or `core/skills/` that this pipeline uses, note this in your Implementation Log for reload_needed tracking."
228
+ {{END_IF_MODE_SELF_EVOLVE}}
229
+
185
230
  Prompt:
186
231
  > "Read {{DEV_SUBAGENT_PATH}}. Implement feature {{FEATURE_ID}} (slug: {{FEATURE_SLUG}}) using TDD.
187
232
  > 1. Read `.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md` FIRST — all source files and context are there. Do NOT re-read individual source files.
@@ -210,6 +255,18 @@ All tasks `[x]`, tests pass.
210
255
 
211
256
  Spawn Reviewer agent (Task tool, subagent_type="prizm-dev-team-reviewer", run_in_background=false).
212
257
 
258
+ {{IF_MODE_SELF_EVOLVE}}
259
+ **Framework Self-Evolve — Reviewer Extra Instructions**:
260
+ Append the following to the Reviewer agent prompt:
261
+ > "FRAMEWORK REVIEW DIMENSIONS (self-evolve mode):
262
+ > In addition to standard code review, check:
263
+ > 1. **Structural integrity**: Every `core/skills/*/` must have `_metadata.json`. Run `node tests/validate-all.js` to verify.
264
+ > 2. **Template safety**: If any `dev-pipeline/templates/*.md` was modified, check that all `{{PLACEHOLDER}}` markers are properly balanced (open/close) and resolvable.
265
+ > 3. **Agent frontmatter**: If any `core/agents/*.md` was modified, validate YAML frontmatter has required fields (name, description, tools).
266
+ > 4. **CI gate**: Run `npm run ci` and report the result. Any failure is CRITICAL.
267
+ > 5. **Bundle safety**: Verify no files in `create-prizmkit/bundled/` were directly modified (check `git diff --name-only` for bundled/ changes)."
268
+ {{END_IF_MODE_SELF_EVOLVE}}
269
+
213
270
  Prompt:
214
271
  > "Read {{REVIEWER_SUBAGENT_PATH}}. For feature {{FEATURE_ID}} (slug: {{FEATURE_SLUG}}):
215
272
  > 1. Read `.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md` FIRST — Section 4 has original source files, 'Implementation Log' section lists exactly what Dev changed. Do NOT re-read source files that are NOT mentioned in the Implementation Log.
@@ -232,6 +289,27 @@ Wait for Reviewer to return.
232
289
 
233
290
  ### Phase 7: Summarize & Commit — DO NOT SKIP
234
291
 
292
+ {{IF_MODE_SELF_EVOLVE}}
293
+ **Framework Validation Gate (self-evolve mode)**:
294
+
295
+ Before proceeding with commit, run the full framework CI pipeline:
296
+
297
+ ```bash
298
+ bash {{VALIDATOR_SCRIPTS_DIR}}/validate-framework.sh
299
+ ```
300
+
301
+ - If ALL steps pass → proceed with commit below.
302
+ - If any step fails → fix the issue and re-run. Maximum 2 fix-and-retry rounds.
303
+ - After 2 failed rounds → mark session as `framework_validation_failed`, write session-status.json, and exit.
304
+
305
+ **reload_needed check**: Review the Implementation Log in context-snapshot.md. If ANY of these paths were modified:
306
+ - `dev-pipeline/scripts/*.py` or `dev-pipeline/scripts/*.sh`
307
+ - `dev-pipeline/templates/*.md`
308
+ - `core/skills/` (any skill used by the pipeline)
309
+
310
+ Then set `"reload_needed": true` in session-status.json.
311
+ {{END_IF_MODE_SELF_EVOLVE}}
312
+
235
313
  **For bug fixes**: skip `/prizmkit-summarize`, skip retrospective, and use `fix(<scope>):` commit prefix.
236
314
 
237
315
  **7a.** Check if feature already committed:
@@ -255,23 +255,14 @@ class TestActionUpdateDegradedStatuses:
255
255
 
256
256
  class TestActionStatusForDegradedStates:
257
257
  def test_status_output_contains_degraded_counters(self, feature_list_file, state_dir, capsys):
258
- # Prepare commit_missing and docs_missing statuses
259
- f1_dir = os.path.join(state_dir, "features", "F-001")
260
- f2_dir = os.path.join(state_dir, "features", "F-002")
261
- os.makedirs(f1_dir, exist_ok=True)
262
- os.makedirs(f2_dir, exist_ok=True)
263
-
264
- with open(os.path.join(f1_dir, "status.json"), "w", encoding="utf-8") as f:
265
- json.dump({"status": "commit_missing", "retry_count": 1, "max_retries": 3}, f)
266
- with open(os.path.join(f2_dir, "status.json"), "w", encoding="utf-8") as f:
267
- json.dump({"status": "docs_missing", "retry_count": 2, "max_retries": 3}, f)
268
-
258
+ # Update feature-list.json to set degraded statuses (single source of truth)
269
259
  with open(feature_list_file, "r", encoding="utf-8") as f:
270
260
  feature_list_data = json.load(f)
271
261
 
262
+ feature_list_data["features"][0]["status"] = "commit_missing"
263
+ feature_list_data["features"][1]["status"] = "docs_missing"
264
+
272
265
  ufs.action_status(feature_list_data, state_dir)
273
266
  out = capsys.readouterr().out
274
267
 
275
268
  assert "Commit Missing: 1 | Docs Missing: 1" in out
276
- assert "commit missing, retry 1/3" in out
277
- assert "docs missing, retry 2/3" in out
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.29",
2
+ "version": "1.0.35",
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.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prizmkit",
3
- "version": "1.0.29",
3
+ "version": "1.0.35",
4
4
  "description": "Create a new PrizmKit-powered project with clean initialization — no framework dev files, just what you need.",
5
5
  "type": "module",
6
6
  "bin": {