prizmkit 1.0.13 → 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.
Files changed (77) hide show
  1. package/bin/create-prizmkit.js +4 -1
  2. package/bundled/VERSION.json +3 -3
  3. package/bundled/adapters/claude/command-adapter.js +35 -4
  4. package/bundled/adapters/claude/rules-adapter.js +6 -58
  5. package/bundled/adapters/claude/team-adapter.js +2 -2
  6. package/bundled/adapters/codebuddy/agent-adapter.js +0 -1
  7. package/bundled/adapters/codebuddy/rules-adapter.js +30 -0
  8. package/bundled/adapters/shared/frontmatter.js +3 -1
  9. package/bundled/dev-pipeline/README.md +13 -3
  10. package/bundled/dev-pipeline/launch-bugfix-daemon.sh +10 -0
  11. package/bundled/dev-pipeline/launch-daemon.sh +18 -4
  12. package/bundled/dev-pipeline/lib/common.sh +105 -0
  13. package/bundled/dev-pipeline/run-bugfix.sh +57 -57
  14. package/bundled/dev-pipeline/run.sh +75 -59
  15. package/bundled/dev-pipeline/scripts/check-session-status.py +47 -2
  16. package/bundled/dev-pipeline/scripts/cleanup-logs.py +192 -0
  17. package/bundled/dev-pipeline/scripts/detect-stuck.py +15 -3
  18. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +32 -27
  19. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +23 -23
  20. package/bundled/dev-pipeline/scripts/update-feature-status.py +50 -2
  21. package/bundled/dev-pipeline/scripts/utils.py +22 -0
  22. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +18 -1
  23. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +19 -1
  24. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +18 -2
  25. package/bundled/dev-pipeline/templates/session-status-schema.json +7 -1
  26. package/bundled/dev-pipeline/tests/__init__.py +0 -0
  27. package/bundled/dev-pipeline/tests/conftest.py +133 -0
  28. package/bundled/dev-pipeline/tests/test_check_session.py +127 -0
  29. package/bundled/dev-pipeline/tests/test_cleanup_logs.py +119 -0
  30. package/bundled/dev-pipeline/tests/test_detect_stuck.py +207 -0
  31. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +181 -0
  32. package/bundled/dev-pipeline/tests/test_generate_prompt.py +190 -0
  33. package/bundled/dev-pipeline/tests/test_init_bugfix_pipeline.py +153 -0
  34. package/bundled/dev-pipeline/tests/test_init_pipeline.py +241 -0
  35. package/bundled/dev-pipeline/tests/test_update_bug_status.py +142 -0
  36. package/bundled/dev-pipeline/tests/test_update_feature_status.py +277 -0
  37. package/bundled/dev-pipeline/tests/test_utils.py +141 -0
  38. package/bundled/rules/USAGE.md +153 -0
  39. package/bundled/rules/_rules-metadata.json +43 -0
  40. package/bundled/rules/general/prefer-linux-commands.md +9 -0
  41. package/bundled/rules/prizm/prizm-commit-workflow.md +10 -0
  42. package/bundled/rules/prizm/prizm-documentation.md +19 -0
  43. package/bundled/rules/prizm/prizm-progressive-loading.md +11 -0
  44. package/bundled/skills/_metadata.json +130 -67
  45. package/bundled/skills/app-planner/SKILL.md +252 -499
  46. package/bundled/skills/app-planner/assets/evaluation-guide.md +44 -0
  47. package/bundled/skills/app-planner/scripts/validate-and-generate.py +143 -4
  48. package/bundled/skills/bug-planner/SKILL.md +58 -13
  49. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +5 -7
  50. package/bundled/skills/dev-pipeline-launcher/SKILL.md +16 -7
  51. package/bundled/skills/feature-workflow/SKILL.md +175 -234
  52. package/bundled/skills/prizm-kit/SKILL.md +17 -31
  53. package/bundled/skills/{prizmkit-adr-manager → prizmkit-tool-adr-manager}/SKILL.md +6 -7
  54. package/bundled/skills/{prizmkit-api-doc-generator → prizmkit-tool-api-doc-generator}/SKILL.md +4 -5
  55. package/bundled/skills/{prizmkit-bug-reproducer → prizmkit-tool-bug-reproducer}/SKILL.md +4 -5
  56. package/bundled/skills/{prizmkit-ci-cd-generator → prizmkit-tool-ci-cd-generator}/SKILL.md +4 -5
  57. package/bundled/skills/{prizmkit-db-migration → prizmkit-tool-db-migration}/SKILL.md +4 -5
  58. package/bundled/skills/{prizmkit-dependency-health → prizmkit-tool-dependency-health}/SKILL.md +3 -4
  59. package/bundled/skills/{prizmkit-deployment-strategy → prizmkit-tool-deployment-strategy}/SKILL.md +4 -5
  60. package/bundled/skills/{prizmkit-error-triage → prizmkit-tool-error-triage}/SKILL.md +4 -5
  61. package/bundled/skills/{prizmkit-log-analyzer → prizmkit-tool-log-analyzer}/SKILL.md +4 -5
  62. package/bundled/skills/{prizmkit-monitoring-setup → prizmkit-tool-monitoring-setup}/SKILL.md +4 -5
  63. package/bundled/skills/{prizmkit-onboarding-generator → prizmkit-tool-onboarding-generator}/SKILL.md +4 -5
  64. package/bundled/skills/{prizmkit-perf-profiler → prizmkit-tool-perf-profiler}/SKILL.md +4 -5
  65. package/bundled/skills/{prizmkit-security-audit → prizmkit-tool-security-audit}/SKILL.md +3 -4
  66. package/bundled/skills/{prizmkit-tech-debt-tracker → prizmkit-tool-tech-debt-tracker}/SKILL.md +3 -4
  67. package/bundled/skills/refactor-skill/SKILL.md +371 -0
  68. package/bundled/skills/refactor-workflow/SKILL.md +17 -119
  69. package/package.json +1 -1
  70. package/src/external-skills.js +71 -0
  71. package/src/index.js +62 -4
  72. package/src/metadata.js +36 -0
  73. package/src/scaffold.js +136 -32
  74. package/bundled/skills/prizmkit-bug-fix-workflow/SKILL.md +0 -356
  75. package/bundled/templates/claude-md-template.md +0 -38
  76. package/bundled/templates/codebuddy-md-template.md +0 -35
  77. /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
- # AI CLI detection
37
- if [[ -n "${AI_CLI:-}" ]]; then
38
- CLI_CMD="$AI_CLI"
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
  # ============================================================
@@ -234,13 +208,29 @@ trap cleanup SIGINT SIGTERM
234
208
  # ============================================================
235
209
 
236
210
  check_dependencies() {
237
- if ! command -v jq &>/dev/null; then
238
- log_error "jq is required but not installed. Install with: brew install jq"
239
- exit 1
211
+ prizm_check_common_dependencies "$CLI_CMD"
212
+ }
213
+
214
+ run_log_cleanup() {
215
+ if [[ "$LOG_CLEANUP_ENABLED" != "1" ]]; then
216
+ return 0
240
217
  fi
241
- if ! command -v python3 &>/dev/null; then
242
- log_error "python3 is required but not installed."
243
- exit 1
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"
244
234
  fi
245
235
  }
246
236
 
@@ -283,6 +273,7 @@ run_one() {
283
273
  fi
284
274
 
285
275
  check_dependencies
276
+ run_log_cleanup
286
277
 
287
278
  # Initialize state if needed
288
279
  if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
@@ -299,14 +290,14 @@ run_one() {
299
290
  local bug_title
300
291
  bug_title=$(python3 -c "
301
292
  import json, sys
302
- with open('$bug_list') as f:
293
+ with open(sys.argv[1]) as f:
303
294
  data = json.load(f)
304
295
  for bug in data.get('bugs', []):
305
- if bug.get('id') == '$bug_id':
296
+ if bug.get('id') == sys.argv[2]:
306
297
  print(bug.get('title', ''))
307
298
  sys.exit(0)
308
299
  sys.exit(1)
309
- " 2>/dev/null) || {
300
+ " "$bug_list" "$bug_id" 2>/dev/null) || {
310
301
  log_error "Bug $bug_id not found in $bug_list"
311
302
  exit 1
312
303
  }
@@ -314,14 +305,14 @@ sys.exit(1)
314
305
  local bug_severity
315
306
  bug_severity=$(python3 -c "
316
307
  import json, sys
317
- with open('$bug_list') as f:
308
+ with open(sys.argv[1]) as f:
318
309
  data = json.load(f)
319
310
  for bug in data.get('bugs', []):
320
- if bug.get('id') == '$bug_id':
311
+ if bug.get('id') == sys.argv[2]:
321
312
  print(bug.get('severity', 'medium'))
322
313
  sys.exit(0)
323
314
  sys.exit(1)
324
- " 2>/dev/null) || bug_severity="medium"
315
+ " "$bug_list" "$bug_id" 2>/dev/null) || bug_severity="medium"
325
316
 
326
317
  # Reset bug status
327
318
  python3 "$SCRIPTS_DIR/update-bug-status.py" \
@@ -376,14 +367,14 @@ sys.exit(1)
376
367
  echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
377
368
  echo ""
378
369
 
379
- cleanup() {
370
+ cleanup_single_bug() {
380
371
  echo ""
381
372
  log_warn "Interrupted. Killing session..."
382
373
  kill 0 2>/dev/null || true
383
374
  log_info "Session log: $session_dir/logs/session.log"
384
375
  exit 130
385
376
  }
386
- trap cleanup SIGINT SIGTERM
377
+ trap cleanup_single_bug SIGINT SIGTERM
387
378
 
388
379
  _SPAWN_RESULT=""
389
380
  spawn_and_wait_session \
@@ -424,6 +415,7 @@ main() {
424
415
  fi
425
416
 
426
417
  check_dependencies
418
+ run_log_cleanup
427
419
 
428
420
  # Initialize pipeline state if needed
429
421
  if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
@@ -482,6 +474,7 @@ main() {
482
474
  log_success " All bugs processed! Bug fix pipeline finished."
483
475
  log_success " Total sessions: $session_count"
484
476
  log_success "════════════════════════════════════════════════════"
477
+ rm -f "$STATE_DIR/current-session.json"
485
478
  break
486
479
  fi
487
480
 
@@ -529,18 +522,22 @@ main() {
529
522
  --state-dir "$STATE_DIR" \
530
523
  --output "$bootstrap_prompt" >/dev/null 2>&1
531
524
 
532
- # Track current session
525
+ # Track current session (atomic write via temp file)
533
526
  python3 -c "
534
- import json
527
+ import json, sys, os
535
528
  from datetime import datetime
529
+ bug_id, session_id, state_dir = sys.argv[1], sys.argv[2], sys.argv[3]
536
530
  data = {
537
- 'bug_id': '$bug_id',
538
- 'session_id': '$session_id',
531
+ 'bug_id': bug_id,
532
+ 'session_id': session_id,
539
533
  'started_at': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
540
534
  }
541
- with open('$STATE_DIR/current-session.json', 'w') as f:
535
+ target = os.path.join(state_dir, 'current-session.json')
536
+ tmp = target + '.tmp'
537
+ with open(tmp, 'w') as f:
542
538
  json.dump(data, f, indent=2)
543
- "
539
+ os.replace(tmp, target)
540
+ " "$bug_id" "$session_id" "$STATE_DIR"
544
541
 
545
542
  # Spawn session
546
543
  log_info "Spawning AI CLI session: $session_id"
@@ -580,6 +577,9 @@ show_help() {
580
577
  echo " AI_CLI AI CLI command name (auto-detected: cbc or claude)"
581
578
  echo " VERBOSE Set to 1 for verbose AI CLI output"
582
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)"
583
583
  echo ""
584
584
  echo "Examples:"
585
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 (auto-detected: cbc or claude)
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
- # AI CLI detection: AI_CLI > CODEBUDDY_CLI > auto-detect > error
40
- if [[ -n "${AI_CLI:-}" ]]; then
41
- CLI_CMD="$AI_CLI"
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
  # ============================================================
@@ -206,13 +178,42 @@ spawn_and_wait_session() {
206
178
  local dirty_files=""
207
179
  dirty_files=$(git -C "$project_root" status --porcelain 2>/dev/null || true)
208
180
  if [[ -n "$dirty_files" ]]; then
209
- log_error "Session reported success but git working tree is not clean."
181
+ log_warn "Session reported success but git working tree is not clean."
210
182
  echo "$dirty_files" | sed 's/^/ - /'
211
- session_status="failed"
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
212
199
  fi
213
200
  fi
214
201
  fi
215
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
+
216
217
  log_info "Session result: $session_status"
217
218
 
218
219
  # Update feature status
@@ -254,23 +255,29 @@ trap cleanup SIGINT SIGTERM
254
255
  # ============================================================
255
256
 
256
257
  check_dependencies() {
257
- # Check for jq
258
- if ! command -v jq &>/dev/null; then
259
- log_error "jq is required but not installed. Install with: brew install jq"
260
- exit 1
261
- fi
258
+ prizm_check_common_dependencies "$CLI_CMD"
259
+ }
262
260
 
263
- # Check for python3
264
- if ! command -v python3 &>/dev/null; then
265
- log_error "python3 is required but not installed."
266
- exit 1
261
+ run_log_cleanup() {
262
+ if [[ "$LOG_CLEANUP_ENABLED" != "1" ]]; then
263
+ return 0
267
264
  fi
268
265
 
269
- # Check for AI CLI
270
- if ! command -v "$CLI_CMD" &>/dev/null; then
271
- log_warn "AI CLI '$CLI_CMD' not found in PATH."
272
- log_warn "Set AI_CLI environment variable to the correct command."
273
- log_warn "Continuing anyway (will fail when spawning sessions)..."
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"
274
281
  fi
275
282
  }
276
283
 
@@ -385,6 +392,7 @@ run_one() {
385
392
  fi
386
393
 
387
394
  check_dependencies
395
+ run_log_cleanup
388
396
 
389
397
  # Verify feature exists
390
398
  local feature_title
@@ -550,8 +558,8 @@ sys.exit(1)
550
558
  echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
551
559
  echo ""
552
560
 
553
- # Override cleanup trap for single-feature mode
554
- cleanup() {
561
+ # Override cleanup trap for single-feature mode (use distinct name to avoid overwriting global cleanup)
562
+ cleanup_single_feature() {
555
563
  echo ""
556
564
  log_warn "Interrupted. Killing session..."
557
565
  # Kill all child processes
@@ -559,7 +567,7 @@ sys.exit(1)
559
567
  log_info "Session log: $session_dir/logs/session.log"
560
568
  exit 130
561
569
  }
562
- trap cleanup SIGINT SIGTERM
570
+ trap cleanup_single_feature SIGINT SIGTERM
563
571
 
564
572
  _SPAWN_RESULT=""
565
573
  spawn_and_wait_session \
@@ -603,6 +611,7 @@ main() {
603
611
  fi
604
612
 
605
613
  check_dependencies
614
+ run_log_cleanup
606
615
 
607
616
  # Initialize pipeline state if needed
608
617
  if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
@@ -683,6 +692,7 @@ for f in data.get('stuck_features', []):
683
692
  log_success " All features completed! Pipeline finished."
684
693
  log_success " Total sessions: $session_count"
685
694
  log_success "════════════════════════════════════════════════════"
695
+ rm -f "$STATE_DIR/current-session.json"
686
696
  break
687
697
  fi
688
698
 
@@ -729,7 +739,7 @@ for f in data.get('stuck_features', []):
729
739
  --state-dir "$STATE_DIR" \
730
740
  --output "$bootstrap_prompt" >/dev/null 2>&1
731
741
 
732
- # Update current session tracking
742
+ # Update current session tracking (atomic write via temp file)
733
743
  python3 -c "
734
744
  import json, sys, os
735
745
  from datetime import datetime
@@ -739,8 +749,11 @@ data = {
739
749
  'session_id': session_id,
740
750
  'started_at': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
741
751
  }
742
- with open(os.path.join(state_dir, 'current-session.json'), 'w') as f:
752
+ target = os.path.join(state_dir, 'current-session.json')
753
+ tmp = target + '.tmp'
754
+ with open(tmp, 'w') as f:
743
755
  json.dump(data, f, indent=2)
756
+ os.replace(tmp, target)
744
757
  " "$feature_id" "$session_id" "$STATE_DIR"
745
758
 
746
759
  # Mark feature as in-progress before spawning session
@@ -794,6 +807,9 @@ show_help() {
794
807
  echo " AI_CLI AI CLI command name (auto-detected: cbc or claude)"
795
808
  echo " HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)"
796
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)"
797
813
  echo ""
798
814
  echo "Examples:"
799
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, failed.
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
- main()
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)