prizmkit 1.0.12 → 1.0.14
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 +4 -1
- package/bundled/VERSION.json +3 -3
- package/bundled/adapters/claude/command-adapter.js +35 -4
- package/bundled/adapters/claude/rules-adapter.js +6 -58
- package/bundled/adapters/claude/team-adapter.js +2 -2
- package/bundled/adapters/codebuddy/agent-adapter.js +0 -1
- package/bundled/adapters/codebuddy/rules-adapter.js +30 -0
- package/bundled/adapters/shared/frontmatter.js +3 -1
- package/bundled/dev-pipeline/README.md +13 -3
- package/bundled/dev-pipeline/launch-bugfix-daemon.sh +10 -0
- package/bundled/dev-pipeline/launch-daemon.sh +18 -4
- package/bundled/dev-pipeline/lib/common.sh +105 -0
- package/bundled/dev-pipeline/retry-bug.sh +12 -0
- package/bundled/dev-pipeline/retry-feature.sh +12 -0
- package/bundled/dev-pipeline/run-bugfix.sh +71 -57
- package/bundled/dev-pipeline/run.sh +87 -57
- package/bundled/dev-pipeline/scripts/check-session-status.py +47 -2
- package/bundled/dev-pipeline/scripts/cleanup-logs.py +192 -0
- package/bundled/dev-pipeline/scripts/detect-stuck.py +15 -3
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +32 -27
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +23 -23
- package/bundled/dev-pipeline/scripts/update-feature-status.py +73 -2
- package/bundled/dev-pipeline/scripts/utils.py +22 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +38 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +39 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +39 -2
- package/bundled/dev-pipeline/templates/session-status-schema.json +7 -1
- package/bundled/dev-pipeline/tests/__init__.py +0 -0
- package/bundled/dev-pipeline/tests/conftest.py +133 -0
- package/bundled/dev-pipeline/tests/test_check_session.py +127 -0
- package/bundled/dev-pipeline/tests/test_cleanup_logs.py +119 -0
- package/bundled/dev-pipeline/tests/test_detect_stuck.py +207 -0
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +181 -0
- package/bundled/dev-pipeline/tests/test_generate_prompt.py +190 -0
- package/bundled/dev-pipeline/tests/test_init_bugfix_pipeline.py +153 -0
- package/bundled/dev-pipeline/tests/test_init_pipeline.py +241 -0
- package/bundled/dev-pipeline/tests/test_update_bug_status.py +142 -0
- package/bundled/dev-pipeline/tests/test_update_feature_status.py +277 -0
- package/bundled/dev-pipeline/tests/test_utils.py +141 -0
- package/bundled/rules/USAGE.md +153 -0
- package/bundled/rules/_rules-metadata.json +43 -0
- package/bundled/rules/general/prefer-linux-commands.md +9 -0
- package/bundled/rules/prizm/prizm-commit-workflow.md +10 -0
- package/bundled/rules/prizm/prizm-documentation.md +19 -0
- package/bundled/rules/prizm/prizm-progressive-loading.md +11 -0
- package/bundled/skills/_metadata.json +130 -67
- package/bundled/skills/app-planner/SKILL.md +252 -499
- package/bundled/skills/app-planner/assets/evaluation-guide.md +44 -0
- package/bundled/skills/app-planner/scripts/validate-and-generate.py +143 -4
- package/bundled/skills/bug-planner/SKILL.md +58 -13
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +5 -7
- package/bundled/skills/dev-pipeline-launcher/SKILL.md +16 -7
- package/bundled/skills/feature-workflow/SKILL.md +175 -234
- package/bundled/skills/prizm-kit/SKILL.md +17 -31
- package/bundled/skills/{prizmkit-adr-manager → prizmkit-tool-adr-manager}/SKILL.md +6 -7
- package/bundled/skills/{prizmkit-api-doc-generator → prizmkit-tool-api-doc-generator}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-bug-reproducer → prizmkit-tool-bug-reproducer}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-ci-cd-generator → prizmkit-tool-ci-cd-generator}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-db-migration → prizmkit-tool-db-migration}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-dependency-health → prizmkit-tool-dependency-health}/SKILL.md +3 -4
- package/bundled/skills/{prizmkit-deployment-strategy → prizmkit-tool-deployment-strategy}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-error-triage → prizmkit-tool-error-triage}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-log-analyzer → prizmkit-tool-log-analyzer}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-monitoring-setup → prizmkit-tool-monitoring-setup}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-onboarding-generator → prizmkit-tool-onboarding-generator}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-perf-profiler → prizmkit-tool-perf-profiler}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-security-audit → prizmkit-tool-security-audit}/SKILL.md +3 -4
- package/bundled/skills/{prizmkit-tech-debt-tracker → prizmkit-tool-tech-debt-tracker}/SKILL.md +3 -4
- package/bundled/skills/refactor-skill/SKILL.md +371 -0
- package/bundled/skills/refactor-workflow/SKILL.md +17 -119
- package/package.json +1 -1
- package/src/external-skills.js +71 -0
- package/src/index.js +62 -4
- package/src/metadata.js +36 -0
- package/src/scaffold.js +136 -32
- package/bundled/skills/prizmkit-bug-fix-workflow/SKILL.md +0 -356
- package/bundled/templates/claude-md-template.md +0 -38
- package/bundled/templates/codebuddy-md-template.md +0 -35
- /package/bundled/skills/{prizmkit-adr-manager → prizmkit-tool-adr-manager}/assets/adr-template.md +0 -0
|
@@ -17,9 +17,14 @@ set -euo pipefail
|
|
|
17
17
|
# MAX_RETRIES Max retries per bug (default: 3)
|
|
18
18
|
# SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)
|
|
19
19
|
# AI_CLI AI CLI command name (auto-detected: cbc or claude)
|
|
20
|
+
# CODEBUDDY_CLI Legacy alias for AI_CLI (deprecated, use AI_CLI instead)
|
|
21
|
+
# PRIZMKIT_PLATFORM Force platform: 'codebuddy' or 'claude' (auto-detected)
|
|
20
22
|
# VERBOSE Set to 1 to enable --verbose on AI CLI
|
|
21
23
|
# HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)
|
|
22
24
|
# HEARTBEAT_STALE_THRESHOLD Heartbeat stale threshold in seconds (default: 600)
|
|
25
|
+
# LOG_CLEANUP_ENABLED Run periodic log cleanup (default: 1)
|
|
26
|
+
# LOG_RETENTION_DAYS Delete logs older than N days (default: 14)
|
|
27
|
+
# LOG_MAX_TOTAL_MB Keep total logs under N MB via oldest-first cleanup (default: 1024)
|
|
23
28
|
# ============================================================
|
|
24
29
|
|
|
25
30
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
@@ -31,31 +36,14 @@ MAX_RETRIES=${MAX_RETRIES:-3}
|
|
|
31
36
|
SESSION_TIMEOUT=${SESSION_TIMEOUT:-0}
|
|
32
37
|
HEARTBEAT_STALE_THRESHOLD=${HEARTBEAT_STALE_THRESHOLD:-600}
|
|
33
38
|
HEARTBEAT_INTERVAL=${HEARTBEAT_INTERVAL:-30}
|
|
39
|
+
LOG_CLEANUP_ENABLED=${LOG_CLEANUP_ENABLED:-1}
|
|
40
|
+
LOG_RETENTION_DAYS=${LOG_RETENTION_DAYS:-14}
|
|
41
|
+
LOG_MAX_TOTAL_MB=${LOG_MAX_TOTAL_MB:-1024}
|
|
34
42
|
VERBOSE=${VERBOSE:-0}
|
|
35
43
|
|
|
36
|
-
#
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
elif [[ -n "${CODEBUDDY_CLI:-}" ]]; then
|
|
40
|
-
CLI_CMD="$CODEBUDDY_CLI"
|
|
41
|
-
elif command -v cbc &>/dev/null; then
|
|
42
|
-
CLI_CMD="cbc"
|
|
43
|
-
elif command -v claude &>/dev/null; then
|
|
44
|
-
CLI_CMD="claude"
|
|
45
|
-
else
|
|
46
|
-
echo "ERROR: No AI CLI found. Install CodeBuddy (cbc) or Claude Code (claude)." >&2
|
|
47
|
-
exit 1
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
|
-
# Platform detection
|
|
51
|
-
if [[ -n "${PRIZMKIT_PLATFORM:-}" ]]; then
|
|
52
|
-
PLATFORM="$PRIZMKIT_PLATFORM"
|
|
53
|
-
elif [[ "$CLI_CMD" == *"claude"* ]]; then
|
|
54
|
-
PLATFORM="claude"
|
|
55
|
-
else
|
|
56
|
-
PLATFORM="codebuddy"
|
|
57
|
-
fi
|
|
58
|
-
export PRIZMKIT_PLATFORM="$PLATFORM"
|
|
44
|
+
# Source shared common helpers (CLI/platform detection + logs + deps)
|
|
45
|
+
source "$SCRIPT_DIR/lib/common.sh"
|
|
46
|
+
prizm_detect_cli_and_platform
|
|
59
47
|
|
|
60
48
|
# Source shared heartbeat library
|
|
61
49
|
source "$SCRIPT_DIR/lib/heartbeat.sh"
|
|
@@ -66,20 +54,6 @@ detect_stream_json_support "$CLI_CMD"
|
|
|
66
54
|
# Bug list path (set in main, used by cleanup trap)
|
|
67
55
|
BUG_LIST=""
|
|
68
56
|
|
|
69
|
-
# Colors
|
|
70
|
-
RED='\033[0;31m'
|
|
71
|
-
GREEN='\033[0;32m'
|
|
72
|
-
YELLOW='\033[1;33m'
|
|
73
|
-
BLUE='\033[0;34m'
|
|
74
|
-
MAGENTA='\033[0;35m'
|
|
75
|
-
BOLD='\033[1m'
|
|
76
|
-
NC='\033[0m'
|
|
77
|
-
|
|
78
|
-
log_info() { echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
|
79
|
-
log_warn() { echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
|
80
|
-
log_error() { echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
|
81
|
-
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
|
82
|
-
|
|
83
57
|
# ============================================================
|
|
84
58
|
# Shared: Spawn AI CLI session and wait for result
|
|
85
59
|
# ============================================================
|
|
@@ -180,6 +154,20 @@ spawn_and_wait_session() {
|
|
|
180
154
|
session_status="crashed"
|
|
181
155
|
fi
|
|
182
156
|
|
|
157
|
+
if [[ "$session_status" == "success" ]]; then
|
|
158
|
+
local project_root
|
|
159
|
+
project_root="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
160
|
+
if git -C "$project_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
161
|
+
local dirty_files=""
|
|
162
|
+
dirty_files=$(git -C "$project_root" status --porcelain 2>/dev/null || true)
|
|
163
|
+
if [[ -n "$dirty_files" ]]; then
|
|
164
|
+
log_error "Session reported success but git working tree is not clean."
|
|
165
|
+
echo "$dirty_files" | sed 's/^/ - /'
|
|
166
|
+
session_status="failed"
|
|
167
|
+
fi
|
|
168
|
+
fi
|
|
169
|
+
fi
|
|
170
|
+
|
|
183
171
|
log_info "Session result: $session_status"
|
|
184
172
|
|
|
185
173
|
# Update bug status
|
|
@@ -220,13 +208,29 @@ trap cleanup SIGINT SIGTERM
|
|
|
220
208
|
# ============================================================
|
|
221
209
|
|
|
222
210
|
check_dependencies() {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
211
|
+
prizm_check_common_dependencies "$CLI_CMD"
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
run_log_cleanup() {
|
|
215
|
+
if [[ "$LOG_CLEANUP_ENABLED" != "1" ]]; then
|
|
216
|
+
return 0
|
|
226
217
|
fi
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
218
|
+
|
|
219
|
+
local cleanup_result
|
|
220
|
+
cleanup_result=$(python3 "$SCRIPTS_DIR/cleanup-logs.py" \
|
|
221
|
+
--state-dir "$STATE_DIR" \
|
|
222
|
+
--retention-days "$LOG_RETENTION_DAYS" \
|
|
223
|
+
--max-total-mb "$LOG_MAX_TOTAL_MB" 2>/dev/null) || {
|
|
224
|
+
log_warn "Log cleanup failed (continuing)"
|
|
225
|
+
return 0
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
local deleted reclaimed_kb
|
|
229
|
+
deleted=$(echo "$cleanup_result" | python3 -c "import sys,json; print(json.load(sys.stdin).get('deleted_files', 0))" 2>/dev/null || echo "0")
|
|
230
|
+
reclaimed_kb=$(echo "$cleanup_result" | python3 -c "import sys,json; print(int(json.load(sys.stdin).get('reclaimed_bytes', 0)/1024))" 2>/dev/null || echo "0")
|
|
231
|
+
|
|
232
|
+
if [[ "$deleted" -gt 0 ]]; then
|
|
233
|
+
log_info "Log cleanup: deleted $deleted files, reclaimed ${reclaimed_kb}KB"
|
|
230
234
|
fi
|
|
231
235
|
}
|
|
232
236
|
|
|
@@ -269,6 +273,7 @@ run_one() {
|
|
|
269
273
|
fi
|
|
270
274
|
|
|
271
275
|
check_dependencies
|
|
276
|
+
run_log_cleanup
|
|
272
277
|
|
|
273
278
|
# Initialize state if needed
|
|
274
279
|
if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
|
|
@@ -285,14 +290,14 @@ run_one() {
|
|
|
285
290
|
local bug_title
|
|
286
291
|
bug_title=$(python3 -c "
|
|
287
292
|
import json, sys
|
|
288
|
-
with open(
|
|
293
|
+
with open(sys.argv[1]) as f:
|
|
289
294
|
data = json.load(f)
|
|
290
295
|
for bug in data.get('bugs', []):
|
|
291
|
-
if bug.get('id') ==
|
|
296
|
+
if bug.get('id') == sys.argv[2]:
|
|
292
297
|
print(bug.get('title', ''))
|
|
293
298
|
sys.exit(0)
|
|
294
299
|
sys.exit(1)
|
|
295
|
-
" 2>/dev/null) || {
|
|
300
|
+
" "$bug_list" "$bug_id" 2>/dev/null) || {
|
|
296
301
|
log_error "Bug $bug_id not found in $bug_list"
|
|
297
302
|
exit 1
|
|
298
303
|
}
|
|
@@ -300,14 +305,14 @@ sys.exit(1)
|
|
|
300
305
|
local bug_severity
|
|
301
306
|
bug_severity=$(python3 -c "
|
|
302
307
|
import json, sys
|
|
303
|
-
with open(
|
|
308
|
+
with open(sys.argv[1]) as f:
|
|
304
309
|
data = json.load(f)
|
|
305
310
|
for bug in data.get('bugs', []):
|
|
306
|
-
if bug.get('id') ==
|
|
311
|
+
if bug.get('id') == sys.argv[2]:
|
|
307
312
|
print(bug.get('severity', 'medium'))
|
|
308
313
|
sys.exit(0)
|
|
309
314
|
sys.exit(1)
|
|
310
|
-
" 2>/dev/null) || bug_severity="medium"
|
|
315
|
+
" "$bug_list" "$bug_id" 2>/dev/null) || bug_severity="medium"
|
|
311
316
|
|
|
312
317
|
# Reset bug status
|
|
313
318
|
python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
@@ -362,14 +367,14 @@ sys.exit(1)
|
|
|
362
367
|
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
363
368
|
echo ""
|
|
364
369
|
|
|
365
|
-
|
|
370
|
+
cleanup_single_bug() {
|
|
366
371
|
echo ""
|
|
367
372
|
log_warn "Interrupted. Killing session..."
|
|
368
373
|
kill 0 2>/dev/null || true
|
|
369
374
|
log_info "Session log: $session_dir/logs/session.log"
|
|
370
375
|
exit 130
|
|
371
376
|
}
|
|
372
|
-
trap
|
|
377
|
+
trap cleanup_single_bug SIGINT SIGTERM
|
|
373
378
|
|
|
374
379
|
_SPAWN_RESULT=""
|
|
375
380
|
spawn_and_wait_session \
|
|
@@ -410,6 +415,7 @@ main() {
|
|
|
410
415
|
fi
|
|
411
416
|
|
|
412
417
|
check_dependencies
|
|
418
|
+
run_log_cleanup
|
|
413
419
|
|
|
414
420
|
# Initialize pipeline state if needed
|
|
415
421
|
if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
|
|
@@ -468,6 +474,7 @@ main() {
|
|
|
468
474
|
log_success " All bugs processed! Bug fix pipeline finished."
|
|
469
475
|
log_success " Total sessions: $session_count"
|
|
470
476
|
log_success "════════════════════════════════════════════════════"
|
|
477
|
+
rm -f "$STATE_DIR/current-session.json"
|
|
471
478
|
break
|
|
472
479
|
fi
|
|
473
480
|
|
|
@@ -515,18 +522,22 @@ main() {
|
|
|
515
522
|
--state-dir "$STATE_DIR" \
|
|
516
523
|
--output "$bootstrap_prompt" >/dev/null 2>&1
|
|
517
524
|
|
|
518
|
-
# Track current session
|
|
525
|
+
# Track current session (atomic write via temp file)
|
|
519
526
|
python3 -c "
|
|
520
|
-
import json
|
|
527
|
+
import json, sys, os
|
|
521
528
|
from datetime import datetime
|
|
529
|
+
bug_id, session_id, state_dir = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
522
530
|
data = {
|
|
523
|
-
'bug_id':
|
|
524
|
-
'session_id':
|
|
531
|
+
'bug_id': bug_id,
|
|
532
|
+
'session_id': session_id,
|
|
525
533
|
'started_at': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
526
534
|
}
|
|
527
|
-
|
|
535
|
+
target = os.path.join(state_dir, 'current-session.json')
|
|
536
|
+
tmp = target + '.tmp'
|
|
537
|
+
with open(tmp, 'w') as f:
|
|
528
538
|
json.dump(data, f, indent=2)
|
|
529
|
-
|
|
539
|
+
os.replace(tmp, target)
|
|
540
|
+
" "$bug_id" "$session_id" "$STATE_DIR"
|
|
530
541
|
|
|
531
542
|
# Spawn session
|
|
532
543
|
log_info "Spawning AI CLI session: $session_id"
|
|
@@ -566,6 +577,9 @@ show_help() {
|
|
|
566
577
|
echo " AI_CLI AI CLI command name (auto-detected: cbc or claude)"
|
|
567
578
|
echo " VERBOSE Set to 1 for verbose AI CLI output"
|
|
568
579
|
echo " HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)"
|
|
580
|
+
echo " LOG_CLEANUP_ENABLED Run log cleanup before execution (default: 1)"
|
|
581
|
+
echo " LOG_RETENTION_DAYS Delete logs older than N days (default: 14)"
|
|
582
|
+
echo " LOG_MAX_TOTAL_MB Keep total logs under N MB (default: 1024)"
|
|
569
583
|
echo ""
|
|
570
584
|
echo "Examples:"
|
|
571
585
|
echo " ./run-bugfix.sh run # Run all bugs"
|
|
@@ -17,12 +17,15 @@ set -euo pipefail
|
|
|
17
17
|
# Environment Variables:
|
|
18
18
|
# MAX_RETRIES Max retries per feature (default: 3)
|
|
19
19
|
# SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)
|
|
20
|
-
# AI_CLI AI CLI command name (
|
|
20
|
+
# AI_CLI AI CLI command name (override; also readable from .prizmkit/config.json)
|
|
21
21
|
# CODEBUDDY_CLI Legacy alias for AI_CLI (deprecated, use AI_CLI instead)
|
|
22
22
|
# PRIZMKIT_PLATFORM Force platform: 'codebuddy' or 'claude' (auto-detected)
|
|
23
23
|
# VERBOSE Set to 1 to enable --verbose on AI CLI (shows subagent output)
|
|
24
24
|
# HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)
|
|
25
25
|
# HEARTBEAT_STALE_THRESHOLD Heartbeat stale threshold in seconds (default: 600)
|
|
26
|
+
# LOG_CLEANUP_ENABLED Run periodic log cleanup (default: 1)
|
|
27
|
+
# LOG_RETENTION_DAYS Delete logs older than N days (default: 14)
|
|
28
|
+
# LOG_MAX_TOTAL_MB Keep total logs under N MB via oldest-first cleanup (default: 1024)
|
|
26
29
|
# ============================================================
|
|
27
30
|
|
|
28
31
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
@@ -34,32 +37,14 @@ MAX_RETRIES=${MAX_RETRIES:-3}
|
|
|
34
37
|
SESSION_TIMEOUT=${SESSION_TIMEOUT:-0}
|
|
35
38
|
HEARTBEAT_STALE_THRESHOLD=${HEARTBEAT_STALE_THRESHOLD:-600}
|
|
36
39
|
HEARTBEAT_INTERVAL=${HEARTBEAT_INTERVAL:-30}
|
|
40
|
+
LOG_CLEANUP_ENABLED=${LOG_CLEANUP_ENABLED:-1}
|
|
41
|
+
LOG_RETENTION_DAYS=${LOG_RETENTION_DAYS:-14}
|
|
42
|
+
LOG_MAX_TOTAL_MB=${LOG_MAX_TOTAL_MB:-1024}
|
|
37
43
|
VERBOSE=${VERBOSE:-0}
|
|
38
44
|
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
elif [[ -n "${CODEBUDDY_CLI:-}" ]]; then
|
|
43
|
-
CLI_CMD="$CODEBUDDY_CLI"
|
|
44
|
-
elif command -v cbc &>/dev/null; then
|
|
45
|
-
CLI_CMD="cbc"
|
|
46
|
-
elif command -v claude &>/dev/null; then
|
|
47
|
-
CLI_CMD="claude"
|
|
48
|
-
else
|
|
49
|
-
echo "ERROR: No AI CLI found. Install CodeBuddy (cbc) or Claude Code (claude)." >&2
|
|
50
|
-
exit 1
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
# Platform detection
|
|
54
|
-
if [[ -n "${PRIZMKIT_PLATFORM:-}" ]]; then
|
|
55
|
-
PLATFORM="$PRIZMKIT_PLATFORM"
|
|
56
|
-
elif [[ "$CLI_CMD" == *"claude"* ]]; then
|
|
57
|
-
PLATFORM="claude"
|
|
58
|
-
else
|
|
59
|
-
PLATFORM="codebuddy"
|
|
60
|
-
fi
|
|
61
|
-
|
|
62
|
-
export PRIZMKIT_PLATFORM="$PLATFORM"
|
|
45
|
+
# Source shared common helpers (CLI/platform detection + logs + deps)
|
|
46
|
+
source "$SCRIPT_DIR/lib/common.sh"
|
|
47
|
+
prizm_detect_cli_and_platform
|
|
63
48
|
|
|
64
49
|
# Source shared heartbeat library
|
|
65
50
|
source "$SCRIPT_DIR/lib/heartbeat.sh"
|
|
@@ -70,19 +55,6 @@ detect_stream_json_support "$CLI_CMD"
|
|
|
70
55
|
# Feature list path (set in main, used by cleanup trap)
|
|
71
56
|
FEATURE_LIST=""
|
|
72
57
|
|
|
73
|
-
# Colors for output
|
|
74
|
-
RED='\033[0;31m'
|
|
75
|
-
GREEN='\033[0;32m'
|
|
76
|
-
YELLOW='\033[1;33m'
|
|
77
|
-
BLUE='\033[0;34m'
|
|
78
|
-
BOLD='\033[1m'
|
|
79
|
-
NC='\033[0m'
|
|
80
|
-
|
|
81
|
-
log_info() { echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
|
82
|
-
log_warn() { echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
|
83
|
-
log_error() { echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
|
84
|
-
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
|
85
|
-
|
|
86
58
|
# ============================================================
|
|
87
59
|
# Shared: Spawn an AI CLI session and wait for result
|
|
88
60
|
# ============================================================
|
|
@@ -199,6 +171,49 @@ spawn_and_wait_session() {
|
|
|
199
171
|
session_status="crashed"
|
|
200
172
|
fi
|
|
201
173
|
|
|
174
|
+
if [[ "$session_status" == "success" ]]; then
|
|
175
|
+
local project_root
|
|
176
|
+
project_root="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
177
|
+
if git -C "$project_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
178
|
+
local dirty_files=""
|
|
179
|
+
dirty_files=$(git -C "$project_root" status --porcelain 2>/dev/null || true)
|
|
180
|
+
if [[ -n "$dirty_files" ]]; then
|
|
181
|
+
log_warn "Session reported success but git working tree is not clean."
|
|
182
|
+
echo "$dirty_files" | sed 's/^/ - /'
|
|
183
|
+
session_status="commit_missing"
|
|
184
|
+
else
|
|
185
|
+
# Bugfix commits (fix: / fix(scope):) are exempt from docs check.
|
|
186
|
+
local last_commit_subject=""
|
|
187
|
+
last_commit_subject=$(git -C "$project_root" log -1 --pretty=%s 2>/dev/null || true)
|
|
188
|
+
if [[ "$last_commit_subject" =~ ^fix(\(|:) ]]; then
|
|
189
|
+
log_info "Detected bugfix commit prefix; skipping docs check."
|
|
190
|
+
else
|
|
191
|
+
local docs_changed=""
|
|
192
|
+
docs_changed=$(git -C "$project_root" log --name-only --format="" -1 2>/dev/null \
|
|
193
|
+
| grep -E '(\.prizm-docs/|\.prizmkit/specs/REGISTRY\.md)' | head -1 || true)
|
|
194
|
+
if [[ -z "$docs_changed" ]]; then
|
|
195
|
+
log_warn "Session committed but no .prizm-docs or REGISTRY.md changes detected."
|
|
196
|
+
session_status="docs_missing"
|
|
197
|
+
fi
|
|
198
|
+
fi
|
|
199
|
+
fi
|
|
200
|
+
fi
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# Persist final pipeline-resolved status back to session-status.json
|
|
204
|
+
# only for post-check statuses introduced by the pipeline itself.
|
|
205
|
+
if [[ -f "$session_status_file" && ( "$session_status" == "commit_missing" || "$session_status" == "docs_missing" ) ]]; then
|
|
206
|
+
python3 -c "
|
|
207
|
+
import json, sys
|
|
208
|
+
p, st = sys.argv[1], sys.argv[2]
|
|
209
|
+
with open(p, 'r', encoding='utf-8') as f:
|
|
210
|
+
data = json.load(f)
|
|
211
|
+
data['status'] = st
|
|
212
|
+
with open(p, 'w', encoding='utf-8') as f:
|
|
213
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
214
|
+
" "$session_status_file" "$session_status" >/dev/null 2>&1 || true
|
|
215
|
+
fi
|
|
216
|
+
|
|
202
217
|
log_info "Session result: $session_status"
|
|
203
218
|
|
|
204
219
|
# Update feature status
|
|
@@ -240,23 +255,29 @@ trap cleanup SIGINT SIGTERM
|
|
|
240
255
|
# ============================================================
|
|
241
256
|
|
|
242
257
|
check_dependencies() {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
log_error "jq is required but not installed. Install with: brew install jq"
|
|
246
|
-
exit 1
|
|
247
|
-
fi
|
|
258
|
+
prizm_check_common_dependencies "$CLI_CMD"
|
|
259
|
+
}
|
|
248
260
|
|
|
249
|
-
|
|
250
|
-
if
|
|
251
|
-
|
|
252
|
-
exit 1
|
|
261
|
+
run_log_cleanup() {
|
|
262
|
+
if [[ "$LOG_CLEANUP_ENABLED" != "1" ]]; then
|
|
263
|
+
return 0
|
|
253
264
|
fi
|
|
254
265
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
266
|
+
local cleanup_result
|
|
267
|
+
cleanup_result=$(python3 "$SCRIPTS_DIR/cleanup-logs.py" \
|
|
268
|
+
--state-dir "$STATE_DIR" \
|
|
269
|
+
--retention-days "$LOG_RETENTION_DAYS" \
|
|
270
|
+
--max-total-mb "$LOG_MAX_TOTAL_MB" 2>/dev/null) || {
|
|
271
|
+
log_warn "Log cleanup failed (continuing)"
|
|
272
|
+
return 0
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
local deleted reclaimed_kb
|
|
276
|
+
deleted=$(echo "$cleanup_result" | python3 -c "import sys,json; print(json.load(sys.stdin).get('deleted_files', 0))" 2>/dev/null || echo "0")
|
|
277
|
+
reclaimed_kb=$(echo "$cleanup_result" | python3 -c "import sys,json; print(int(json.load(sys.stdin).get('reclaimed_bytes', 0)/1024))" 2>/dev/null || echo "0")
|
|
278
|
+
|
|
279
|
+
if [[ "$deleted" -gt 0 ]]; then
|
|
280
|
+
log_info "Log cleanup: deleted $deleted files, reclaimed ${reclaimed_kb}KB"
|
|
260
281
|
fi
|
|
261
282
|
}
|
|
262
283
|
|
|
@@ -371,6 +392,7 @@ run_one() {
|
|
|
371
392
|
fi
|
|
372
393
|
|
|
373
394
|
check_dependencies
|
|
395
|
+
run_log_cleanup
|
|
374
396
|
|
|
375
397
|
# Verify feature exists
|
|
376
398
|
local feature_title
|
|
@@ -536,8 +558,8 @@ sys.exit(1)
|
|
|
536
558
|
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
537
559
|
echo ""
|
|
538
560
|
|
|
539
|
-
# Override cleanup trap for single-feature mode
|
|
540
|
-
|
|
561
|
+
# Override cleanup trap for single-feature mode (use distinct name to avoid overwriting global cleanup)
|
|
562
|
+
cleanup_single_feature() {
|
|
541
563
|
echo ""
|
|
542
564
|
log_warn "Interrupted. Killing session..."
|
|
543
565
|
# Kill all child processes
|
|
@@ -545,7 +567,7 @@ sys.exit(1)
|
|
|
545
567
|
log_info "Session log: $session_dir/logs/session.log"
|
|
546
568
|
exit 130
|
|
547
569
|
}
|
|
548
|
-
trap
|
|
570
|
+
trap cleanup_single_feature SIGINT SIGTERM
|
|
549
571
|
|
|
550
572
|
_SPAWN_RESULT=""
|
|
551
573
|
spawn_and_wait_session \
|
|
@@ -589,6 +611,7 @@ main() {
|
|
|
589
611
|
fi
|
|
590
612
|
|
|
591
613
|
check_dependencies
|
|
614
|
+
run_log_cleanup
|
|
592
615
|
|
|
593
616
|
# Initialize pipeline state if needed
|
|
594
617
|
if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
|
|
@@ -669,6 +692,7 @@ for f in data.get('stuck_features', []):
|
|
|
669
692
|
log_success " All features completed! Pipeline finished."
|
|
670
693
|
log_success " Total sessions: $session_count"
|
|
671
694
|
log_success "════════════════════════════════════════════════════"
|
|
695
|
+
rm -f "$STATE_DIR/current-session.json"
|
|
672
696
|
break
|
|
673
697
|
fi
|
|
674
698
|
|
|
@@ -715,7 +739,7 @@ for f in data.get('stuck_features', []):
|
|
|
715
739
|
--state-dir "$STATE_DIR" \
|
|
716
740
|
--output "$bootstrap_prompt" >/dev/null 2>&1
|
|
717
741
|
|
|
718
|
-
# Update current session tracking
|
|
742
|
+
# Update current session tracking (atomic write via temp file)
|
|
719
743
|
python3 -c "
|
|
720
744
|
import json, sys, os
|
|
721
745
|
from datetime import datetime
|
|
@@ -725,8 +749,11 @@ data = {
|
|
|
725
749
|
'session_id': session_id,
|
|
726
750
|
'started_at': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
727
751
|
}
|
|
728
|
-
|
|
752
|
+
target = os.path.join(state_dir, 'current-session.json')
|
|
753
|
+
tmp = target + '.tmp'
|
|
754
|
+
with open(tmp, 'w') as f:
|
|
729
755
|
json.dump(data, f, indent=2)
|
|
756
|
+
os.replace(tmp, target)
|
|
730
757
|
" "$feature_id" "$session_id" "$STATE_DIR"
|
|
731
758
|
|
|
732
759
|
# Mark feature as in-progress before spawning session
|
|
@@ -780,6 +807,9 @@ show_help() {
|
|
|
780
807
|
echo " AI_CLI AI CLI command name (auto-detected: cbc or claude)"
|
|
781
808
|
echo " HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)"
|
|
782
809
|
echo " HEARTBEAT_STALE_THRESHOLD Heartbeat stale threshold in seconds (default: 600)"
|
|
810
|
+
echo " LOG_CLEANUP_ENABLED Run log cleanup before execution (default: 1)"
|
|
811
|
+
echo " LOG_RETENTION_DAYS Delete logs older than N days (default: 14)"
|
|
812
|
+
echo " LOG_MAX_TOTAL_MB Keep total logs under N MB (default: 1024)"
|
|
783
813
|
echo ""
|
|
784
814
|
echo "Examples:"
|
|
785
815
|
echo " ./run.sh run # Run all features"
|
|
@@ -13,9 +13,13 @@ import argparse
|
|
|
13
13
|
import json
|
|
14
14
|
import sys
|
|
15
15
|
|
|
16
|
+
from utils import setup_logging
|
|
17
|
+
|
|
16
18
|
|
|
17
19
|
REQUIRED_FIELDS = ["session_id", "feature_id", "status", "timestamp"]
|
|
18
20
|
|
|
21
|
+
LOGGER = setup_logging("check-session-status")
|
|
22
|
+
|
|
19
23
|
|
|
20
24
|
def parse_args():
|
|
21
25
|
parser = argparse.ArgumentParser(
|
|
@@ -61,7 +65,8 @@ def validate_required_fields(data):
|
|
|
61
65
|
def determine_status(data):
|
|
62
66
|
"""Determine the single-line status string from the parsed data.
|
|
63
67
|
|
|
64
|
-
Returns one of: success, partial_resumable, partial_not_resumable,
|
|
68
|
+
Returns one of: success, partial_resumable, partial_not_resumable,
|
|
69
|
+
failed, commit_missing, docs_missing.
|
|
65
70
|
"""
|
|
66
71
|
status = data.get("status", "")
|
|
67
72
|
|
|
@@ -75,6 +80,10 @@ def determine_status(data):
|
|
|
75
80
|
return "partial_not_resumable"
|
|
76
81
|
elif status == "failed":
|
|
77
82
|
return "failed"
|
|
83
|
+
elif status == "commit_missing":
|
|
84
|
+
return "commit_missing"
|
|
85
|
+
elif status == "docs_missing":
|
|
86
|
+
return "docs_missing"
|
|
78
87
|
else:
|
|
79
88
|
# Unknown status value — treat as crashed
|
|
80
89
|
return "crashed"
|
|
@@ -155,4 +164,40 @@ def main():
|
|
|
155
164
|
|
|
156
165
|
|
|
157
166
|
if __name__ == "__main__":
|
|
158
|
-
|
|
167
|
+
try:
|
|
168
|
+
main()
|
|
169
|
+
except KeyboardInterrupt:
|
|
170
|
+
detail = {
|
|
171
|
+
"status": "crashed",
|
|
172
|
+
"feature_id": None,
|
|
173
|
+
"completed_phases": [],
|
|
174
|
+
"checkpoint_reached": None,
|
|
175
|
+
"tasks_completed": 0,
|
|
176
|
+
"tasks_total": 0,
|
|
177
|
+
"error_count": 1,
|
|
178
|
+
"can_resume": False,
|
|
179
|
+
"resume_from_phase": None,
|
|
180
|
+
"internal_error": "Interrupted",
|
|
181
|
+
}
|
|
182
|
+
sys.stderr.write(json.dumps(detail, indent=2, ensure_ascii=False) + "\n")
|
|
183
|
+
print("crashed")
|
|
184
|
+
sys.exit(0)
|
|
185
|
+
except SystemExit:
|
|
186
|
+
raise
|
|
187
|
+
except Exception as exc:
|
|
188
|
+
LOGGER.exception("Unhandled exception in check-session-status")
|
|
189
|
+
detail = {
|
|
190
|
+
"status": "crashed",
|
|
191
|
+
"feature_id": None,
|
|
192
|
+
"completed_phases": [],
|
|
193
|
+
"checkpoint_reached": None,
|
|
194
|
+
"tasks_completed": 0,
|
|
195
|
+
"tasks_total": 0,
|
|
196
|
+
"error_count": 1,
|
|
197
|
+
"can_resume": False,
|
|
198
|
+
"resume_from_phase": None,
|
|
199
|
+
"internal_error": str(exc),
|
|
200
|
+
}
|
|
201
|
+
sys.stderr.write(json.dumps(detail, indent=2, ensure_ascii=False) + "\n")
|
|
202
|
+
print("crashed")
|
|
203
|
+
sys.exit(0)
|