juno-code 1.0.41 → 1.0.43

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.
@@ -52,10 +52,9 @@ BLUE='\033[0;34m'
52
52
  NC='\033[0m' # No Color
53
53
 
54
54
  # Required packages
55
- REQUIRED_PACKAGES=("juno-kanban" "roundtable-ai")
56
-
57
- # Slack integration dependencies (optional, only installed when Slack scripts are used)
58
- SLACK_PACKAGES=("slack_sdk" "python-dotenv")
55
+ # Note: requests and python-dotenv are required by github.py
56
+ # slack_sdk is required by Slack integration scripts (slack_fetch.py, slack_respond.py)
57
+ REQUIRED_PACKAGES=("juno-kanban" "roundtable-ai" "requests" "python-dotenv" "slack_sdk")
59
58
 
60
59
  # Version check cache configuration
61
60
  # This ensures we don't check PyPI on every run (performance optimization per Task RTafs5)
@@ -9,6 +9,12 @@
9
9
  #
10
10
  # Usage: ./.juno_task/scripts/kanban.sh [juno-kanban arguments]
11
11
  # Example: ./.juno_task/scripts/kanban.sh list --limit 5
12
+ # Example: ./.juno_task/scripts/kanban.sh list -f json --raw # (flag order normalized)
13
+ # Example: ./.juno_task/scripts/kanban.sh -f json --raw list # (also works)
14
+ #
15
+ # Note: Global flags (-f/--format, -p/--pretty, --raw, -v/--verbose, -c/--config)
16
+ # can be placed anywhere in the command line. This wrapper normalizes them
17
+ # to appear before the command for juno-kanban compatibility.
12
18
  #
13
19
  # Environment Variables:
14
20
  # JUNO_DEBUG=true - Show [DEBUG] diagnostic messages
@@ -170,6 +176,67 @@ PROJECT_ROOT="$( cd "$SCRIPT_DIR/../.." && pwd )"
170
176
  # Change to project root
171
177
  cd "$PROJECT_ROOT"
172
178
 
179
+ # Arrays to store normalized arguments (declared at script level for proper handling)
180
+ declare -a NORMALIZED_GLOBAL_FLAGS=()
181
+ declare -a NORMALIZED_COMMAND_ARGS=()
182
+
183
+ # Normalize argument order for juno-kanban
184
+ # juno-kanban requires global flags BEFORE the command, but users often
185
+ # write them after (e.g., "list -f json --raw" instead of "-f json --raw list")
186
+ # This function reorders arguments so global flags come first.
187
+ # Results are stored in NORMALIZED_GLOBAL_FLAGS and NORMALIZED_COMMAND_ARGS arrays.
188
+ normalize_arguments() {
189
+ # Reset arrays
190
+ NORMALIZED_GLOBAL_FLAGS=()
191
+ NORMALIZED_COMMAND_ARGS=()
192
+ local found_command=false
193
+
194
+ # Known subcommands
195
+ local commands="create search get show update archive mark list merge"
196
+
197
+ while [[ $# -gt 0 ]]; do
198
+ case $1 in
199
+ # Global flags that take a value
200
+ -f|--format|-c|--config)
201
+ if [[ -n "${2:-}" ]]; then
202
+ NORMALIZED_GLOBAL_FLAGS+=("$1" "$2")
203
+ shift 2
204
+ else
205
+ NORMALIZED_GLOBAL_FLAGS+=("$1")
206
+ shift
207
+ fi
208
+ ;;
209
+ # Global flags that don't take a value
210
+ -p|--pretty|--raw|-v|--verbose|-h|--help|--version)
211
+ NORMALIZED_GLOBAL_FLAGS+=("$1")
212
+ shift
213
+ ;;
214
+ # Check if this is a known command
215
+ *)
216
+ # Check if this argument is a known command
217
+ local is_command=false
218
+ for cmd in $commands; do
219
+ if [[ "$1" == "$cmd" ]]; then
220
+ is_command=true
221
+ found_command=true
222
+ break
223
+ fi
224
+ done
225
+
226
+ # If we found a command, everything from here goes to command_args
227
+ if $is_command || $found_command; then
228
+ NORMALIZED_COMMAND_ARGS+=("$1")
229
+ found_command=true
230
+ else
231
+ # Before finding a command, treat as command arg
232
+ NORMALIZED_COMMAND_ARGS+=("$1")
233
+ fi
234
+ shift
235
+ ;;
236
+ esac
237
+ done
238
+ }
239
+
173
240
  # Main kanban logic
174
241
  main() {
175
242
  log_info "=== juno-kanban Wrapper ==="
@@ -182,11 +249,26 @@ main() {
182
249
 
183
250
  log_success "Python environment ready!"
184
251
 
185
- # Execute juno-kanban with all passed arguments from project root
252
+ # Normalize argument order (global flags before command)
253
+ # This allows users to write "list -f json --raw" which gets reordered to "-f json --raw list"
254
+ normalize_arguments "$@"
255
+
256
+ if [ "${JUNO_DEBUG:-false}" = "true" ]; then
257
+ echo "[DEBUG] Original args: $*" >&2
258
+ echo "[DEBUG] Normalized global flags: ${NORMALIZED_GLOBAL_FLAGS[*]:-<none>}" >&2
259
+ echo "[DEBUG] Normalized command args: ${NORMALIZED_COMMAND_ARGS[*]:-<none>}" >&2
260
+ fi
261
+
262
+ # Execute juno-kanban with normalized arguments from project root
186
263
  # Close stdin (redirect from /dev/null) to prevent hanging when called from tools
187
264
  # that don't provide stdin (similar to Issue #42 hook fix)
188
- log_info "Executing juno-kanban: $*"
189
- juno-kanban "$@" < /dev/null
265
+ # Build the command properly preserving argument quoting
266
+ log_info "Executing juno-kanban with normalized arguments"
267
+
268
+ # Execute with proper array expansion to preserve quoting
269
+ # Use ${arr[@]+"${arr[@]}"} pattern to handle empty arrays with set -u
270
+ juno-kanban ${NORMALIZED_GLOBAL_FLAGS[@]+"${NORMALIZED_GLOBAL_FLAGS[@]}"} \
271
+ ${NORMALIZED_COMMAND_ARGS[@]+"${NORMALIZED_COMMAND_ARGS[@]}"} < /dev/null
190
272
  }
191
273
 
192
274
  # Run main function with all arguments
@@ -4,11 +4,10 @@
4
4
  #
5
5
  # Purpose: Continuously run juno-code until all kanban tasks are completed
6
6
  #
7
- # This script uses a do-while loop pattern: it runs juno-code at least once,
8
- # then checks the kanban board for tasks in backlog, todo, or in_progress status.
9
- # If tasks remain, it continues running juno-code. This ensures juno-code's
10
- # internal task management systems get a chance to operate even if kanban.sh
11
- # doesn't initially detect any tasks.
7
+ # This script uses a while loop pattern: it ALWAYS runs pre-run hooks/commands,
8
+ # then checks the kanban board for tasks BEFORE running juno-code. If no tasks
9
+ # exist, juno-code is NOT executed. This allows pre-run hooks (e.g., Slack sync,
10
+ # GitHub sync) to create tasks that will then be processed by juno-code.
12
11
  #
13
12
  # Usage: ./.juno_task/scripts/run_until_completion.sh [options] [juno-code arguments]
14
13
  # Example: ./.juno_task/scripts/run_until_completion.sh -s claude -i 5 -v
@@ -33,6 +32,9 @@
33
32
  # - Using the flag multiple times: --pre-run-hook A --pre-run-hook B
34
33
  # - Comma-separated: --pre-run-hook "A,B,C"
35
34
  # - Pipe-separated: --pre-run-hook "A|B|C"
35
+ # --stale-threshold <n> - Number of stale iterations before exiting (default: 3)
36
+ # Set to 0 to disable stale detection
37
+ # --no-stale-check - Alias for --stale-threshold 0
36
38
  #
37
39
  # All other arguments are forwarded to juno-code.
38
40
  # The script shows all stdout/stderr from juno-code in real-time.
@@ -42,8 +44,16 @@
42
44
  # JUNO_VERBOSE=true - Show [RUN_UNTIL] informational messages
43
45
  # JUNO_PRE_RUN - Alternative way to specify pre-run command (env var)
44
46
  # JUNO_PRE_RUN_HOOK - Alternative way to specify pre-run hook name (env var)
47
+ # JUNO_STALE_THRESHOLD - Number of stale iterations before exiting (default: 3)
48
+ # Set to 0 to disable stale detection
45
49
  # (JUNO_DEBUG and JUNO_VERBOSE default to false for silent operation)
46
50
  #
51
+ # Stale Iteration Detection:
52
+ # The script tracks kanban state (task IDs and statuses) between iterations.
53
+ # If no changes are detected for JUNO_STALE_THRESHOLD consecutive iterations,
54
+ # the script will exit to prevent infinite loops where the agent doesn't
55
+ # process any tasks.
56
+ #
47
57
  # Created by: juno-code init command
48
58
  # Date: Auto-generated during project initialization
49
59
 
@@ -66,6 +76,12 @@ NC='\033[0m' # No Color
66
76
  SCRIPTS_DIR=".juno_task/scripts"
67
77
  KANBAN_SCRIPT="${SCRIPTS_DIR}/kanban.sh"
68
78
 
79
+ # Stale iteration detection configuration
80
+ # Number of consecutive iterations without kanban changes before exiting
81
+ STALE_THRESHOLD="${JUNO_STALE_THRESHOLD:-3}"
82
+ STALE_COUNTER=0
83
+ PREVIOUS_KANBAN_STATE=""
84
+
69
85
  # Arrays to store pre-run commands, hooks, and juno-code arguments
70
86
  declare -a PRE_RUN_CMDS=()
71
87
  declare -a PRE_RUN_HOOKS=()
@@ -86,6 +102,22 @@ parse_arguments() {
86
102
  PRE_RUN_CMDS+=("$2")
87
103
  shift 2
88
104
  ;;
105
+ --stale-threshold)
106
+ if [[ -z "${2:-}" ]]; then
107
+ echo "[ERROR] --stale-threshold requires a number argument" >&2
108
+ exit 1
109
+ fi
110
+ if ! [[ "$2" =~ ^[0-9]+$ ]]; then
111
+ echo "[ERROR] --stale-threshold must be a non-negative integer, got: $2" >&2
112
+ exit 1
113
+ fi
114
+ STALE_THRESHOLD="$2"
115
+ shift 2
116
+ ;;
117
+ --no-stale-check)
118
+ STALE_THRESHOLD=0
119
+ shift
120
+ ;;
89
121
  --pre-run-hook|--pre-run-hooks|--run-pre-hook|--run-pre-hooks)
90
122
  if [[ -z "${2:-}" ]]; then
91
123
  echo "[ERROR] $1 requires a hook name argument" >&2
@@ -327,6 +359,85 @@ PROJECT_ROOT="$( cd "$SCRIPT_DIR/../.." && pwd )"
327
359
  # Change to project root
328
360
  cd "$PROJECT_ROOT"
329
361
 
362
+ # Function to get a snapshot of kanban state for comparison
363
+ # Uses the summary statistics (total_tasks + status_counts) as a reliable indicator
364
+ # of kanban state changes. This approach is more robust than parsing multi-line JSON
365
+ # which can contain control characters that break jq parsing.
366
+ get_kanban_state_snapshot() {
367
+ local snapshot=""
368
+
369
+ # Check if kanban script exists
370
+ if [ ! -f "$KANBAN_SCRIPT" ]; then
371
+ echo ""
372
+ return
373
+ fi
374
+
375
+ # Use --raw format for cleaner JSON output
376
+ # The --raw flag outputs: line 1 = tasks array, line 2 = summary object
377
+ local kanban_output
378
+ if kanban_output=$("$KANBAN_SCRIPT" -f json --raw list --status backlog todo in_progress 2>/dev/null); then
379
+ if command -v jq &> /dev/null; then
380
+ # Extract the summary line (last line of --raw output)
381
+ local summary_line
382
+ summary_line=$(echo "$kanban_output" | tail -1)
383
+
384
+ # Extract status counts from summary - this is a reliable state indicator
385
+ # Format: "backlog:N|done:N|in_progress:N|todo:N|archive:N|total:N"
386
+ local summary_snapshot=""
387
+ if [[ -n "$summary_line" ]] && echo "$summary_line" | grep -q '"summary"'; then
388
+ summary_snapshot=$(echo "$summary_line" | jq -r '
389
+ .summary |
390
+ "backlog:\(.status_counts.backlog // 0)|" +
391
+ "todo:\(.status_counts.todo // 0)|" +
392
+ "in_progress:\(.status_counts.in_progress // 0)|" +
393
+ "done:\(.status_counts.done // 0)|" +
394
+ "archive:\(.status_counts.archive // 0)|" +
395
+ "total:\(.total_tasks // 0)"
396
+ ' 2>/dev/null)
397
+ fi
398
+
399
+ # Also try to extract task IDs using grep (more robust than jq for multi-line JSON)
400
+ # This catches cases where tasks are added/removed but counts stay the same
401
+ local task_ids=""
402
+ task_ids=$(echo "$kanban_output" | grep -o '"id": *"[^"]*"' | sed 's/"id": *"\([^"]*\)"/\1/' | sort | tr '\n' ',')
403
+
404
+ # Combine summary stats and task IDs for comprehensive state tracking
405
+ if [[ -n "$summary_snapshot" ]]; then
406
+ snapshot="${summary_snapshot}|ids:${task_ids}"
407
+ elif [[ -n "$task_ids" ]]; then
408
+ # Fallback to just task IDs if summary parsing failed
409
+ snapshot="ids:${task_ids}"
410
+ fi
411
+ else
412
+ # Fallback without jq: use grep to extract id and status fields
413
+ snapshot=$(echo "$kanban_output" | grep -E '"id"|"status"' | tr -d ' \n')
414
+ fi
415
+ fi
416
+
417
+ echo "$snapshot"
418
+ }
419
+
420
+ # Function to check if kanban state has changed
421
+ # Returns 0 if state changed, 1 if stale (no change)
422
+ check_kanban_state_changed() {
423
+ local current_state
424
+ current_state=$(get_kanban_state_snapshot)
425
+
426
+ if [ "${JUNO_DEBUG:-false}" = "true" ]; then
427
+ echo "[DEBUG] Previous kanban state: $PREVIOUS_KANBAN_STATE" >&2
428
+ echo "[DEBUG] Current kanban state: $current_state" >&2
429
+ fi
430
+
431
+ if [[ "$current_state" == "$PREVIOUS_KANBAN_STATE" ]]; then
432
+ # State is the same - no changes detected
433
+ return 1
434
+ else
435
+ # State changed
436
+ PREVIOUS_KANBAN_STATE="$current_state"
437
+ return 0
438
+ fi
439
+ }
440
+
330
441
  # Function to check if there are tasks remaining
331
442
  has_remaining_tasks() {
332
443
  log_info "Checking kanban for remaining tasks..."
@@ -391,19 +502,37 @@ main() {
391
502
  log_status "Maximum iterations: unlimited"
392
503
  fi
393
504
 
505
+ if [ "$STALE_THRESHOLD" -gt 0 ]; then
506
+ log_status "Stale iteration threshold: $STALE_THRESHOLD"
507
+ else
508
+ log_status "Stale iteration detection: disabled"
509
+ fi
510
+
511
+ # Capture initial kanban state before first iteration
512
+ PREVIOUS_KANBAN_STATE=$(get_kanban_state_snapshot)
513
+
394
514
  # Check if we have any arguments for juno-code
395
515
  if [[ ${#JUNO_ARGS[@]} -eq 0 ]]; then
396
516
  log_warning "No arguments provided. Running juno-code with no arguments."
397
517
  fi
398
518
 
399
- # Execute pre-run hooks and commands before entering the main loop
400
- # Hooks run first, then explicit commands
519
+ # ALWAYS execute pre-run hooks and commands before checking for tasks
520
+ # This ensures hooks (e.g., Slack sync, GitHub sync) can create tasks
521
+ # that will then be processed by juno-code
401
522
  execute_pre_run_hooks
402
523
  execute_pre_run_commands
403
524
 
404
- # Do-while loop pattern: Run juno-code at least once, then continue while tasks remain
405
- # This ensures juno-code's internal task management systems get a chance to operate
406
- # even if kanban.sh doesn't initially detect any tasks
525
+ # Check for tasks BEFORE entering the main loop
526
+ # If no tasks exist after running pre-run hooks, exit gracefully
527
+ if ! has_remaining_tasks; then
528
+ log_success ""
529
+ log_success "=========================================="
530
+ log_success "No tasks found in kanban. Pre-run hooks executed, juno-code skipped."
531
+ log_success "=========================================="
532
+ exit 0
533
+ fi
534
+
535
+ # While loop pattern: Only run juno-code if there are tasks to process
407
536
  while true; do
408
537
  iteration=$((iteration + 1))
409
538
 
@@ -425,7 +554,6 @@ main() {
425
554
  log_status "------------------------------------------"
426
555
 
427
556
  # Run juno-code with parsed arguments (excluding --pre-run which was already processed)
428
- # We run juno-code FIRST (do-while pattern), then check for remaining tasks
429
557
  if juno-code "${JUNO_ARGS[@]}"; then
430
558
  log_success "juno-code completed successfully"
431
559
  else
@@ -441,9 +569,57 @@ main() {
441
569
  # Small delay to prevent rapid-fire execution and allow user to Ctrl+C if needed
442
570
  sleep 1
443
571
 
444
- # Check for remaining tasks AFTER running juno-code (do-while pattern)
445
- # This ensures juno-code runs at least once, allowing its internal task
446
- # management systems to check kanban for updates
572
+ # Check for stale iterations (no kanban state changes)
573
+ # This prevents infinite loops where agent doesn't process any tasks
574
+ if [ "$STALE_THRESHOLD" -gt 0 ]; then
575
+ if check_kanban_state_changed; then
576
+ # State changed - reset the stale counter
577
+ STALE_COUNTER=0
578
+ log_info "Kanban state changed. Stale counter reset."
579
+ else
580
+ # State unchanged - increment stale counter
581
+ STALE_COUNTER=$((STALE_COUNTER + 1))
582
+ log_warning "No kanban changes detected. Stale iteration count: $STALE_COUNTER/$STALE_THRESHOLD"
583
+
584
+ if [ "$STALE_COUNTER" -ge "$STALE_THRESHOLD" ]; then
585
+ # Execute ON_STALE hook before exiting
586
+ log_status ""
587
+ log_status "Executing ON_STALE hook due to stale iteration detection"
588
+ execute_hook_commands "ON_STALE"
589
+
590
+ log_error ""
591
+ log_error "=========================================="
592
+ log_error "STALE ITERATION LIMIT REACHED"
593
+ log_error "=========================================="
594
+ log_error ""
595
+ log_error "The script has run $STALE_COUNTER consecutive iterations"
596
+ log_error "without any changes to the kanban board state."
597
+ log_error ""
598
+ log_error "This typically happens when:"
599
+ log_error " 1. The agent doesn't recognize or prioritize remaining tasks"
600
+ log_error " 2. Tasks are stuck in a state the agent cannot process"
601
+ log_error " 3. There's a configuration or prompt issue"
602
+ log_error ""
603
+ log_error "Remaining tasks are still in the kanban system but"
604
+ log_error "the agent is not making progress on them."
605
+ log_error ""
606
+ log_error "To adjust this threshold, set JUNO_STALE_THRESHOLD"
607
+ log_error "environment variable (current: $STALE_THRESHOLD, default: 3)"
608
+ log_error "Set to 0 to disable stale detection."
609
+ log_error ""
610
+ log_error "=========================================="
611
+ # Also print to stdout so it's visible in all contexts
612
+ echo ""
613
+ echo "STALE ITERATION LIMIT REACHED: No kanban changes detected for $STALE_COUNTER iterations."
614
+ echo "The agent is not processing remaining tasks. Exiting to prevent infinite loop."
615
+ echo "Set JUNO_STALE_THRESHOLD=0 to disable this check, or increase the threshold value."
616
+ echo ""
617
+ exit 2
618
+ fi
619
+ fi
620
+ fi
621
+
622
+ # Check for remaining tasks AFTER running juno-code
447
623
  if ! has_remaining_tasks; then
448
624
  log_success ""
449
625
  log_success "=========================================="
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juno-code",
3
- "version": "1.0.41",
3
+ "version": "1.0.43",
4
4
  "description": "Ralph Wiggum meet Kanban! Ralph style execution for [Claude Code, Codex, Gemini, Cursor]. One task per iteration, automatic progress tracking, and git commits. Set it and let it run.",
5
5
  "keywords": [
6
6
  "Ralph",