loki-mode 5.26.2 → 5.27.1

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/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with zero human intervention. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v5.26.2
6
+ # Loki Mode v5.27.1
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -260,4 +260,4 @@ Auto-detected or force with `LOKI_COMPLEXITY`:
260
260
 
261
261
  ---
262
262
 
263
- **v5.26.2 | Dashboard/shell/UX fixes, security hardening | ~270 lines core**
263
+ **v5.27.1 | Unified dashboard data flow, overview component, backend fixes | ~270 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 5.26.2
1
+ 5.27.1
@@ -226,6 +226,7 @@ council_vote() {
226
226
  1) role="requirements_verifier" ;;
227
227
  2) role="test_auditor" ;;
228
228
  3) role="devils_advocate" ;;
229
+ *) role="generalist" ;;
229
230
  esac
230
231
 
231
232
  log_info "Council member $member/$COUNCIL_SIZE ($role) reviewing..."
@@ -255,7 +256,24 @@ council_vote() {
255
256
  ((member++))
256
257
  done
257
258
 
258
- # Record vote results
259
+ # Anti-sycophancy check: if unanimous APPROVE, run devil's advocate
260
+ if [ $approve_count -eq $COUNCIL_SIZE ] && [ $COUNCIL_SIZE -ge 3 ]; then
261
+ log_warn "Unanimous approval detected - running anti-sycophancy check..."
262
+ local contrarian_verdict
263
+ contrarian_verdict=$(council_devils_advocate "$evidence_file" "$vote_dir")
264
+ local contrarian_vote
265
+ contrarian_vote=$(echo "$contrarian_verdict" | grep -oE "VOTE:\s*(APPROVE|REJECT)" | grep -oE "APPROVE|REJECT" | head -1)
266
+
267
+ if [ "$contrarian_vote" = "REJECT" ]; then
268
+ log_warn "Anti-sycophancy: Devil's advocate REJECTED unanimous approval"
269
+ log_warn "Overriding to require one more iteration for verification"
270
+ approve_count=$((approve_count - 1))
271
+ else
272
+ log_info "Anti-sycophancy: Devil's advocate confirmed approval"
273
+ fi
274
+ fi
275
+
276
+ # Record vote results (AFTER anti-sycophancy check so verdict reflects any override)
259
277
  _COUNCIL_STATE_FILE="$COUNCIL_STATE_DIR/state.json" \
260
278
  _COUNCIL_SIZE="$COUNCIL_SIZE" \
261
279
  _COUNCIL_APPROVE="$approve_count" \
@@ -291,23 +309,6 @@ with open(state_file, 'w') as f:
291
309
  json.dump(state, f, indent=2)
292
310
  " || log_warn "Failed to record council vote results"
293
311
 
294
- # Anti-sycophancy check: if unanimous APPROVE, run devil's advocate
295
- if [ $approve_count -eq $COUNCIL_SIZE ] && [ $COUNCIL_SIZE -ge 3 ]; then
296
- log_warn "Unanimous approval detected - running anti-sycophancy check..."
297
- local contrarian_verdict
298
- contrarian_verdict=$(council_devils_advocate "$evidence_file" "$vote_dir")
299
- local contrarian_vote
300
- contrarian_vote=$(echo "$contrarian_verdict" | grep -oE "VOTE:\s*(APPROVE|REJECT)" | grep -oE "APPROVE|REJECT" | head -1)
301
-
302
- if [ "$contrarian_vote" = "REJECT" ]; then
303
- log_warn "Anti-sycophancy: Devil's advocate REJECTED unanimous approval"
304
- log_warn "Overriding to require one more iteration for verification"
305
- approve_count=$((approve_count - 1))
306
- else
307
- log_info "Anti-sycophancy: Devil's advocate confirmed approval"
308
- fi
309
- fi
310
-
311
312
  echo ""
312
313
  log_info "Council verdict: $approve_count APPROVE / $reject_count REJECT (threshold: $COUNCIL_THRESHOLD)"
313
314
  echo -e "$verdicts"
@@ -29,7 +29,7 @@ fi
29
29
 
30
30
  # Check for TODO/FIXME in recent changes
31
31
  if [ -d "$CWD/.git" ]; then
32
- TODOS=$(git -C "$CWD" diff HEAD~1 2>/dev/null | grep -c "TODO\|FIXME" || echo "0")
32
+ TODOS=$(git -C "$CWD" diff HEAD~1 2>/dev/null | grep -c "TODO\|FIXME" 2>/dev/null) || TODOS=0
33
33
  if [ "$TODOS" -gt 0 ]; then
34
34
  GATE_RESULTS+=("new_todos: $TODOS")
35
35
  fi
@@ -11,6 +11,7 @@ METRICS_DIR="$CWD/.loki/metrics"
11
11
  mkdir -p "$METRICS_DIR"
12
12
 
13
13
  TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
14
- echo "{\"timestamp\":\"$TIMESTAMP\",\"tool\":\"$TOOL_NAME\",\"event\":\"PostToolUse\"}" >> "$METRICS_DIR/tool-usage.jsonl"
14
+ tool_escaped=$(printf '%s' "$TOOL_NAME" | sed 's/\\/\\\\/g; s/"/\\"/g')
15
+ echo "{\"timestamp\":\"$TIMESTAMP\",\"tool\":\"$tool_escaped\",\"event\":\"PostToolUse\"}" >> "$METRICS_DIR/tool-usage.jsonl"
15
16
 
16
17
  exit 0
@@ -10,13 +10,13 @@ CWD=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).ge
10
10
 
11
11
  # Dangerous command patterns
12
12
  BLOCKED_PATTERNS=(
13
- "^rm -rf /$"
14
- "^rm -rf ~$"
15
- "^rm -rf \\\$HOME$"
13
+ "^rm -rf /"
14
+ "^rm -rf ~"
15
+ "^rm -rf \\\$HOME"
16
16
  "> /dev/sd"
17
17
  "^mkfs "
18
18
  "^dd if=/dev/zero"
19
- "^chmod -R 777 /$"
19
+ "^chmod -R 777 /"
20
20
  )
21
21
 
22
22
  # Check for blocked patterns
@@ -38,7 +38,8 @@ done
38
38
  # Log command to audit trail
39
39
  LOG_DIR="$CWD/.loki/logs"
40
40
  mkdir -p "$LOG_DIR"
41
- echo "{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"command\":$(echo "$COMMAND" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))')}" >> "$LOG_DIR/bash-audit.jsonl"
41
+ printf '%s' "{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"command\":$(echo "$COMMAND" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))')}" >> "$LOG_DIR/bash-audit.jsonl"
42
+ echo >> "$LOG_DIR/bash-audit.jsonl"
42
43
 
43
44
  # Allow command
44
45
  cat << EOF
package/autonomy/loki CHANGED
@@ -942,7 +942,7 @@ cmd_provider_set() {
942
942
  echo "Install: npm install -g @openai/codex"
943
943
  ;;
944
944
  gemini)
945
- echo "Install: npm install -g @anthropic-ai/gemini-cli"
945
+ echo "Install: npm install -g @google/gemini-cli"
946
946
  ;;
947
947
  esac
948
948
  echo ""
@@ -1026,7 +1026,7 @@ cmd_provider_info() {
1026
1026
  echo "Name: Codex CLI"
1027
1027
  echo "Vendor: OpenAI"
1028
1028
  echo "CLI: codex"
1029
- echo "Flag: exec --dangerously-bypass-approvals-and-sandbox"
1029
+ echo "Flag: --full-auto"
1030
1030
  echo ""
1031
1031
  echo "Features:"
1032
1032
  echo " - Autonomous mode"
@@ -1038,7 +1038,7 @@ cmd_provider_info() {
1038
1038
  echo "Name: Gemini CLI"
1039
1039
  echo "Vendor: Google"
1040
1040
  echo "CLI: gemini"
1041
- echo "Flag: --yolo"
1041
+ echo "Flag: --approval-mode=yolo"
1042
1042
  echo ""
1043
1043
  echo "Features:"
1044
1044
  echo " - Autonomous mode"
@@ -1572,7 +1572,7 @@ cmd_issue_parse() {
1572
1572
  ;;
1573
1573
  --format)
1574
1574
  format="${2:-yaml}"
1575
- shift 2
1575
+ if [ $# -ge 2 ]; then shift 2; else shift; fi
1576
1576
  ;;
1577
1577
  --format=*)
1578
1578
  format="${1#*=}"
@@ -1580,7 +1580,7 @@ cmd_issue_parse() {
1580
1580
  ;;
1581
1581
  --output|-o)
1582
1582
  output_file="${2:-}"
1583
- shift 2
1583
+ if [ $# -ge 2 ]; then shift 2; else shift; fi
1584
1584
  ;;
1585
1585
  --output=*)
1586
1586
  output_file="${1#*=}"
@@ -2291,7 +2291,7 @@ cmd_api() {
2291
2291
  fi
2292
2292
 
2293
2293
  # Start server
2294
- mkdir -p "$LOKI_DIR"
2294
+ mkdir -p "$LOKI_DIR/logs"
2295
2295
  nohup node "$api_server" --port "$port" > "$LOKI_DIR/logs/api.log" 2>&1 &
2296
2296
  local new_pid=$!
2297
2297
  echo "$new_pid" > "$pid_file"
@@ -2612,6 +2612,8 @@ send_slack_notification() {
2612
2612
  project_name=$(basename "$(pwd)")
2613
2613
  local timestamp
2614
2614
  timestamp=$(date '+%Y-%m-%d %H:%M:%S')
2615
+ local message_escaped
2616
+ message_escaped=$(printf '%s' "$message" | sed 's/\\/\\\\/g; s/"/\\"/g; s/ /\\t/g')
2615
2617
 
2616
2618
  # Build Slack payload with blocks for better formatting
2617
2619
  local payload
@@ -2629,7 +2631,7 @@ send_slack_notification() {
2629
2631
  "type": "section",
2630
2632
  "text": {
2631
2633
  "type": "mrkdwn",
2632
- "text": "$message"
2634
+ "text": "$message_escaped"
2633
2635
  }
2634
2636
  },
2635
2637
  {
@@ -2666,6 +2668,8 @@ send_discord_notification() {
2666
2668
  project_name=$(basename "$(pwd)")
2667
2669
  local timestamp
2668
2670
  timestamp=$(date '+%Y-%m-%d %H:%M:%S')
2671
+ local message_escaped
2672
+ message_escaped=$(printf '%s' "$message" | sed 's/\\/\\\\/g; s/"/\\"/g; s/ /\\t/g')
2669
2673
 
2670
2674
  # Build Discord embed payload
2671
2675
  local payload
@@ -2674,7 +2678,7 @@ send_discord_notification() {
2674
2678
  "embeds": [
2675
2679
  {
2676
2680
  "title": "Loki Mode: $event_type",
2677
- "description": "$message",
2681
+ "description": "$message_escaped",
2678
2682
  "color": 5814783,
2679
2683
  "footer": {
2680
2684
  "text": "Project: $project_name | $timestamp"
@@ -2705,6 +2709,8 @@ send_webhook_notification() {
2705
2709
  project_name=$(basename "$(pwd)")
2706
2710
  local timestamp
2707
2711
  timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
2712
+ local message_escaped
2713
+ message_escaped=$(printf '%s' "$message" | sed 's/\\/\\\\/g; s/"/\\"/g; s/ /\\t/g')
2708
2714
 
2709
2715
  # Build generic JSON payload
2710
2716
  local payload
@@ -2712,7 +2718,7 @@ send_webhook_notification() {
2712
2718
  {
2713
2719
  "source": "loki-mode",
2714
2720
  "event": "$event_type",
2715
- "message": "$message",
2721
+ "message": "$message_escaped",
2716
2722
  "project": "$project_name",
2717
2723
  "timestamp": "$timestamp",
2718
2724
  "cwd": "$(pwd)"
@@ -3085,11 +3091,11 @@ if count == 0:
3085
3091
  ;;
3086
3092
 
3087
3093
  all)
3088
- cmd_memory show patterns --limit "$limit"
3094
+ cmd_memory show patterns --limit "$limit" --project "$project"
3089
3095
  echo ""
3090
- cmd_memory show mistakes --limit "$limit"
3096
+ cmd_memory show mistakes --limit "$limit" --project "$project"
3091
3097
  echo ""
3092
- cmd_memory show successes --limit "$limit"
3098
+ cmd_memory show successes --limit "$limit" --project "$project"
3093
3099
  ;;
3094
3100
 
3095
3101
  *)
@@ -3110,12 +3116,13 @@ if count == 0:
3110
3116
  echo -e "${BOLD}Search Results for: $query${NC}"
3111
3117
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
3112
3118
 
3119
+ local query_escaped="${query//\'/\'\\\'\'}"
3113
3120
  python3 -c "
3114
3121
  import json
3115
3122
  import os
3116
3123
 
3117
3124
  learnings_dir = '$learnings_dir'
3118
- query = '$query'.lower()
3125
+ query = '${query_escaped}'.lower()
3119
3126
 
3120
3127
  for filename in ['patterns.jsonl', 'mistakes.jsonl', 'successes.jsonl']:
3121
3128
  filepath = os.path.join(learnings_dir, filename)
package/autonomy/run.sh CHANGED
@@ -582,6 +582,8 @@ else
582
582
  # shellcheck disable=SC2178
583
583
  WORKTREE_PATHS=""
584
584
  fi
585
+ # Track background install PIDs for cleanup (indexed array, works on all bash versions)
586
+ WORKTREE_INSTALL_PIDS=()
585
587
 
586
588
  # Colors
587
589
  RED='\033[0;31m'
@@ -1052,9 +1054,15 @@ import_github_issues() {
1052
1054
  local pending_file=".loki/queue/pending.json"
1053
1055
  local task_count=0
1054
1056
 
1055
- # Ensure pending.json exists
1057
+ # Ensure pending.json exists with correct format for GitHub import
1056
1058
  if [ ! -f "$pending_file" ]; then
1057
1059
  echo '{"tasks":[]}' > "$pending_file"
1060
+ elif jq -e 'type == "array"' "$pending_file" &>/dev/null; then
1061
+ # Normalize bare array format to {"tasks":[...]} for GitHub import compatibility
1062
+ local _tmp_normalize
1063
+ _tmp_normalize=$(mktemp)
1064
+ jq '{tasks: .}' "$pending_file" > "$_tmp_normalize" && mv "$_tmp_normalize" "$pending_file"
1065
+ rm -f "$_tmp_normalize"
1058
1066
  fi
1059
1067
 
1060
1068
  # Parse issues and add to pending queue
@@ -1468,6 +1476,8 @@ create_worktree() {
1468
1476
  cargo build --quiet 2>/dev/null || true
1469
1477
  fi
1470
1478
  ) &
1479
+ # Capture install PID for cleanup on exit
1480
+ WORKTREE_INSTALL_PIDS+=($!)
1471
1481
 
1472
1482
  log_info "Created worktree: $worktree_path"
1473
1483
  return 0
@@ -1776,6 +1786,14 @@ spawn_feature_stream() {
1776
1786
  cleanup_parallel_streams() {
1777
1787
  log_header "Cleaning Up Parallel Streams"
1778
1788
 
1789
+ # Kill background install processes
1790
+ for pid in "${WORKTREE_INSTALL_PIDS[@]}"; do
1791
+ if kill -0 "$pid" 2>/dev/null; then
1792
+ kill "$pid" 2>/dev/null || true
1793
+ fi
1794
+ done
1795
+ WORKTREE_INSTALL_PIDS=()
1796
+
1779
1797
  # Kill all sessions
1780
1798
  for stream in "${!WORKTREE_PIDS[@]}"; do
1781
1799
  local pid="${WORKTREE_PIDS[$stream]}"
@@ -2294,11 +2312,12 @@ write_dashboard_state() {
2294
2312
  local failed_tasks="[]"
2295
2313
  local review_tasks="[]"
2296
2314
 
2297
- [ -f ".loki/queue/pending.json" ] && pending_tasks=$(cat ".loki/queue/pending.json" 2>/dev/null || echo "[]")
2298
- [ -f ".loki/queue/in-progress.json" ] && in_progress_tasks=$(cat ".loki/queue/in-progress.json" 2>/dev/null || echo "[]")
2299
- [ -f ".loki/queue/completed.json" ] && completed_tasks=$(cat ".loki/queue/completed.json" 2>/dev/null || echo "[]")
2300
- [ -f ".loki/queue/failed.json" ] && failed_tasks=$(cat ".loki/queue/failed.json" 2>/dev/null || echo "[]")
2301
- [ -f ".loki/queue/review.json" ] && review_tasks=$(cat ".loki/queue/review.json" 2>/dev/null || echo "[]")
2315
+ # Read queue files, normalizing {"tasks":[...]} format to plain array
2316
+ [ -f ".loki/queue/pending.json" ] && pending_tasks=$(jq 'if type == "object" then .tasks // [] else . end' ".loki/queue/pending.json" 2>/dev/null || echo "[]")
2317
+ [ -f ".loki/queue/in-progress.json" ] && in_progress_tasks=$(jq 'if type == "object" then .tasks // [] else . end' ".loki/queue/in-progress.json" 2>/dev/null || echo "[]")
2318
+ [ -f ".loki/queue/completed.json" ] && completed_tasks=$(jq 'if type == "object" then .tasks // [] else . end' ".loki/queue/completed.json" 2>/dev/null || echo "[]")
2319
+ [ -f ".loki/queue/failed.json" ] && failed_tasks=$(jq 'if type == "object" then .tasks // [] else . end' ".loki/queue/failed.json" 2>/dev/null || echo "[]")
2320
+ [ -f ".loki/queue/review.json" ] && review_tasks=$(jq 'if type == "object" then .tasks // [] else . end' ".loki/queue/review.json" 2>/dev/null || echo "[]")
2302
2321
 
2303
2322
  # Get agents state
2304
2323
  local agents="[]"
@@ -2351,11 +2370,12 @@ write_dashboard_state() {
2351
2370
  council_state=$(cat ".loki/council/state.json" 2>/dev/null || echo '{"enabled":false}')
2352
2371
  fi
2353
2372
 
2354
- # Write comprehensive JSON state
2373
+ # Write comprehensive JSON state (atomic via temp file + mv)
2355
2374
  local project_name=$(basename "$(pwd)")
2356
2375
  local project_path=$(pwd)
2376
+ local _tmp_state="${output_file}.tmp"
2357
2377
 
2358
- cat > "$output_file" << EOF
2378
+ cat > "$_tmp_state" << EOF
2359
2379
  {
2360
2380
  "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
2361
2381
  "version": "$version",
@@ -2396,6 +2416,7 @@ write_dashboard_state() {
2396
2416
  "council": $council_state
2397
2417
  }
2398
2418
  EOF
2419
+ mv "$_tmp_state" "$output_file"
2399
2420
  }
2400
2421
 
2401
2422
  #===============================================================================
@@ -2419,13 +2440,15 @@ track_iteration_start() {
2419
2440
  "provider=${PROVIDER_NAME:-claude}" \
2420
2441
  "prd=${prd:-Codebase Analysis}"
2421
2442
 
2422
- # Create task entry
2443
+ # Create task entry (escape PRD path for safe JSON embedding)
2444
+ local prd_escaped
2445
+ prd_escaped=$(printf '%s' "${prd:-Codebase Analysis}" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g')
2423
2446
  local task_json=$(cat <<EOF
2424
2447
  {
2425
2448
  "id": "$task_id",
2426
2449
  "type": "iteration",
2427
2450
  "title": "Iteration $iteration",
2428
- "description": "PRD: ${prd:-Codebase Analysis}",
2451
+ "description": "PRD: ${prd_escaped}",
2429
2452
  "status": "in_progress",
2430
2453
  "priority": "medium",
2431
2454
  "startedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
@@ -4317,6 +4340,9 @@ build_prompt() {
4317
4340
  local human_directive=""
4318
4341
  if [ -n "${LOKI_HUMAN_INPUT:-}" ]; then
4319
4342
  human_directive="HUMAN_DIRECTIVE (PRIORITY): $LOKI_HUMAN_INPUT Execute this directive BEFORE continuing normal tasks."
4343
+ # Clear after consumption so it doesn't repeat every iteration
4344
+ unset LOKI_HUMAN_INPUT
4345
+ rm -f "${TARGET_DIR:-.}/.loki/HUMAN_INPUT.md"
4320
4346
  fi
4321
4347
 
4322
4348
  # Queue task injection (from dashboard or API)
@@ -4883,8 +4909,8 @@ check_human_intervention() {
4883
4909
  if [ -f "$loki_dir/PAUSE" ]; then
4884
4910
  log_warn "PAUSE file detected - pausing execution"
4885
4911
  notify_intervention_needed "Execution paused via PAUSE file"
4886
- rm -f "$loki_dir/PAUSE"
4887
4912
  handle_pause
4913
+ rm -f "$loki_dir/PAUSE"
4888
4914
  return 1
4889
4915
  fi
4890
4916
 
@@ -4933,6 +4959,12 @@ check_human_intervention() {
4933
4959
  rm -f "$loki_dir/signals/COUNCIL_REVIEW_REQUESTED"
4934
4960
  if type council_vote &>/dev/null && council_vote; then
4935
4961
  log_header "COMPLETION COUNCIL: FORCE REVIEW - PROJECT COMPLETE"
4962
+ # Complete the missing steps: COMPLETED marker, memory consolidation, report
4963
+ touch "$loki_dir/COMPLETED"
4964
+ log_info "Running memory consolidation..."
4965
+ run_memory_consolidation
4966
+ notify_all_complete
4967
+ save_state ${RETRY_COUNT:-0} "council_force_approved" 0
4936
4968
  return 2 # Stop
4937
4969
  fi
4938
4970
  log_info "Council force-review: voted to continue"
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "5.26.2"
10
+ __version__ = "5.27.1"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -5,7 +5,7 @@ FastAPI-based session control endpoints for the Loki Mode dashboard.
5
5
  Provides start/stop/pause/resume functionality and real-time status updates.
6
6
 
7
7
  Usage:
8
- uvicorn dashboard.control:app --host 0.0.0.0 --port 8420
8
+ uvicorn dashboard.control:app --host 0.0.0.0 --port 57374
9
9
  # Or run with the CLI:
10
10
  loki dashboard start
11
11
  """
@@ -510,5 +510,5 @@ async def asyncio_sleep(seconds: float):
510
510
  # Run with uvicorn if executed directly
511
511
  if __name__ == "__main__":
512
512
  import uvicorn
513
- port = int(os.environ.get("LOKI_DASHBOARD_PORT", "8420"))
513
+ port = int(os.environ.get("LOKI_DASHBOARD_PORT", "57374"))
514
514
  uvicorn.run(app, host="0.0.0.0", port=port)