loki-mode 5.42.2 → 5.46.0

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/autonomy/run.sh CHANGED
@@ -534,6 +534,24 @@ if [ -f "$COUNCIL_SCRIPT" ]; then
534
534
  source "$COUNCIL_SCRIPT"
535
535
  fi
536
536
 
537
+ # PRD Checklist module (v5.44.0)
538
+ if [ -f "${SCRIPT_DIR}/prd-checklist.sh" ]; then
539
+ # shellcheck source=prd-checklist.sh
540
+ source "${SCRIPT_DIR}/prd-checklist.sh"
541
+ fi
542
+
543
+ # App Runner module (v5.45.0)
544
+ if [ -f "${SCRIPT_DIR}/app-runner.sh" ]; then
545
+ # shellcheck source=app-runner.sh
546
+ source "${SCRIPT_DIR}/app-runner.sh"
547
+ fi
548
+
549
+ # Playwright Smoke Test module (v5.46.0)
550
+ if [ -f "${SCRIPT_DIR}/playwright-verify.sh" ]; then
551
+ # shellcheck source=playwright-verify.sh
552
+ source "${SCRIPT_DIR}/playwright-verify.sh"
553
+ fi
554
+
537
555
  # Anonymous usage telemetry (opt-out: LOKI_TELEMETRY_DISABLED=true or DO_NOT_TRACK=1)
538
556
  TELEMETRY_SCRIPT="$SCRIPT_DIR/telemetry.sh"
539
557
  if [ -f "$TELEMETRY_SCRIPT" ]; then
@@ -2627,6 +2645,24 @@ write_dashboard_state() {
2627
2645
  council_state=$(cat ".loki/council/state.json" 2>/dev/null || echo '{"enabled":false}')
2628
2646
  fi
2629
2647
 
2648
+ # PRD Checklist summary (v5.44.0)
2649
+ local checklist_summary='null'
2650
+ if [ -f ".loki/checklist/verification-results.json" ]; then
2651
+ checklist_summary=$(cat ".loki/checklist/verification-results.json" 2>/dev/null || echo "null")
2652
+ fi
2653
+
2654
+ # App Runner state (v5.45.0)
2655
+ local app_runner_state='{"status":"not_initialized"}'
2656
+ if [ -f ".loki/app-runner/state.json" ]; then
2657
+ app_runner_state=$(cat ".loki/app-runner/state.json" 2>/dev/null || echo '{"status":"error"}')
2658
+ fi
2659
+
2660
+ # Playwright verification results (v5.46.0)
2661
+ local playwright_results='null'
2662
+ if [ -f ".loki/verification/playwright-results.json" ]; then
2663
+ playwright_results=$(cat ".loki/verification/playwright-results.json" 2>/dev/null || echo "null")
2664
+ fi
2665
+
2630
2666
  # Get budget status (if configured)
2631
2667
  local budget_json="null"
2632
2668
  if [ -f ".loki/metrics/budget.json" ]; then
@@ -2703,6 +2739,9 @@ except: print('{\"total\":0,\"unacknowledged\":0}')
2703
2739
  },
2704
2740
  "qualityGates": $quality_gates,
2705
2741
  "council": $council_state,
2742
+ "checklist": $checklist_summary,
2743
+ "appRunner": $app_runner_state,
2744
+ "playwright": $playwright_results,
2706
2745
  "budget": $budget_json,
2707
2746
  "context": $context_state,
2708
2747
  "tokens": $(python3 -c "
@@ -6047,17 +6086,63 @@ build_prompt() {
6047
6086
  memory_context_section="CONTEXT: $context_injection"
6048
6087
  fi
6049
6088
 
6089
+ # PRD Checklist status injection (v5.44.0)
6090
+ local checklist_status=""
6091
+ if [ -n "$prd" ] && [ ! -f ".loki/checklist/checklist.json" ]; then
6092
+ # First iteration with PRD but no checklist yet: instruct AI to create it
6093
+ checklist_status="PRD_CHECKLIST_INIT: Create .loki/checklist/checklist.json from the PRD. Extract requirements into categories with items. Each item needs: id, title, description, priority (critical|major|minor), and verification checks (file_exists, file_contains, tests_pass, grep_codebase, command). This checklist will be auto-verified every ${CHECKLIST_INTERVAL:-5} iterations."
6094
+ elif type checklist_summary &>/dev/null && [ -f ".loki/checklist/verification-results.json" ]; then
6095
+ checklist_status=$(checklist_summary 2>/dev/null || true)
6096
+ if [ -n "$checklist_status" ]; then
6097
+ checklist_status="PRD_CHECKLIST_STATUS: ${checklist_status}. Review failing items and prioritize fixing them in this iteration."
6098
+ fi
6099
+ fi
6100
+
6101
+ # App Runner status injection (v5.45.0)
6102
+ local app_runner_info=""
6103
+ if [ -f ".loki/app-runner/state.json" ]; then
6104
+ app_runner_info=$(python3 -c "
6105
+ import json
6106
+ try:
6107
+ d = json.load(open('.loki/app-runner/state.json'))
6108
+ s = d.get('status', '')
6109
+ if s == 'running':
6110
+ print('APP_RUNNING_AT: ' + d.get('url', '') + ' (auto-restarts on code changes). Method: ' + d.get('method', ''))
6111
+ elif s == 'crashed':
6112
+ print('APP_CRASHED: Application has crashed ' + str(d.get('crash_count', 0)) + ' times. Check .loki/app-runner/app.log for errors.')
6113
+ except: pass
6114
+ " 2>/dev/null || true)
6115
+ fi
6116
+
6117
+ # Playwright verification status injection (v5.46.0)
6118
+ local playwright_info=""
6119
+ if [ -f ".loki/verification/playwright-results.json" ]; then
6120
+ playwright_info=$(python3 -c "
6121
+ import json
6122
+ try:
6123
+ d = json.load(open('.loki/verification/playwright-results.json'))
6124
+ if d.get('passed'):
6125
+ print('PLAYWRIGHT_SMOKE_TEST: PASSED - App loads correctly.')
6126
+ else:
6127
+ errors = d.get('errors', [])
6128
+ checks = d.get('checks', {})
6129
+ failing = [k for k, v in checks.items() if not v]
6130
+ print('PLAYWRIGHT_SMOKE_TEST: FAILED - ' + ', '.join(failing[:3]) + ('. Errors: ' + '; '.join(errors[:3]) if errors else ''))
6131
+ except: pass
6132
+ " 2>/dev/null || true)
6133
+ fi
6134
+
6050
6135
  if [ $retry -eq 0 ]; then
6051
6136
  if [ -n "$prd" ]; then
6052
- echo "Loki Mode with PRD at $prd. $human_directive $queue_tasks $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
6137
+ echo "Loki Mode with PRD at $prd. $human_directive $queue_tasks $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
6053
6138
  else
6054
- echo "Loki Mode. $human_directive $queue_tasks $memory_context_section $analysis_instruction $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
6139
+ echo "Loki Mode. $human_directive $queue_tasks $checklist_status $app_runner_info $playwright_info $memory_context_section $analysis_instruction $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
6055
6140
  fi
6056
6141
  else
6057
6142
  if [ -n "$prd" ]; then
6058
- echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $queue_tasks $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
6143
+ echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $queue_tasks $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
6059
6144
  else
6060
- echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $queue_tasks $memory_context_section Use .loki/generated-prd.md if exists. $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
6145
+ echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $queue_tasks $checklist_status $app_runner_info $playwright_info $memory_context_section Use .loki/generated-prd.md if exists. $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
6061
6146
  fi
6062
6147
  fi
6063
6148
  }
@@ -6130,6 +6215,19 @@ run_autonomous() {
6130
6215
  council_init "$prd_path"
6131
6216
  fi
6132
6217
 
6218
+ # PRD Quality Analysis and Checklist Init (v5.44.0)
6219
+ if [ -n "$prd_path" ] && [ -f "$prd_path" ]; then
6220
+ if [ -f "${SCRIPT_DIR}/prd-analyzer.py" ]; then
6221
+ log_step "Analyzing PRD quality..."
6222
+ python3 "${SCRIPT_DIR}/prd-analyzer.py" "$prd_path" \
6223
+ --output ".loki/prd-observations.md" \
6224
+ ${LOKI_INTERACTIVE_PRD:+--interactive} 2>/dev/null || true
6225
+ fi
6226
+ if type checklist_init &>/dev/null; then
6227
+ checklist_init "$prd_path"
6228
+ fi
6229
+ fi
6230
+
6133
6231
  # Check max iterations before starting
6134
6232
  if check_max_iterations; then
6135
6233
  log_error "Max iterations already reached. Reset with: rm .loki/autonomy-state.json"
@@ -6508,6 +6606,57 @@ if __name__ == "__main__":
6508
6606
  # Auto-track iteration completion (for dashboard task queue)
6509
6607
  track_iteration_complete "$ITERATION_COUNT" "$exit_code"
6510
6608
 
6609
+ # PRD Checklist verification on interval (v5.44.0)
6610
+ if type checklist_should_verify &>/dev/null && checklist_should_verify; then
6611
+ checklist_verify
6612
+ fi
6613
+
6614
+ # App Runner: init after first successful iteration (v5.45.0)
6615
+ if [ "${APP_RUNNER_INITIALIZED:-}" != "true" ] && [ $exit_code -eq 0 ] && \
6616
+ [ "${LOKI_APP_RUNNER:-true}" = "true" ] && type app_runner_init &>/dev/null; then
6617
+ if app_runner_init; then
6618
+ app_runner_start || log_warn "App runner: failed to start application"
6619
+ APP_RUNNER_INITIALIZED=true
6620
+ fi
6621
+ fi
6622
+
6623
+ # App Runner: restart on code changes (v5.45.0)
6624
+ if [ "${APP_RUNNER_INITIALIZED:-}" = "true" ] && type app_runner_should_restart &>/dev/null; then
6625
+ if app_runner_should_restart; then
6626
+ app_runner_restart || log_warn "App runner: failed to restart application"
6627
+ fi
6628
+ fi
6629
+
6630
+ # App Runner: watchdog check (v5.45.0)
6631
+ if [ "${APP_RUNNER_INITIALIZED:-}" = "true" ] && type app_runner_watchdog &>/dev/null; then
6632
+ app_runner_watchdog
6633
+ fi
6634
+
6635
+ # Playwright smoke test on interval (v5.46.0)
6636
+ if type playwright_verify_should_run &>/dev/null && playwright_verify_should_run; then
6637
+ if [ -f ".loki/app-runner/state.json" ]; then
6638
+ local app_url
6639
+ app_url=$(python3 -c "import json; d=json.load(open('.loki/app-runner/state.json')); print(d.get('url','') if d.get('status')=='running' else '')" 2>/dev/null || true)
6640
+ if [ -n "$app_url" ]; then
6641
+ playwright_verify_app "$app_url" || true
6642
+ fi
6643
+ fi
6644
+ fi
6645
+
6646
+ # App Runner: check for dashboard control signals (v5.45.0)
6647
+ if [ "${APP_RUNNER_INITIALIZED:-}" = "true" ]; then
6648
+ if [ -f ".loki/app-runner/restart-signal" ]; then
6649
+ rm -f ".loki/app-runner/restart-signal"
6650
+ log_info "App runner: restart signal received from dashboard"
6651
+ app_runner_restart || true
6652
+ fi
6653
+ if [ -f ".loki/app-runner/stop-signal" ]; then
6654
+ rm -f ".loki/app-runner/stop-signal"
6655
+ log_info "App runner: stop signal received from dashboard"
6656
+ app_runner_stop || true
6657
+ fi
6658
+ fi
6659
+
6511
6660
  # Update session continuity file for next iteration / agent handoff
6512
6661
  update_continuity
6513
6662
 
@@ -6799,6 +6948,9 @@ cleanup() {
6799
6948
  echo ""
6800
6949
  log_warn "Stop signal received - shutting down"
6801
6950
  rm -f "$loki_dir/STOP" "$loki_dir/PAUSE" "$loki_dir/PAUSED.md" 2>/dev/null
6951
+ if type app_runner_cleanup &>/dev/null; then
6952
+ app_runner_cleanup
6953
+ fi
6802
6954
  stop_dashboard
6803
6955
  stop_status_monitor
6804
6956
  rm -f "$loki_dir/loki.pid" 2>/dev/null
@@ -6823,6 +6975,9 @@ except (json.JSONDecodeError, OSError): pass
6823
6975
  if [ "$time_diff" -lt 2 ] && [ "$INTERRUPT_COUNT" -gt 0 ]; then
6824
6976
  echo ""
6825
6977
  log_warn "Double interrupt - stopping immediately"
6978
+ if type app_runner_cleanup &>/dev/null; then
6979
+ app_runner_cleanup
6980
+ fi
6826
6981
  stop_dashboard
6827
6982
  stop_status_monitor
6828
6983
  rm -f .loki/loki.pid .loki/PAUSE 2>/dev/null
@@ -6940,6 +7095,10 @@ main() {
6940
7095
  BACKGROUND_MODE=true
6941
7096
  shift
6942
7097
  ;;
7098
+ --interactive-prd|--interactive)
7099
+ LOKI_INTERACTIVE_PRD=true
7100
+ shift
7101
+ ;;
6943
7102
  --help|-h)
6944
7103
  echo "Usage: ./autonomy/run.sh [OPTIONS] [PRD_PATH]"
6945
7104
  echo ""
@@ -6948,6 +7107,7 @@ main() {
6948
7107
  echo " --allow-haiku Enable Haiku model for fast tier (default: disabled)"
6949
7108
  echo " --provider <name> Provider: claude (default), codex, gemini"
6950
7109
  echo " --bg, --background Run in background mode"
7110
+ echo " --interactive-prd Interactive PRD pre-flight analysis"
6951
7111
  echo " --help, -h Show this help message"
6952
7112
  echo ""
6953
7113
  echo "Environment variables: See header comments in this script"
@@ -5,7 +5,7 @@ _loki_completion() {
5
5
  _init_completion || return
6
6
 
7
7
  # Main subcommands (must match autonomy/loki main case statement)
8
- local main_commands="start quick demo init stop pause resume status dashboard logs serve api sandbox notify import issue config provider reset memory compound council dogfood projects enterprise voice version completions doctor help"
8
+ local main_commands="start quick demo init stop pause resume status dashboard logs serve api sandbox notify import github issue config provider reset memory compound checkpoint council dogfood projects enterprise voice secrets doctor watchdog audit metrics syslog version completions help"
9
9
 
10
10
  # 1. If we are on the first argument (subcommand)
11
11
  if [[ $cword -eq 1 ]]; then
@@ -88,6 +88,11 @@ _loki_completion() {
88
88
  COMPREPLY=( $(compgen -W "${voice_cmds}" -- "$cur") )
89
89
  ;;
90
90
 
91
+ syslog)
92
+ local syslog_cmds="test status help"
93
+ COMPREPLY=( $(compgen -W "${syslog_cmds}" -- "$cur") )
94
+ ;;
95
+
91
96
  status)
92
97
  if [[ "$cur" == -* ]]; then
93
98
  COMPREPLY=( $(compgen -W "--json --help" -- "$cur") )
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "5.42.2"
10
+ __version__ = "5.46.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -48,6 +48,7 @@ from . import auth
48
48
  from . import audit
49
49
  from . import secrets as secrets_mod
50
50
  from . import telemetry as _telemetry
51
+ from .control import atomic_write_json
51
52
 
52
53
  try:
53
54
  from . import __version__ as _version
@@ -2036,7 +2037,7 @@ async def stop_session(request: Request):
2036
2037
  try:
2037
2038
  sd = json.loads(session_file.read_text())
2038
2039
  sd["status"] = "stopped"
2039
- session_file.write_text(json.dumps(sd))
2040
+ atomic_write_json(session_file, sd, use_lock=True)
2040
2041
  except Exception:
2041
2042
  pass
2042
2043
 
@@ -3357,6 +3358,138 @@ async def prometheus_metrics():
3357
3358
  return _build_metrics_text()
3358
3359
 
3359
3360
 
3361
+ # =============================================================================
3362
+ # PRD Checklist Endpoints (v5.44.0)
3363
+ # =============================================================================
3364
+
3365
+ @app.get("/api/checklist")
3366
+ async def get_checklist():
3367
+ """Get full PRD checklist with verification status."""
3368
+ loki_dir = _get_loki_dir()
3369
+ checklist_file = loki_dir / "checklist" / "checklist.json"
3370
+ if not checklist_file.exists():
3371
+ return {"status": "not_initialized", "categories": [], "summary": {"total": 0, "verified": 0, "failing": 0, "pending": 0}}
3372
+ try:
3373
+ return json.loads(checklist_file.read_text())
3374
+ except (json.JSONDecodeError, OSError):
3375
+ return {"status": "error", "categories": [], "summary": {"total": 0, "verified": 0, "failing": 0, "pending": 0}}
3376
+
3377
+
3378
+ @app.get("/api/checklist/summary")
3379
+ async def get_checklist_summary():
3380
+ """Get checklist verification summary."""
3381
+ loki_dir = _get_loki_dir()
3382
+ results_file = loki_dir / "checklist" / "verification-results.json"
3383
+ if not results_file.exists():
3384
+ return {"status": "not_initialized", "summary": {"total": 0, "verified": 0, "failing": 0, "pending": 0}}
3385
+ try:
3386
+ return json.loads(results_file.read_text())
3387
+ except (json.JSONDecodeError, OSError):
3388
+ return {"status": "error", "summary": {"total": 0, "verified": 0, "failing": 0, "pending": 0}}
3389
+
3390
+
3391
+ @app.get("/api/prd-observations")
3392
+ async def get_prd_observations():
3393
+ """Get PRD quality analysis observations."""
3394
+ loki_dir = _get_loki_dir()
3395
+ obs_file = loki_dir / "prd-observations.md"
3396
+ if not obs_file.exists():
3397
+ return PlainTextResponse("No PRD observations available yet.", status_code=200)
3398
+ try:
3399
+ content = obs_file.read_text()
3400
+ return PlainTextResponse(content, status_code=200)
3401
+ except OSError:
3402
+ return PlainTextResponse("Error reading PRD observations.", status_code=500)
3403
+
3404
+
3405
+ # =============================================================================
3406
+ # App Runner Endpoints (v5.45.0)
3407
+ # =============================================================================
3408
+
3409
+ @app.get("/api/app-runner/status")
3410
+ async def get_app_runner_status():
3411
+ """Get app runner current status."""
3412
+ loki_dir = _get_loki_dir()
3413
+ state_file = loki_dir / "app-runner" / "state.json"
3414
+ if not state_file.exists():
3415
+ return {"status": "not_initialized"}
3416
+ try:
3417
+ return json.loads(state_file.read_text())
3418
+ except (json.JSONDecodeError, OSError):
3419
+ return {"status": "error"}
3420
+
3421
+
3422
+ @app.get("/api/app-runner/logs")
3423
+ async def get_app_runner_logs(lines: int = Query(default=100, ge=1, le=1000)):
3424
+ """Get last N lines of app runner logs."""
3425
+ loki_dir = _get_loki_dir()
3426
+ log_file = loki_dir / "app-runner" / "app.log"
3427
+ if not log_file.exists():
3428
+ return {"lines": []}
3429
+ try:
3430
+ all_lines = log_file.read_text().splitlines()
3431
+ return {"lines": all_lines[-lines:]}
3432
+ except OSError:
3433
+ return {"lines": []}
3434
+
3435
+
3436
+ @app.post("/api/control/app-restart", dependencies=[Depends(auth.require_scope("control"))])
3437
+ async def control_app_restart(request: Request):
3438
+ """Signal app runner to restart the application."""
3439
+ if not _control_limiter.check(str(request.client.host)):
3440
+ raise HTTPException(status_code=429, detail="Rate limit exceeded")
3441
+ loki_dir = _get_loki_dir()
3442
+ signal_dir = loki_dir / "app-runner"
3443
+ signal_dir.mkdir(parents=True, exist_ok=True)
3444
+ signal_file = signal_dir / "restart-signal"
3445
+ signal_file.write_text(datetime.now(timezone.utc).isoformat())
3446
+ return {"status": "restart_signaled"}
3447
+
3448
+
3449
+ @app.post("/api/control/app-stop", dependencies=[Depends(auth.require_scope("control"))])
3450
+ async def control_app_stop(request: Request):
3451
+ """Signal app runner to stop the application."""
3452
+ if not _control_limiter.check(str(request.client.host)):
3453
+ raise HTTPException(status_code=429, detail="Rate limit exceeded")
3454
+ loki_dir = _get_loki_dir()
3455
+ signal_dir = loki_dir / "app-runner"
3456
+ signal_dir.mkdir(parents=True, exist_ok=True)
3457
+ signal_file = signal_dir / "stop-signal"
3458
+ signal_file.write_text(datetime.now(timezone.utc).isoformat())
3459
+ return {"status": "stop_signaled"}
3460
+
3461
+
3462
+ # =============================================================================
3463
+ # Playwright Verification Endpoints (v5.46.0)
3464
+ # =============================================================================
3465
+
3466
+ @app.get("/api/playwright/results")
3467
+ async def get_playwright_results():
3468
+ """Get latest Playwright smoke test results."""
3469
+ loki_dir = _get_loki_dir()
3470
+ results_file = loki_dir / "verification" / "playwright-results.json"
3471
+ if not results_file.exists():
3472
+ return {"status": "not_run"}
3473
+ try:
3474
+ return json.loads(results_file.read_text())
3475
+ except (json.JSONDecodeError, OSError):
3476
+ return {"status": "error"}
3477
+
3478
+
3479
+ @app.get("/api/playwright/screenshot")
3480
+ async def get_playwright_screenshot():
3481
+ """Get path to latest Playwright screenshot."""
3482
+ loki_dir = _get_loki_dir()
3483
+ screenshots_dir = loki_dir / "verification" / "screenshots"
3484
+ if not screenshots_dir.exists():
3485
+ return {"screenshot": None}
3486
+ # Get most recent screenshot
3487
+ screenshots = sorted(screenshots_dir.glob("*.png"), key=lambda p: p.stat().st_mtime, reverse=True)
3488
+ if not screenshots:
3489
+ return {"screenshot": None}
3490
+ return {"screenshot": str(screenshots[0])}
3491
+
3492
+
3360
3493
  # =============================================================================
3361
3494
  # Static File Serving (Production/Docker)
3362
3495
  # =============================================================================