loki-mode 6.3.1 → 6.5.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/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  [![Agent Types](https://img.shields.io/badge/Agent%20Types-41-blue)]()
10
10
  [![Autonomi](https://img.shields.io/badge/Autonomi-autonomi.dev-5B4EEA)](https://www.autonomi.dev/)
11
11
 
12
- **Current Version: v6.2.1**
12
+ **Current Version: v6.5.0**
13
13
 
14
14
  ---
15
15
 
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 minimal human intervention. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v6.3.1
6
+ # Loki Mode v6.5.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -263,4 +263,4 @@ The following features are documented in skill modules but not yet fully automat
263
263
  | Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
264
264
  | Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
265
265
 
266
- **v6.3.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
266
+ **v6.5.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 6.3.1
1
+ 6.5.0
@@ -52,7 +52,7 @@ load_migration_hook_config() {
52
52
  # Parse YAML config safely using read/declare instead of eval
53
53
  while IFS='=' read -r key val; do
54
54
  case "$key" in
55
- HOOK_*) declare -g "$key=$val" ;;
55
+ HOOK_*) printf -v "$key" '%s' "$val" ;;
56
56
  esac
57
57
  done < <(python3 -c "
58
58
  import sys
@@ -224,7 +224,7 @@ try:
224
224
  print(len([s for s in steps if s.get('status') != 'completed']))
225
225
  except: print(-1)
226
226
  " 2>/dev/null || echo -1)
227
- [[ "$pending" -gt 0 ]] && echo "GATE_BLOCKED: ${pending} steps still pending" && return 1
227
+ [[ "$pending" -ne 0 ]] && echo "GATE_BLOCKED: ${pending} steps still pending (or plan missing)" && return 1
228
228
  ;;
229
229
  esac
230
230
 
@@ -236,6 +236,10 @@ hook_on_agent_stop() {
236
236
  local features_path="${LOKI_FEATURES_PATH:-}"
237
237
 
238
238
  [[ "$HOOK_ON_AGENT_STOP_ENABLED" != "true" ]] && return 0
239
+ if [[ -z "$features_path" ]]; then
240
+ echo "HOOK_BLOCKED: LOKI_FEATURES_PATH not set. Cannot verify features."
241
+ return 1
242
+ fi
239
243
  [[ ! -f "$features_path" ]] && return 0
240
244
 
241
245
  local failing
package/autonomy/loki CHANGED
@@ -806,8 +806,35 @@ cmd_start() {
806
806
  }
807
807
 
808
808
  # Check if session is running
809
+ # Usage: is_session_running [session_id]
810
+ # Without args: returns true if ANY session (global or per-session) is running
811
+ # With session_id: returns true only if that specific session is running
809
812
  is_session_running() {
810
- # Check both possible PID files (loki.pid from run.sh, run.pid legacy)
813
+ local target_session="${1:-}"
814
+
815
+ if [ -n "$target_session" ]; then
816
+ # Check specific session
817
+ local session_pid_file="$LOKI_DIR/sessions/$target_session/loki.pid"
818
+ if [ -f "$session_pid_file" ]; then
819
+ local pid
820
+ pid=$(cat "$session_pid_file" 2>/dev/null)
821
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
822
+ return 0
823
+ fi
824
+ fi
825
+ # Also check legacy per-run PID file (run-<number>.pid)
826
+ local run_pid_file="$LOKI_DIR/run-${target_session}.pid"
827
+ if [ -f "$run_pid_file" ]; then
828
+ local pid
829
+ pid=$(cat "$run_pid_file" 2>/dev/null)
830
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
831
+ return 0
832
+ fi
833
+ fi
834
+ return 1
835
+ fi
836
+
837
+ # Check global PID files (loki.pid from run.sh, run.pid legacy)
811
838
  local pid_files=("$LOKI_DIR/loki.pid" "$LOKI_DIR/run.pid")
812
839
  for pid_file in "${pid_files[@]}"; do
813
840
  if [ -f "$pid_file" ]; then
@@ -818,15 +845,151 @@ is_session_running() {
818
845
  fi
819
846
  fi
820
847
  done
848
+
849
+ # Check per-session PIDs
850
+ if [ -d "$LOKI_DIR/sessions" ]; then
851
+ for session_dir in "$LOKI_DIR/sessions"/*/; do
852
+ [ -d "$session_dir" ] || continue
853
+ local pid_file="${session_dir}loki.pid"
854
+ if [ -f "$pid_file" ]; then
855
+ local pid
856
+ pid=$(cat "$pid_file" 2>/dev/null)
857
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
858
+ return 0
859
+ fi
860
+ fi
861
+ done
862
+ fi
821
863
  return 1
822
864
  }
823
865
 
824
- # Stop execution immediately
866
+ # List all running session IDs
867
+ list_running_sessions() {
868
+ local sessions=()
869
+ # Check global session
870
+ if [ -f "$LOKI_DIR/loki.pid" ]; then
871
+ local pid
872
+ pid=$(cat "$LOKI_DIR/loki.pid" 2>/dev/null)
873
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
874
+ sessions+=("global:$pid")
875
+ fi
876
+ fi
877
+ # Check per-session PIDs
878
+ if [ -d "$LOKI_DIR/sessions" ]; then
879
+ for session_dir in "$LOKI_DIR/sessions"/*/; do
880
+ [ -d "$session_dir" ] || continue
881
+ local sid pid_file pid
882
+ sid=$(basename "$session_dir")
883
+ pid_file="${session_dir}loki.pid"
884
+ if [ -f "$pid_file" ]; then
885
+ pid=$(cat "$pid_file" 2>/dev/null)
886
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
887
+ sessions+=("$sid:$pid")
888
+ fi
889
+ fi
890
+ done
891
+ fi
892
+ # Check legacy run-*.pid files
893
+ for run_pid_file in "$LOKI_DIR"/run-*.pid; do
894
+ [ -f "$run_pid_file" ] || continue
895
+ local sid pid
896
+ sid=$(basename "$run_pid_file" .pid)
897
+ sid="${sid#run-}"
898
+ pid=$(cat "$run_pid_file" 2>/dev/null)
899
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
900
+ # Avoid duplicates (if also in sessions/)
901
+ local dup=false
902
+ for s in "${sessions[@]}"; do
903
+ if [[ "$s" == "$sid:"* ]]; then dup=true; break; fi
904
+ done
905
+ if ! $dup; then
906
+ sessions+=("$sid:$pid")
907
+ fi
908
+ fi
909
+ done
910
+ printf '%s\n' "${sessions[@]}"
911
+ }
912
+
913
+ # Kill a PID with SIGTERM, wait, then SIGKILL if needed
914
+ _kill_pid() {
915
+ local pid="$1"
916
+ pkill -P "$pid" 2>/dev/null || true
917
+ kill "$pid" 2>/dev/null || true
918
+ sleep 1
919
+ if kill -0 "$pid" 2>/dev/null; then
920
+ pkill -9 -P "$pid" 2>/dev/null || true
921
+ kill -9 "$pid" 2>/dev/null || true
922
+ fi
923
+ }
924
+
925
+ # Stop a specific session by its session ID
926
+ _stop_session_by_id() {
927
+ local sid="$1"
928
+ local session_dir="$LOKI_DIR/sessions/$sid"
929
+ local pid_file="$session_dir/loki.pid"
930
+
931
+ if [ -f "$pid_file" ]; then
932
+ local pid
933
+ pid=$(cat "$pid_file" 2>/dev/null)
934
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
935
+ _kill_pid "$pid"
936
+ echo -e "${RED}Stopped session $sid (PID: $pid)${NC}"
937
+ fi
938
+ rm -f "$pid_file"
939
+ fi
940
+
941
+ # Also check legacy run-<id>.pid
942
+ local run_pid_file="$LOKI_DIR/run-${sid}.pid"
943
+ if [ -f "$run_pid_file" ]; then
944
+ local pid
945
+ pid=$(cat "$run_pid_file" 2>/dev/null)
946
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
947
+ _kill_pid "$pid"
948
+ fi
949
+ rm -f "$run_pid_file"
950
+ fi
951
+
952
+ # Clean up session lock file
953
+ rm -f "$session_dir/session.lock" 2>/dev/null
954
+ }
955
+
956
+ # Stop a specific session or all sessions
957
+ # Usage: loki stop [session_id]
958
+ # Without args: stops all running sessions
959
+ # With session_id: stops only that specific session
825
960
  cmd_stop() {
961
+ local target_session=""
962
+
963
+ # Parse arguments
964
+ while [[ $# -gt 0 ]]; do
965
+ case "$1" in
966
+ --help|-h)
967
+ echo -e "${BOLD}loki stop${NC} - Stop running sessions (v6.4.0)"
968
+ echo ""
969
+ echo "Usage: loki stop [session-id]"
970
+ echo ""
971
+ echo " loki stop Stop all running sessions"
972
+ echo " loki stop 52 Stop only session #52"
973
+ echo " loki stop 54 Stop only session #54"
974
+ echo ""
975
+ echo "Use 'loki status' to see all running sessions."
976
+ exit 0
977
+ ;;
978
+ -*)
979
+ echo -e "${RED}Unknown option: $1${NC}"
980
+ echo "Run 'loki stop --help' for usage."
981
+ exit 1
982
+ ;;
983
+ *)
984
+ target_session="$1"
985
+ shift
986
+ ;;
987
+ esac
988
+ done
989
+
826
990
  if [ ! -d "$LOKI_DIR" ]; then
827
991
  echo -e "${YELLOW}No .loki directory found.${NC}"
828
992
  echo "No active session to stop."
829
- # Emit error pattern for stop without session (SYN-018)
830
993
  emit_learning_signal error_pattern \
831
994
  --source cli \
832
995
  --action "cmd_stop" \
@@ -837,28 +1000,54 @@ cmd_stop() {
837
1000
  exit 0
838
1001
  fi
839
1002
 
1003
+ # Stop a specific session by ID
1004
+ if [ -n "$target_session" ]; then
1005
+ if is_session_running "$target_session"; then
1006
+ _stop_session_by_id "$target_session"
1007
+ else
1008
+ echo -e "${YELLOW}Session '$target_session' is not running.${NC}"
1009
+ fi
1010
+ return 0
1011
+ fi
1012
+
1013
+ # No session ID given -- stop all running sessions
840
1014
  if is_session_running; then
1015
+ # Stop per-session PIDs first
1016
+ if [ -d "$LOKI_DIR/sessions" ]; then
1017
+ for session_dir in "$LOKI_DIR/sessions"/*/; do
1018
+ [ -d "$session_dir" ] || continue
1019
+ local sid
1020
+ sid=$(basename "$session_dir")
1021
+ if is_session_running "$sid"; then
1022
+ _stop_session_by_id "$sid"
1023
+ fi
1024
+ done
1025
+ fi
1026
+ # Stop legacy run-*.pid sessions
1027
+ for run_pid_file in "$LOKI_DIR"/run-*.pid; do
1028
+ [ -f "$run_pid_file" ] || continue
1029
+ local pid
1030
+ pid=$(cat "$run_pid_file" 2>/dev/null)
1031
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
1032
+ _kill_pid "$pid"
1033
+ local sid
1034
+ sid=$(basename "$run_pid_file" .pid)
1035
+ echo -e "${RED}Stopped session ${sid#run-} (PID: $pid)${NC}"
1036
+ fi
1037
+ rm -f "$run_pid_file"
1038
+ done
1039
+
1040
+ # Stop global session
841
1041
  touch "$LOKI_DIR/STOP"
842
1042
 
843
- # Kill the background process and all its children
844
1043
  local killed_pid=""
845
1044
  for pid_file in "$LOKI_DIR/loki.pid" "$LOKI_DIR/run.pid"; do
846
1045
  if [ -f "$pid_file" ]; then
847
1046
  local pid
848
1047
  pid=$(cat "$pid_file" 2>/dev/null)
849
1048
  if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
850
- # Kill all child processes first (SIGTERM)
851
- pkill -P "$pid" 2>/dev/null || true
852
- # Then kill the parent (SIGTERM)
853
- kill "$pid" 2>/dev/null || true
1049
+ _kill_pid "$pid"
854
1050
  killed_pid="$pid"
855
-
856
- # Wait briefly, then SIGKILL if still alive
857
- sleep 1
858
- if kill -0 "$pid" 2>/dev/null; then
859
- pkill -9 -P "$pid" 2>/dev/null || true
860
- kill -9 "$pid" 2>/dev/null || true
861
- fi
862
1051
  fi
863
1052
  rm -f "$pid_file"
864
1053
  fi
@@ -871,7 +1060,6 @@ cmd_stop() {
871
1060
 
872
1061
  # Mark session.json as stopped (skill-invoked sessions)
873
1062
  if [ -f "$LOKI_DIR/session.json" ]; then
874
- # Use python for reliable JSON update, fall back to overwrite
875
1063
  python3 -c "
876
1064
  import json, sys, os
877
1065
  try:
@@ -1162,6 +1350,29 @@ cmd_status() {
1162
1350
  echo -e "${DIM} Switch with: loki provider set <claude|codex|gemini>${NC}"
1163
1351
  echo ""
1164
1352
 
1353
+ # Show running sessions (v6.4.0 - concurrent session support)
1354
+ local running_sessions=()
1355
+ while IFS= read -r line; do
1356
+ [ -n "$line" ] && running_sessions+=("$line")
1357
+ done < <(list_running_sessions 2>/dev/null)
1358
+
1359
+ if [ ${#running_sessions[@]} -gt 0 ]; then
1360
+ echo -e "${GREEN}Active Sessions: ${#running_sessions[@]}${NC}"
1361
+ for entry in "${running_sessions[@]}"; do
1362
+ local sid="${entry%%:*}"
1363
+ local spid="${entry#*:}"
1364
+ if [ "$sid" = "global" ]; then
1365
+ echo -e " ${CYAN}[global]${NC} PID $spid"
1366
+ else
1367
+ echo -e " ${CYAN}[#$sid]${NC} PID $spid"
1368
+ fi
1369
+ done
1370
+ echo ""
1371
+ echo -e "${DIM} Stop specific: loki stop <session-id>${NC}"
1372
+ echo -e "${DIM} Stop all: loki stop${NC}"
1373
+ echo ""
1374
+ fi
1375
+
1165
1376
  # Check for signals first (more prominent)
1166
1377
  if [ -f "$LOKI_DIR/PAUSE" ]; then
1167
1378
  echo -e "${YELLOW}Status: PAUSED${NC}"
@@ -1881,7 +2092,7 @@ cmd_dashboard_stop() {
1881
2092
  local wait_count=0
1882
2093
  while kill -0 "$pid" 2>/dev/null && [ $wait_count -lt 10 ]; do
1883
2094
  sleep 0.5
1884
- ((wait_count++))
2095
+ wait_count=$((wait_count + 1))
1885
2096
  done
1886
2097
 
1887
2098
  # Force kill if still running
@@ -2663,20 +2874,17 @@ cmd_run() {
2663
2874
  --pr)
2664
2875
  use_worktree=true
2665
2876
  create_pr=true
2666
- start_args+=("--parallel")
2667
2877
  shift
2668
2878
  ;;
2669
2879
  --ship)
2670
2880
  use_worktree=true
2671
2881
  create_pr=true
2672
2882
  auto_merge=true
2673
- start_args+=("--parallel")
2674
2883
  shift
2675
2884
  ;;
2676
2885
  --detach|-d)
2677
2886
  use_worktree=true
2678
2887
  run_detached=true
2679
- start_args+=("--parallel")
2680
2888
  shift
2681
2889
  ;;
2682
2890
  -*)
@@ -2693,6 +2901,11 @@ cmd_run() {
2693
2901
  esac
2694
2902
  done
2695
2903
 
2904
+ # Add --parallel once if worktree mode is enabled (not per-flag)
2905
+ if $use_worktree; then
2906
+ start_args+=("--parallel")
2907
+ fi
2908
+
2696
2909
  if [[ -z "$issue_ref" ]]; then
2697
2910
  echo -e "${RED}Error: Issue reference required${NC}"
2698
2911
  echo ""
@@ -2740,6 +2953,12 @@ cmd_run() {
2740
2953
  fi
2741
2954
  echo ""
2742
2955
 
2956
+ # Per-session locking (v6.4.0): export session ID so run.sh uses
2957
+ # per-session PID/lock files instead of the global lock. This allows
2958
+ # multiple concurrent `loki run` sessions (e.g., loki run 52 -d && loki run 54 -d).
2959
+ local session_id="${number:-$(date +%s)}"
2960
+ export LOKI_SESSION_ID="$session_id"
2961
+
2743
2962
  # Progressive isolation: set up worktree branch naming
2744
2963
  if $use_worktree; then
2745
2964
  local branch_name="issue/${issue_provider}-${number:-$(date +%s)}"
@@ -2754,6 +2973,13 @@ cmd_run() {
2754
2973
 
2755
2974
  # Detached mode: fork to background
2756
2975
  if $run_detached; then
2976
+ # Guard: prevent launching duplicate session
2977
+ if is_session_running "$session_id"; then
2978
+ echo -e "${RED}Error: Session '$session_id' is already running.${NC}"
2979
+ echo -e "Stop it first with: ${CYAN}loki stop $session_id${NC}"
2980
+ exit 1
2981
+ fi
2982
+
2757
2983
  local log_file="$LOKI_DIR/logs/run-${number:-$(date +%s)}.log"
2758
2984
  mkdir -p "$(dirname "$log_file")"
2759
2985
  echo -e "${GREEN}Running detached. Logs: $log_file${NC}"
@@ -2768,31 +2994,51 @@ cmd_run() {
2768
2994
  branch_name="issue/detach-$(date +%s)"
2769
2995
  fi
2770
2996
 
2771
- nohup bash -c "
2772
- cd $(pwd)
2773
- export LOKI_DETACHED=true
2774
- export LOKI_PARALLEL_MODE=true
2775
- export LOKI_WORKTREE_BRANCH=\"$branch_name\"
2776
- $(command -v loki || echo "$0") start \"$detach_prd\" --parallel ${start_args[*]+"${start_args[*]}"}
2777
-
2778
- # Post-completion: create PR if requested
2779
- if [[ \"$create_pr\" == \"true\" ]]; then
2780
- branch_current=\$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"\")
2781
- if [[ -n \"\$branch_current\" && \"\$branch_current\" != \"main\" && \"\$branch_current\" != \"master\" ]]; then
2782
- git push origin \"\$branch_current\" 2>/dev/null || true
2783
- gh pr create --title \"${title:-Implementation for issue ${issue_ref}}\" --body \"Implemented by Loki Mode\" --head \"\$branch_current\" 2>/dev/null || true
2784
- fi
2785
- fi
2786
- # Post-completion: auto-merge if requested
2787
- if [[ \"$auto_merge\" == \"true\" ]]; then
2788
- branch_current=\$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"\")
2789
- if gh pr merge \"\$branch_current\" --squash --delete-branch 2>/dev/null; then
2790
- if [[ -n \"${number:-}\" ]]; then
2791
- gh issue close \"$number\" --comment \"Resolved by Loki Mode\" 2>/dev/null || true
2792
- fi
2793
- fi
2794
- fi
2795
- " > "$log_file" 2>&1 &
2997
+ # Write a temp script to avoid shell injection via variable interpolation
2998
+ local run_script="$LOKI_DIR/scripts/run-${number:-detached}.sh"
2999
+ mkdir -p "$LOKI_DIR/scripts"
3000
+ local loki_cmd
3001
+ loki_cmd="$(command -v loki || echo "$0")"
3002
+ cat > "$run_script" << 'INNER_SCRIPT_EOF'
3003
+ #!/usr/bin/env bash
3004
+ set -euo pipefail
3005
+ cd "$LOKI_RUN_DIR"
3006
+ export LOKI_DETACHED=true
3007
+ export LOKI_PARALLEL_MODE=true
3008
+ "$LOKI_CMD" start "$LOKI_PRD_PATH" ${LOKI_START_ARGS:-}
3009
+
3010
+ # Post-completion: create PR if requested
3011
+ if [[ "$LOKI_CREATE_PR" == "true" ]]; then
3012
+ branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
3013
+ if [[ -n "$branch_current" && "$branch_current" != "main" && "$branch_current" != "master" ]]; then
3014
+ git push origin "$branch_current" 2>/dev/null || true
3015
+ gh pr create --title "$LOKI_PR_TITLE" --body "Implemented by Loki Mode" --head "$branch_current" 2>/dev/null || true
3016
+ fi
3017
+ fi
3018
+ # Post-completion: auto-merge if requested
3019
+ if [[ "$LOKI_AUTO_MERGE" == "true" ]]; then
3020
+ branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
3021
+ if gh pr merge "$branch_current" --squash --delete-branch 2>/dev/null; then
3022
+ if [[ -n "${LOKI_ISSUE_NUMBER:-}" ]]; then
3023
+ gh issue close "$LOKI_ISSUE_NUMBER" --comment "Resolved by Loki Mode" 2>/dev/null || true
3024
+ fi
3025
+ fi
3026
+ fi
3027
+ INNER_SCRIPT_EOF
3028
+ chmod +x "$run_script"
3029
+
3030
+ # Pass all variables safely via environment
3031
+ LOKI_RUN_DIR="$(pwd)" \
3032
+ LOKI_CMD="$loki_cmd" \
3033
+ LOKI_SESSION_ID="$session_id" \
3034
+ LOKI_WORKTREE_BRANCH="$branch_name" \
3035
+ LOKI_PRD_PATH="$detach_prd" \
3036
+ LOKI_START_ARGS="${start_args[*]+"${start_args[*]}"}" \
3037
+ LOKI_CREATE_PR="$create_pr" \
3038
+ LOKI_AUTO_MERGE="$auto_merge" \
3039
+ LOKI_PR_TITLE="${title:-Implementation for issue ${issue_ref}}" \
3040
+ LOKI_ISSUE_NUMBER="${number:-}" \
3041
+ nohup bash "$run_script" > "$log_file" 2>&1 &
2796
3042
 
2797
3043
  local bg_pid=$!
2798
3044
  echo "$bg_pid" > "$LOKI_DIR/run-${number:-detached}.pid"
@@ -4728,7 +4974,7 @@ cmd_notify_test() {
4728
4974
  echo -n " Slack... "
4729
4975
  if send_slack_notification "$message" "Test"; then
4730
4976
  echo -e "${GREEN}OK${NC}"
4731
- ((channels_notified++))
4977
+ channels_notified=$((channels_notified + 1))
4732
4978
  else
4733
4979
  echo -e "${RED}FAILED${NC}"
4734
4980
  fi
@@ -4741,7 +4987,7 @@ cmd_notify_test() {
4741
4987
  echo -n " Discord... "
4742
4988
  if send_discord_notification "$message" "Test"; then
4743
4989
  echo -e "${GREEN}OK${NC}"
4744
- ((channels_notified++))
4990
+ channels_notified=$((channels_notified + 1))
4745
4991
  else
4746
4992
  echo -e "${RED}FAILED${NC}"
4747
4993
  fi
@@ -4754,7 +5000,7 @@ cmd_notify_test() {
4754
5000
  echo -n " Webhook... "
4755
5001
  if send_webhook_notification "$message" "Test"; then
4756
5002
  echo -e "${GREEN}OK${NC}"
4757
- ((channels_notified++))
5003
+ channels_notified=$((channels_notified + 1))
4758
5004
  else
4759
5005
  echo -e "${RED}FAILED${NC}"
4760
5006
  fi
@@ -6473,7 +6719,7 @@ Tasks:
6473
6719
  local provider_name="${LOKI_PROVIDER:-claude}"
6474
6720
  case "$provider_name" in
6475
6721
  claude)
6476
- (cd "$codebase_path" && claude --dangerously-skip-permissions -p "$phase_prompt" --output-format stream-json --verbose 2>&1) | \
6722
+ { (cd "$codebase_path" && claude --dangerously-skip-permissions -p "$phase_prompt" --output-format stream-json --verbose 2>&1) | \
6477
6723
  while IFS= read -r line; do
6478
6724
  # Extract text from stream-json
6479
6725
  if echo "$line" | python3 -c "
@@ -6488,8 +6734,7 @@ except Exception: pass
6488
6734
  " 2>/dev/null; then
6489
6735
  true
6490
6736
  fi
6491
- done
6492
- phase_exit=${PIPESTATUS[0]}
6737
+ done; } && phase_exit=0 || phase_exit=$?
6493
6738
  ;;
6494
6739
  codex)
6495
6740
  (cd "$codebase_path" && codex exec --full-auto "$phase_prompt" 2>&1) || phase_exit=$?
@@ -6636,7 +6881,7 @@ IMPORTANT RULES:
6636
6881
  local provider_name="${LOKI_PROVIDER:-claude}"
6637
6882
  case "$provider_name" in
6638
6883
  claude)
6639
- (cd "$codebase_path" && claude --dangerously-skip-permissions -p "$doc_prompt" --output-format stream-json --verbose 2>&1) | \
6884
+ { (cd "$codebase_path" && claude --dangerously-skip-permissions -p "$doc_prompt" --output-format stream-json --verbose 2>&1) | \
6640
6885
  while IFS= read -r line; do
6641
6886
  if echo "$line" | python3 -c "
6642
6887
  import sys, json
@@ -6650,8 +6895,7 @@ except Exception: pass
6650
6895
  " 2>/dev/null; then
6651
6896
  true
6652
6897
  fi
6653
- done
6654
- doc_exit=${PIPESTATUS[0]}
6898
+ done; } && doc_exit=0 || doc_exit=$?
6655
6899
  ;;
6656
6900
  codex)
6657
6901
  (cd "$codebase_path" && codex exec --full-auto "$doc_prompt" 2>&1) || doc_exit=$?
@@ -7181,7 +7425,7 @@ main() {
7181
7425
  cmd_init "$@"
7182
7426
  ;;
7183
7427
  stop)
7184
- cmd_stop
7428
+ cmd_stop "$@"
7185
7429
  ;;
7186
7430
  cleanup)
7187
7431
  cmd_cleanup "$@"