prizmkit 1.0.28 → 1.0.34
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/bin/create-prizmkit.js +16 -0
- package/bundled/VERSION.json +3 -3
- package/bundled/agents/prizm-dev-team-dev.md +10 -0
- package/bundled/agents/prizm-dev-team-reviewer.md +10 -0
- package/bundled/dev-pipeline/launch-bugfix-daemon.sh +5 -0
- package/bundled/dev-pipeline/launch-daemon.sh +55 -11
- package/bundled/dev-pipeline/run-bugfix.sh +11 -3
- package/bundled/dev-pipeline/run.sh +85 -17
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +27 -2
- package/bundled/dev-pipeline/scripts/update-feature-status.py +14 -27
- package/bundled/dev-pipeline/scripts/validate-framework.sh +87 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +78 -0
- package/bundled/dev-pipeline/tests/test_update_feature_status.py +4 -13
- package/bundled/skills/_metadata.json +1 -1
- package/package.json +1 -1
- package/src/clean.js +15 -6
- package/src/index.js +2 -0
- package/src/manifest.js +116 -0
- package/src/scaffold.js +39 -7
- package/src/upgrade.js +371 -0
package/bin/create-prizmkit.js
CHANGED
|
@@ -15,6 +15,7 @@ import { fileURLToPath } from 'url';
|
|
|
15
15
|
import { program } from 'commander';
|
|
16
16
|
import { runScaffold } from '../src/index.js';
|
|
17
17
|
import { runClean } from '../src/clean.js';
|
|
18
|
+
import { runUpgrade } from '../src/upgrade.js';
|
|
18
19
|
|
|
19
20
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
21
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
@@ -61,4 +62,19 @@ program
|
|
|
61
62
|
}
|
|
62
63
|
});
|
|
63
64
|
|
|
65
|
+
program
|
|
66
|
+
.command('upgrade [directory]')
|
|
67
|
+
.description('Upgrade PrizmKit to the latest version (removes orphaned files, updates changed files)')
|
|
68
|
+
.option('--dry-run', 'Show what would change without applying')
|
|
69
|
+
.option('--non-interactive', 'Skip confirmation prompt')
|
|
70
|
+
.option('--force', 'Overwrite all files including user-customizable ones')
|
|
71
|
+
.action(async (directory = '.', options) => {
|
|
72
|
+
try {
|
|
73
|
+
await runUpgrade(directory, options);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error(`\n Error: ${err.message}\n`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
64
80
|
program.parse();
|
package/bundled/VERSION.json
CHANGED
|
@@ -100,3 +100,13 @@ DEV-14: 若 `npm test` 中存在 pre-existing 失败,不得忽略——必须
|
|
|
100
100
|
- 发送 COMPLETION_SIGNAL 标志所有任务完成
|
|
101
101
|
- 发送 ESCALATION 上报接口歧义或任务阻塞
|
|
102
102
|
- 接收 TASK_ASSIGNMENT 获取分配的工作
|
|
103
|
+
|
|
104
|
+
### Framework Self-Development Rules (self-evolve mode)
|
|
105
|
+
|
|
106
|
+
When working in self-evolve mode (developing the PrizmKit framework itself), these additional rules apply:
|
|
107
|
+
|
|
108
|
+
1. **Skill metadata sync**: If you modify any file in `core/skills/<skill-name>/`, you MUST also update `core/skills/<skill-name>/_metadata.json` to reflect changes (description, version, dependencies).
|
|
109
|
+
2. **Template variable check**: If you modify any file in `dev-pipeline/templates/`, verify all `{{PLACEHOLDER}}` markers are resolvable by checking `dev-pipeline/scripts/generate-bootstrap-prompt.py` for matching entries.
|
|
110
|
+
3. **Pre-completion validation**: Before marking implementation complete, run `node tests/validate-all.js` and fix any failures.
|
|
111
|
+
4. **Bundle protection**: NEVER directly modify files in `create-prizmkit/bundled/`. These are auto-generated by `scripts/bundle.js` — your changes would be overwritten.
|
|
112
|
+
5. **Reload tracking**: If you modify files in `dev-pipeline/scripts/`, `dev-pipeline/templates/`, or `core/skills/` that the pipeline actively uses, note this in your Implementation Log so the orchestrator can set `reload_needed: true`.
|
|
@@ -121,3 +121,13 @@ REV-10: 禁止使用 timeout 命令(macOS 不兼容)。运行测试时直接
|
|
|
121
121
|
- 发送 COMPLETION_SIGNAL(含判定结果)标志完成
|
|
122
122
|
- 发送 ISSUE_REPORT 报告 CRITICAL 发现
|
|
123
123
|
- 接收 TASK_ASSIGNMENT 获取分配的工作
|
|
124
|
+
|
|
125
|
+
### Framework Self-Development Review (self-evolve mode)
|
|
126
|
+
|
|
127
|
+
When reviewing in self-evolve mode (framework is modifying itself), add these review dimensions:
|
|
128
|
+
|
|
129
|
+
1. **`_metadata.json` ↔ skill directory 1:1 mapping**: Verify every `core/skills/*/` has a `_metadata.json`, and every `_metadata.json` references a valid skill directory. Run `node tests/validate-all.js` to automate this check.
|
|
130
|
+
2. **Template variable completeness**: For modified `dev-pipeline/templates/*.md` files, verify all `{{PLACEHOLDER}}` markers have matching open/close tags and are resolvable by `generate-bootstrap-prompt.py`.
|
|
131
|
+
3. **Agent frontmatter validation**: For modified `core/agents/*.md` files, validate YAML frontmatter contains required fields: `name`, `description`, `tools`. Optionally check `model`, `skills`.
|
|
132
|
+
4. **CI gate execution**: Run `npm run ci` and report the full result. Any failure here is **CRITICAL** severity — the framework must always ship green.
|
|
133
|
+
5. **Bundle safety check**: Verify no files in `create-prizmkit/bundled/` were directly modified (use `git diff --name-only` to check). Direct modifications to bundled assets are always CRITICAL.
|
|
@@ -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
|
-
|
|
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
|
-
#
|
|
273
|
-
|
|
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
|
-
--
|
|
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.
|
|
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, --
|
|
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
|
-
--
|
|
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
|
|
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
|
-
|
|
742
|
-
|
|
743
|
-
--feature-
|
|
744
|
-
--
|
|
745
|
-
--
|
|
746
|
-
--
|
|
747
|
-
--
|
|
748
|
-
--
|
|
749
|
-
--
|
|
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.
|
|
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"
|
|
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 ==
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 == "
|
|
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 = [
|