agentic-loop 3.26.0 → 3.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.
@@ -6,8 +6,6 @@ description: Rename the current terminal tab so you can tell your Claude Code ta
6
6
 
7
7
  The user wants to rename the current terminal tab. This is useful when you have multiple Claude Code sessions open and every tab just shows "...skip-permissions".
8
8
 
9
- > **Note:** This uses AppleScript and only works in macOS Terminal.app and iTerm2.
10
-
11
9
  ## Step 1: Determine the Tab Name
12
10
 
13
11
  Check if the user provided an argument: `$ARGUMENTS`
@@ -27,20 +25,47 @@ If the user selects "Other", use their custom text as the tab name.
27
25
 
28
26
  ## Step 2: Set the Tab Title
29
27
 
30
- Detect which terminal is running and set the title:
28
+ Use `$TERM_PROGRAM` (via Bash: `echo $TERM_PROGRAM`) to detect the terminal, then apply the right method. **Important:** Escape any double quotes in the tab name before embedding in AppleScript strings.
29
+
30
+ ### iTerm2 (`TERM_PROGRAM=iTerm.app`)
31
+
32
+ Use iTerm2's proprietary escape sequence — this is the most reliable method:
31
33
 
32
34
  ```bash
33
- # Try Terminal.app first
34
- osascript -e 'tell application "Terminal" to set custom title of selected tab of front window to "TAB_NAME"' 2>/dev/null
35
+ printf '\033]1337;SetUserVar=tab_title=%s\007' "$(echo -n 'TAB_NAME' | base64)"
35
36
  ```
36
37
 
37
- If that fails (not Terminal.app), try iTerm2:
38
+ Then also set the session name via osascript as a fallback:
38
39
 
39
40
  ```bash
40
41
  osascript -e 'tell application "iTerm2" to tell current session of current window to set name to "TAB_NAME"' 2>/dev/null
41
42
  ```
42
43
 
43
- **Important:** Escape any double quotes in the tab name before embedding in the AppleScript string.
44
+ ### Terminal.app (`TERM_PROGRAM=Apple_Terminal`)
45
+
46
+ Terminal.app requires **two steps** — set the custom title AND enable the custom title display (otherwise the shell's auto-title overrides it):
47
+
48
+ ```bash
49
+ osascript -e '
50
+ tell application "Terminal"
51
+ set t to selected tab of front window
52
+ set custom title of t to "TAB_NAME"
53
+ end tell' 2>/dev/null
54
+ ```
55
+
56
+ Then use the ANSI escape to set the window/tab title (this is what actually sticks):
57
+
58
+ ```bash
59
+ printf '\033]0;TAB_NAME\007'
60
+ ```
61
+
62
+ ### Other terminals / fallback
63
+
64
+ Use the standard ANSI escape sequence:
65
+
66
+ ```bash
67
+ printf '\033]0;TAB_NAME\007'
68
+ ```
44
69
 
45
70
  ## Step 3: Confirm
46
71
 
@@ -48,6 +73,4 @@ If the rename succeeded, say:
48
73
 
49
74
  "Tab renamed to **{tab_name}**."
50
75
 
51
- If both osascript commands fail, say:
52
-
53
- "Tab renaming requires macOS Terminal.app or iTerm2. On other terminals, you can set the tab title manually with: `printf '\033]0;my-title\007'`"
76
+ > **Tip:** If your shell resets the title on each prompt (common with oh-my-zsh), add `export DISABLE_AUTO_TITLE="true"` to your `~/.zshrc`, then restart your shell.
@@ -24,9 +24,11 @@ $ARGUMENTS
24
24
  ls docs/ideas/*.md 2>/dev/null || echo "No idea files found"
25
25
  ls docs/plans/*.md 2>/dev/null || echo "No plan files found"
26
26
  ```
27
- 2. List what's available and ask: "Would you like to:
28
- - Convert a source file (e.g., `/prd auth` or `/prd plans/my-feature`)
29
- - Describe a feature directly (e.g., `/prd 'Add user logout button'`)"
27
+ 2. If source files exist, use AskUserQuestion to let the user pick:
28
+ - **Question:** "What should I build the PRD from?"
29
+ - **Header:** "PRD source"
30
+ - **Options:** List discovered idea/plan files (up to 3-4 most relevant), plus a "Describe a feature" option that says "Type a description directly (e.g., 'Add user logout button')"
31
+ - If no source files found, skip AskUserQuestion and just say: "Describe the feature you'd like to build (e.g., `/prd 'Add user logout button'`)"
30
32
 
31
33
  **If `$ARGUMENTS` looks like a plan file** (`plans/` prefix, `docs/plans/` path, or full path to a plan file):
32
34
  - If it's a full path, use it directly
@@ -122,9 +124,13 @@ Say: "Before I generate stories, I want to make sure we've covered the key areas
122
124
  - Phases: What's the logical order?
123
125
  - Verification: What commands prove each phase worked?
124
126
 
125
- End with: "(Or say **'go'** to proceed with defaults for anything not answered)"
126
-
127
- **STOP and wait for user input** (can be brief or 'go').
127
+ After presenting the hardening questions, use AskUserQuestion:
128
+ - **Question:** "Answer the questions above, or proceed with sensible defaults?"
129
+ - **Header:** "Hardening"
130
+ - **Options:**
131
+ - **"Go with defaults"** — "Proceed with sensible defaults for unanswered questions"
132
+ - **"Let me answer"** — "I'll respond to the questions above"
133
+ - If the user selects "Let me answer" or "Other", **STOP and wait for their response** before continuing.
128
134
 
129
135
  ### Step 3: Check for Existing PRD
130
136
 
@@ -133,16 +139,11 @@ cat .ralph/prd.json 2>/dev/null
133
139
  ```
134
140
 
135
141
  If it exists, read it and say:
136
- "`.ralph/prd.json` exists with {N} stories ({M} completed, {P} pending).
137
-
138
- Options:
139
- - **'append'** - Add new stories to the existing PRD (recommended)
140
- - **'overwrite'** - Replace it entirely
141
- - **'cancel'** - Stop here"
142
+ "`.ralph/prd.json` exists with {N} stories ({M} completed, {P} pending). I'll append new stories to it."
142
143
 
143
- **STOP and wait for user choice.**
144
+ **Default behavior is append** — just proceed. Do NOT ask for confirmation unless the user explicitly says "overwrite" or "replace".
144
145
 
145
- If user chooses **'append'**:
146
+ When appending:
146
147
  - Find highest existing story number (ignore prefix - could be US-005 or TASK-005)
147
148
  - **Always use TASK- prefix** for new stories (e.g., if highest is US-005 or TASK-005, new stories start at TASK-006)
148
149
  - New stories will be added after existing ones
@@ -332,14 +333,18 @@ Open the PRD for review:
332
333
  open -a TextEdit .ralph/prd.json
333
334
  ```
334
335
 
335
- Say: "I've {created|updated} the PRD with {N} stories and opened it in TextEdit.
336
+ Say: "I've {created|updated} the PRD with {N} stories and opened it in TextEdit."
336
337
 
337
- Review the PRD and let me know:
338
- - **'approved'** - Ready to run in your other terminal
339
- - **'edit [changes]'** - Tell me what to change
340
- - Or edit the JSON directly and say **'done'**"
338
+ Then use AskUserQuestion with **multiSelect: true**:
339
+ - **Question:** "How does the PRD look?"
340
+ - **Header:** "PRD review"
341
+ - **multiSelect:** true
342
+ - **Options:**
343
+ - **"Approved"** — "PRD is good — ready to run with Ralph"
344
+ - **"Edit"** — "I'll tell you what to change"
345
+ - **"I edited the JSON"** — "I made changes directly in the file, re-validate it"
341
346
 
342
- **STOP and wait for user response.**
347
+ If the user selects "Edit" (with or without other selections), **STOP and wait for their changes**. If "I edited the JSON" is selected, re-read and re-validate the PRD. If only "Approved" is selected, proceed to Step 9.
343
348
 
344
349
  ### Step 9: Final Instructions
345
350
 
package/bin/ralph.sh CHANGED
@@ -152,6 +152,12 @@ main() {
152
152
  echo ""
153
153
  exit 1
154
154
  fi
155
+ # Auto-refresh skills/hooks/signs when the package version changes
156
+ local version_file="$RALPH_DIR/.last_version"
157
+ if [[ ! -f "$version_file" ]] || [[ "$(cat "$version_file" 2>/dev/null)" != "$RALPH_VERSION" ]]; then
158
+ setup_refresh
159
+ echo "$RALPH_VERSION" > "$version_file"
160
+ fi
155
161
  # Clear any previous stop signal
156
162
  rm -f "$RALPH_DIR/.stop"
157
163
  run_loop "$@"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-loop",
3
- "version": "3.26.0",
3
+ "version": "3.27.1",
4
4
  "description": "Autonomous AI coding loop - PRD-driven development with Claude Code",
5
5
  "author": "Allie Jones <allie@allthrive.ai>",
6
6
  "license": "MIT",
package/ralph/loop.sh CHANGED
@@ -92,7 +92,8 @@ check_for_updates() {
92
92
  if $update_cmd 2>&1 | tail -3; then
93
93
  print_success " Updated to v$latest — restarting..."
94
94
  echo ""
95
- # Re-exec ralph.sh with "run" + original args
95
+ # Re-exec ralph.sh version-change detection in the run path
96
+ # will auto-refresh skills, hooks, and signs from the new package
96
97
  local ralph_bin
97
98
  ralph_bin="$(cd "$(dirname "${BASH_SOURCE[0]}")/../bin" && pwd)/ralph.sh"
98
99
  exec "$ralph_bin" run "$@"
@@ -681,8 +682,8 @@ _docker_safety_warning() {
681
682
  echo " ║ and executes migrations without asking. ║"
682
683
  echo " ║ ║"
683
684
  echo " ║ Without Docker, Ralph operates directly on your local ║"
684
- echo " ║ machine. A bad migration can corrupt real databases. ║"
685
- echo " ║ A runaway command can affect services outside this repo. ║"
685
+ echo " ║ machine. A runaway command can affect services outside ║"
686
+ echo " ║ this repo. ║"
686
687
  echo " ║ ║"
687
688
  echo " ║ With Docker, your project's services are isolated: ║"
688
689
  echo " ║ - Databases and caches run in containers, not locally ║"
@@ -722,8 +723,9 @@ _docker_safety_warning() {
722
723
  }
723
724
 
724
725
  run_loop() {
725
- # Save original args for update restart
726
- local _original_args=("$@")
726
+ # Save original args for update restart (explicit empty init for Bash 3.2 compatibility)
727
+ local _original_args=()
728
+ [[ $# -gt 0 ]] && _original_args=("$@")
727
729
 
728
730
  # PID of the currently running Claude pipeline (used by trap to kill it)
729
731
  _CLAUDE_PIPELINE_PID=""
@@ -794,7 +796,7 @@ run_loop() {
794
796
  check_dependencies
795
797
 
796
798
  # Check for newer version on npm (once per day, non-blocking if offline)
797
- check_for_updates "${_original_args[@]}"
799
+ check_for_updates "${_original_args[@]+"${_original_args[@]}"}"
798
800
 
799
801
  # Warn if no Docker compose file (safety net for autonomous execution)
800
802
  _docker_safety_warning
package/ralph/setup.sh CHANGED
@@ -118,6 +118,50 @@ _migrate_credentials_to_env() {
118
118
  echo ""
119
119
  }
120
120
 
121
+ # Lightweight refresh after auto-update — syncs files from the new package version
122
+ # without re-running interactive or first-time-only setup steps.
123
+ setup_refresh() {
124
+ local pkg_root
125
+ pkg_root="$(cd "$RALPH_LIB/.." && pwd)"
126
+
127
+ echo " Syncing updated files..."
128
+
129
+ # Re-copy slash commands/skills
130
+ setup_slash_commands "$pkg_root"
131
+
132
+ # Re-copy hooks (scripts + settings.json wiring)
133
+ setup_claude_hooks "$pkg_root"
134
+
135
+ # Merge any new default signs
136
+ if [[ -f ".ralph/signs.json" ]] && [[ -f "$pkg_root/templates/signs.json" ]] && command -v jq &>/dev/null; then
137
+ local existing_ids new_signs_added=0
138
+ existing_ids=$(jq -r '.signs[].id // empty' ".ralph/signs.json" 2>/dev/null | tr '\n' '|')
139
+
140
+ while IFS= read -r sign; do
141
+ local sign_id
142
+ sign_id=$(echo "$sign" | jq -r '.id // empty')
143
+ if [[ -n "$sign_id" && ! "$existing_ids" =~ "$sign_id" ]]; then
144
+ local tmp_file
145
+ tmp_file=$(mktemp)
146
+ jq --argjson new_sign "$sign" '.signs += [$new_sign]' ".ralph/signs.json" > "$tmp_file"
147
+ mv "$tmp_file" ".ralph/signs.json"
148
+ ((new_signs_added++)) || true
149
+ fi
150
+ done < <(jq -c '.signs[]' "$pkg_root/templates/signs.json" 2>/dev/null)
151
+
152
+ if [[ $new_signs_added -gt 0 ]]; then
153
+ echo " Merged $new_signs_added new sign(s)"
154
+ fi
155
+ fi
156
+
157
+ # Append any new PROMPT.md sections
158
+ if [[ -f "PROMPT.md" ]] && [[ -f "$pkg_root/templates/PROMPT.md" ]]; then
159
+ append_prompt_sections "$pkg_root/templates/PROMPT.md" "PROMPT.md"
160
+ fi
161
+
162
+ print_success " Setup refreshed"
163
+ }
164
+
121
165
  ralph_setup() {
122
166
  echo ""
123
167
  echo " _ _ _ _ "
@@ -252,7 +296,7 @@ setup_ralph_dir() {
252
296
  tmp_file=$(mktemp)
253
297
  jq --argjson new_sign "$sign" '.signs += [$new_sign]' ".ralph/signs.json" > "$tmp_file"
254
298
  mv "$tmp_file" ".ralph/signs.json"
255
- ((new_signs_added++))
299
+ ((new_signs_added++)) || true
256
300
  fi
257
301
  done < <(jq -c '.signs[]' "$pkg_root/templates/signs.json" 2>/dev/null)
258
302
 
@@ -312,6 +356,7 @@ setup_gitignore() {
312
356
  ".ralph/tool-log.txt"
313
357
  ".ralph/suggested-signs.txt"
314
358
  ".ralph/.preflight_cache"
359
+ ".ralph/.last_version"
315
360
  ".ralph/.lock"
316
361
  ".backups/"
317
362
  ".claude/settings.json"
package/ralph/utils.sh CHANGED
@@ -58,6 +58,7 @@ NC='\033[0m' # No Color
58
58
  # window running the script. In practice this works because the user just
59
59
  # ran the command, but rapid window switching can cause a mismatch.
60
60
  _ORIGINAL_TERMINAL_BG=""
61
+ _ORIGINAL_TERMINAL_FG=""
61
62
 
62
63
  set_terminal_bg() {
63
64
  local hex="${1:-#1a2e2e}" # Default: subtle dark teal
@@ -65,8 +66,9 @@ set_terminal_bg() {
65
66
  # Only works in Terminal.app
66
67
  [[ "$TERM_PROGRAM" != "Apple_Terminal" ]] && return 0
67
68
 
68
- # Save current background color for restore
69
+ # Save current background and text colors for restore
69
70
  _ORIGINAL_TERMINAL_BG=$(osascript -e 'tell application "Terminal" to get background color of front window' 2>/dev/null) || return 0
71
+ _ORIGINAL_TERMINAL_FG=$(osascript -e 'tell application "Terminal" to get normal text color of front window' 2>/dev/null) || true
70
72
 
71
73
  # Parse hex to 16-bit RGB values (Terminal.app uses 0-65535 range)
72
74
  local r=$((16#${hex:1:2} * 257))
@@ -74,6 +76,18 @@ set_terminal_bg() {
74
76
  local b=$((16#${hex:5:2} * 257))
75
77
 
76
78
  osascript -e "tell application \"Terminal\" to set background color of front window to {$r, $g, $b}" 2>/dev/null || true
79
+
80
+ # If the background is dark, ensure text is light enough to read
81
+ # Calculate perceived brightness: (R*299 + G*587 + B*114) / 1000 (8-bit scale)
82
+ local r8=$((16#${hex:1:2}))
83
+ local g8=$((16#${hex:3:2}))
84
+ local b8=$((16#${hex:5:2}))
85
+ local brightness=$(( (r8 * 299 + g8 * 587 + b8 * 114) / 1000 ))
86
+
87
+ if [[ $brightness -lt 128 ]]; then
88
+ # Dark background — set light text (soft white: #e0e0e0)
89
+ osascript -e 'tell application "Terminal" to set normal text color of front window to {57568, 57568, 57568}' 2>/dev/null || true
90
+ fi
77
91
  }
78
92
 
79
93
  restore_terminal_bg() {
@@ -82,17 +96,22 @@ restore_terminal_bg() {
82
96
 
83
97
  osascript -e "tell application \"Terminal\" to set background color of front window to {$_ORIGINAL_TERMINAL_BG}" 2>/dev/null || true
84
98
  _ORIGINAL_TERMINAL_BG=""
99
+
100
+ if [[ -n "$_ORIGINAL_TERMINAL_FG" ]]; then
101
+ osascript -e "tell application \"Terminal\" to set normal text color of front window to {$_ORIGINAL_TERMINAL_FG}" 2>/dev/null || true
102
+ _ORIGINAL_TERMINAL_FG=""
103
+ fi
85
104
  }
86
105
 
87
106
  # Set terminal tab title (works in Terminal.app, iTerm2, and most xterm-compatible terminals)
88
107
  set_tab_title() {
89
108
  local title="$1"
90
- printf '\033]0;%s\007' "$title"
109
+ printf '\033]0;%s\007' "$title" >&2
91
110
  }
92
111
 
93
112
  # Restore tab title to default (empty = terminal decides)
94
113
  restore_tab_title() {
95
- printf '\033]0;\007'
114
+ printf '\033]0;\007' >&2
96
115
  }
97
116
 
98
117
  # Get existing frontend directories in this project