agileflow 2.99.0 → 2.99.2

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.
Files changed (127) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +3 -3
  3. package/lib/dashboard-protocol.js +38 -0
  4. package/lib/dashboard-server.js +197 -7
  5. package/lib/feedback.js +36 -9
  6. package/lib/git-operations.js +4 -1
  7. package/lib/merge-operations.js +25 -0
  8. package/lib/progress.js +7 -6
  9. package/lib/session-operations.js +611 -0
  10. package/lib/session-switching.js +191 -0
  11. package/lib/template-loader.js +4 -2
  12. package/lib/worktree-operations.js +5 -25
  13. package/package.json +1 -1
  14. package/scripts/agileflow-configure.js +13 -0
  15. package/scripts/agileflow-welcome.js +11 -6
  16. package/scripts/batch-pmap-loop.js +11 -4
  17. package/scripts/claude-tmux.sh +186 -103
  18. package/scripts/damage-control-bash.js +33 -3
  19. package/scripts/damage-control-edit.js +33 -3
  20. package/scripts/damage-control-write.js +33 -3
  21. package/scripts/lib/configure-features.js +10 -7
  22. package/scripts/lib/configure-repair.js +12 -2
  23. package/scripts/lib/process-cleanup.js +197 -15
  24. package/scripts/obtain-context.js +5 -0
  25. package/scripts/session-manager.js +156 -932
  26. package/scripts/spawn-parallel.js +15 -11
  27. package/src/core/agents/configuration/archival.md +2 -1
  28. package/src/core/agents/configuration/attribution.md +2 -1
  29. package/src/core/agents/configuration/ci.md +2 -1
  30. package/src/core/agents/configuration/damage-control.md +2 -1
  31. package/src/core/agents/configuration/git-config.md +2 -1
  32. package/src/core/agents/configuration/hooks.md +2 -1
  33. package/src/core/agents/configuration/precompact.md +2 -1
  34. package/src/core/agents/configuration/status-line.md +2 -1
  35. package/src/core/agents/configuration/verify.md +2 -1
  36. package/src/core/commands/adr/list.md +1 -1
  37. package/src/core/commands/adr/update.md +1 -1
  38. package/src/core/commands/adr/view.md +1 -1
  39. package/src/core/commands/adr.md +1 -1
  40. package/src/core/commands/agent.md +1 -1
  41. package/src/core/commands/api.md +1 -1
  42. package/src/core/commands/assign.md +1 -1
  43. package/src/core/commands/audit.md +1 -1
  44. package/src/core/commands/auto.md +1 -1
  45. package/src/core/commands/automate.md +1 -1
  46. package/src/core/commands/babysit.md +1 -1
  47. package/src/core/commands/baseline.md +1 -1
  48. package/src/core/commands/batch.md +1 -1
  49. package/src/core/commands/blockers.md +1 -1
  50. package/src/core/commands/board.md +1 -1
  51. package/src/core/commands/changelog.md +1 -1
  52. package/src/core/commands/choose.md +1 -1
  53. package/src/core/commands/ci.md +1 -1
  54. package/src/core/commands/compress.md +1 -1
  55. package/src/core/commands/configure.md +56 -1
  56. package/src/core/commands/context/export.md +1 -1
  57. package/src/core/commands/context/full.md +1 -1
  58. package/src/core/commands/context/note.md +1 -1
  59. package/src/core/commands/council.md +1 -1
  60. package/src/core/commands/debt.md +1 -1
  61. package/src/core/commands/deploy.md +1 -1
  62. package/src/core/commands/deps.md +1 -1
  63. package/src/core/commands/diagnose.md +1 -1
  64. package/src/core/commands/docs.md +1 -1
  65. package/src/core/commands/epic/list.md +1 -1
  66. package/src/core/commands/epic/view.md +1 -1
  67. package/src/core/commands/epic.md +1 -1
  68. package/src/core/commands/feedback.md +1 -1
  69. package/src/core/commands/handoff.md +1 -1
  70. package/src/core/commands/help.md +4 -190
  71. package/src/core/commands/ideate/history.md +1 -1
  72. package/src/core/commands/ideate/new.md +1 -1
  73. package/src/core/commands/impact.md +1 -1
  74. package/src/core/commands/install.md +1 -1
  75. package/src/core/commands/logic/audit.md +1 -1
  76. package/src/core/commands/maintain.md +1 -1
  77. package/src/core/commands/metrics.md +1 -1
  78. package/src/core/commands/multi-expert.md +1 -1
  79. package/src/core/commands/packages.md +1 -1
  80. package/src/core/commands/pr.md +1 -1
  81. package/src/core/commands/readme-sync.md +1 -1
  82. package/src/core/commands/research/analyze.md +1 -1
  83. package/src/core/commands/research/ask.md +1 -1
  84. package/src/core/commands/research/import.md +1 -1
  85. package/src/core/commands/research/list.md +1 -1
  86. package/src/core/commands/research/synthesize.md +1 -1
  87. package/src/core/commands/research/view.md +1 -1
  88. package/src/core/commands/retro.md +1 -1
  89. package/src/core/commands/review.md +1 -1
  90. package/src/core/commands/rlm.md +1 -1
  91. package/src/core/commands/roadmap/analyze.md +1 -1
  92. package/src/core/commands/rpi.md +1 -1
  93. package/src/core/commands/serve.md +127 -0
  94. package/src/core/commands/session/cleanup.md +1 -1
  95. package/src/core/commands/session/end.md +84 -23
  96. package/src/core/commands/session/history.md +1 -1
  97. package/src/core/commands/session/init.md +1 -1
  98. package/src/core/commands/session/new.md +198 -84
  99. package/src/core/commands/session/resume.md +1 -1
  100. package/src/core/commands/session/spawn.md +1 -1
  101. package/src/core/commands/session/status.md +1 -1
  102. package/src/core/commands/skill/create.md +1 -1
  103. package/src/core/commands/skill/delete.md +1 -1
  104. package/src/core/commands/skill/edit.md +1 -1
  105. package/src/core/commands/skill/list.md +1 -1
  106. package/src/core/commands/skill/test.md +1 -1
  107. package/src/core/commands/skill/upgrade.md +1 -1
  108. package/src/core/commands/sprint.md +1 -1
  109. package/src/core/commands/status.md +1 -1
  110. package/src/core/commands/story/list.md +1 -1
  111. package/src/core/commands/story/view.md +1 -1
  112. package/src/core/commands/story-validate.md +1 -1
  113. package/src/core/commands/story.md +1 -1
  114. package/src/core/commands/team/list.md +1 -1
  115. package/src/core/commands/team/start.md +1 -1
  116. package/src/core/commands/team/status.md +1 -1
  117. package/src/core/commands/team/stop.md +1 -1
  118. package/src/core/commands/template.md +1 -1
  119. package/src/core/commands/tests.md +1 -1
  120. package/src/core/commands/update.md +1 -1
  121. package/src/core/commands/validate-expertise.md +1 -1
  122. package/src/core/commands/velocity.md +1 -1
  123. package/src/core/commands/verify.md +1 -1
  124. package/src/core/commands/whats-new.md +1 -1
  125. package/src/core/commands/workflow.md +1 -1
  126. package/tools/cli/installers/ide/codex.js +12 -4
  127. package/tools/cli/lib/content-injector.js +23 -4
@@ -2,19 +2,24 @@
2
2
  # claude-tmux.sh - Wrapper script that auto-starts Claude Code in a tmux session
3
3
  #
4
4
  # Usage:
5
- # ./claude-tmux.sh # Always creates fresh tmux + resumes Claude conversation
6
- # ./claude-tmux.sh --attach # Reattach to existing session (if you know it's alive)
5
+ # ./claude-tmux.sh # Create new tmux session (supports multiple from same dir)
6
+ # ./claude-tmux.sh --attach # Reattach to most recent session
7
7
  # ./claude-tmux.sh --no-tmux # Start without tmux (regular claude)
8
8
  # ./claude-tmux.sh -n # Same as --no-tmux
9
- # ./claude-tmux.sh --kill # Kill existing session completely
9
+ # ./claude-tmux.sh --kill # Kill ALL sessions for this directory
10
+ # ./claude-tmux.sh --refresh # Refresh tmux config on all existing sessions
10
11
  # ./claude-tmux.sh --help # Show help with keybinds
11
12
  #
12
13
  # When already in tmux: Just runs claude normally
13
14
  # When not in tmux: Creates a tmux session and runs claude inside it
14
15
  #
15
- # The default behavior always kills any existing session and creates a fresh one.
16
- # This prevents frozen sessions from blocking you. Your Claude conversation is
17
- # preserved via --resume regardless of the tmux session state.
16
+ # Multiple terminals can run `af` from the same directory simultaneously.
17
+ # Sessions are named: claude-<dir>, claude-<dir>-2, claude-<dir>-3, etc.
18
+ # Your Claude conversation is preserved via --resume regardless of tmux state.
19
+ #
20
+ # SESSION CREATION (while inside tmux):
21
+ # - Alt+N New worktree session (isolated branch + directory)
22
+ # - Alt+S Same-directory session (quick, no worktree)
18
23
  #
19
24
  # FREEZE RECOVERY (while inside tmux):
20
25
  # - Alt+k Send Ctrl+C twice (soft interrupt)
@@ -29,6 +34,7 @@ NO_TMUX=false
29
34
  KILL_SESSION=false
30
35
  SHOW_HELP=false
31
36
  ATTACH_ONLY=false
37
+ REFRESH_CONFIG=false
32
38
  USE_RESUME=false
33
39
  RESUME_SESSION_ID=""
34
40
 
@@ -54,6 +60,10 @@ for arg in "$@"; do
54
60
  KILL_SESSION=true
55
61
  shift
56
62
  ;;
63
+ --refresh)
64
+ REFRESH_CONFIG=true
65
+ shift
66
+ ;;
57
67
  --help|-h)
58
68
  SHOW_HELP=true
59
69
  shift
@@ -71,13 +81,15 @@ USAGE:
71
81
  agileflow [options] [claude-args...]
72
82
 
73
83
  OPTIONS:
74
- --attach, -a Reattach to existing session (skip fresh start)
84
+ --attach, -a Reattach to most recent session for this directory
75
85
  --no-tmux, -n Run claude without tmux
76
- --kill Kill existing session completely
86
+ --kill Kill ALL sessions for this directory
87
+ --refresh Refresh tmux config on all existing sessions
77
88
  --help, -h Show this help
78
89
 
79
- By default, af always creates a fresh tmux session and resumes your
80
- Claude conversation. This prevents frozen sessions from blocking you.
90
+ By default, af creates a new tmux session. Multiple terminals can run
91
+ af from the same directory simultaneously (sessions: claude-dir,
92
+ claude-dir-2, claude-dir-3, etc.).
81
93
 
82
94
  TMUX KEYBINDS:
83
95
  Alt+1-9 Switch to window N
@@ -93,6 +105,10 @@ TMUX KEYBINDS:
93
105
  Alt+w Close window
94
106
  Alt+q Detach from tmux
95
107
 
108
+ SESSION CREATION:
109
+ Alt+N New worktree session (isolated branch + directory)
110
+ Alt+S Same-directory session (quick, no worktree)
111
+
96
112
  FREEZE RECOVERY:
97
113
  Alt+k Send Ctrl+C twice (soft interrupt)
98
114
  Alt+K Force kill pane immediately
@@ -106,38 +122,59 @@ if [ "$NO_TMUX" = true ]; then
106
122
  exec claude "$@"
107
123
  fi
108
124
 
109
- # Generate session name based on current directory (needed for all modes)
125
+ # Generate directory name (used for session name patterns)
110
126
  DIR_NAME=$(basename "$(pwd)")
111
- SESSION_NAME="claude-${DIR_NAME}"
112
127
 
113
- # Handle --kill flag
128
+ # Handle --kill flag — kill ALL sessions for this directory
114
129
  if [ "$KILL_SESSION" = true ]; then
115
- if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
116
- echo "Killing session: $SESSION_NAME"
117
- tmux kill-session -t "$SESSION_NAME"
118
- echo "Session killed."
130
+ SESSION_BASE="claude-${DIR_NAME}"
131
+ KILLED=0
132
+ # Kill exact base session
133
+ if tmux has-session -t "$SESSION_BASE" 2>/dev/null; then
134
+ tmux kill-session -t "$SESSION_BASE" 2>/dev/null || true
135
+ KILLED=$((KILLED + 1))
136
+ fi
137
+ # Kill numbered sessions (claude-dir-2, claude-dir-3, etc.)
138
+ for sid in $(tmux list-sessions -F '#{session_name}' 2>/dev/null | grep "^${SESSION_BASE}-[0-9]*$"); do
139
+ tmux kill-session -t "$sid" 2>/dev/null || true
140
+ KILLED=$((KILLED + 1))
141
+ done
142
+ if [ "$KILLED" -gt 0 ]; then
143
+ echo "Killed $KILLED session(s) for $DIR_NAME."
119
144
  else
120
- echo "No session named '$SESSION_NAME' found."
145
+ echo "No sessions found for '$DIR_NAME'."
121
146
  fi
122
147
  exit 0
123
148
  fi
124
149
 
125
- # Handle --attach flag (reattach to existing session without killing it)
150
+ # Handle --attach flag (reattach to most recent session for this directory)
126
151
  if [ "$ATTACH_ONLY" = true ]; then
127
- if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
128
- echo "Attaching to existing session: $SESSION_NAME"
129
- exec tmux attach-session -t "$SESSION_NAME"
152
+ SESSION_BASE="claude-${DIR_NAME}"
153
+ # Find the highest-numbered existing session
154
+ LATEST=""
155
+ if tmux has-session -t "$SESSION_BASE" 2>/dev/null; then
156
+ LATEST="$SESSION_BASE"
157
+ fi
158
+ for sid in $(tmux list-sessions -F '#{session_name}' 2>/dev/null | grep "^${SESSION_BASE}-[0-9]*$" | sort -t- -k3 -n); do
159
+ LATEST="$sid"
160
+ done
161
+ if [ -n "$LATEST" ]; then
162
+ echo "Attaching to session: $LATEST"
163
+ exec tmux attach-session -t "$LATEST"
130
164
  else
131
165
  echo "No existing session. Creating new one..."
132
166
  # Fall through to create new session
133
167
  fi
134
168
  fi
135
169
 
136
- # Default behavior: always kill existing session and start fresh
137
- # This prevents frozen sessions from blocking you
138
- if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
139
- tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
140
- fi
170
+ # Find next available session name (supports multiple from same directory)
171
+ SESSION_BASE="claude-${DIR_NAME}"
172
+ SESSION_NAME="$SESSION_BASE"
173
+ SESSION_NUM=1
174
+ while tmux has-session -t "$SESSION_NAME" 2>/dev/null; do
175
+ SESSION_NUM=$((SESSION_NUM + 1))
176
+ SESSION_NAME="${SESSION_BASE}-${SESSION_NUM}"
177
+ done
141
178
 
142
179
  # Find the most recent conversation to resume
143
180
  PROJ_DIR=$(pwd | sed 's|/|-|g' | sed 's|^-||')
@@ -172,11 +209,31 @@ if [ -f "$METADATA_FILE" ]; then
172
209
  fi
173
210
 
174
211
  # Check for default Claude flags from metadata (e.g., --dangerously-skip-permissions)
212
+ # Priority: 1) sessions.defaultStartupMode (from /configure), 2) features.claudeFlags
175
213
  if [ -f "$METADATA_FILE" ]; then
176
214
  META_FLAGS=$(node -e "
177
215
  try {
178
216
  const meta = JSON.parse(require('fs').readFileSync('$METADATA_FILE', 'utf8'));
179
- console.log(meta.features?.claudeFlags?.enabled ? (meta.features.claudeFlags.defaultFlags || '') : '');
217
+ // Check sessions config first (from /configure)
218
+ const mode = meta.sessions?.defaultStartupMode;
219
+ if (mode && mode !== 'normal') {
220
+ const modeConfig = meta.sessions?.startupModes?.[mode];
221
+ if (modeConfig?.flags) {
222
+ // Normalize short flags to canonical form
223
+ let flags = modeConfig.flags;
224
+ if (flags === '--dangerous') flags = '--dangerously-skip-permissions';
225
+ console.log(flags);
226
+ process.exit(0);
227
+ }
228
+ }
229
+ // Fallback to claudeFlags feature
230
+ if (meta.features?.claudeFlags?.enabled) {
231
+ let flags = meta.features.claudeFlags.defaultFlags || '';
232
+ if (flags === '--dangerous') flags = '--dangerously-skip-permissions';
233
+ console.log(flags);
234
+ } else {
235
+ console.log('');
236
+ }
180
237
  } catch(e) { console.log(''); }
181
238
  " 2>/dev/null || echo "")
182
239
  if [ -n "$META_FLAGS" ]; then
@@ -201,112 +258,138 @@ if ! command -v tmux &> /dev/null; then
201
258
  exec claude "$@"
202
259
  fi
203
260
 
204
- # Create new tmux session with Claude
205
- echo "Starting Claude in tmux session: $SESSION_NAME"
261
+ # ══════════════════════════════════════════════════════════════════════════════
262
+ # TMUX CONFIGURATION FUNCTION applies theme, keybinds, and status bar
263
+ # Extracted so --refresh can re-apply to existing sessions
264
+ # ══════════════════════════════════════════════════════════════════════════════
265
+ configure_tmux_session() {
266
+ local target_session="$1"
206
267
 
207
- # Set base-index globally BEFORE creating session so first window gets index 1
208
- tmux set-option -g base-index 1
268
+ # Enable mouse support
269
+ tmux set-option -t "$target_session" mouse on
209
270
 
210
- # Create session in detached mode first (will use base-index 1)
211
- tmux new-session -d -s "$SESSION_NAME" -n "main"
271
+ # Fix colors - proper terminal support
272
+ tmux set-option -t "$target_session" default-terminal "xterm-256color"
273
+ tmux set-option -t "$target_session" -ga terminal-overrides ",xterm-256color:Tc"
212
274
 
213
- # ══════════════════════════════════════════════════════════════════════════════
214
- # TMUX CONFIGURATION - Modern status bar with keybinds
215
- # ══════════════════════════════════════════════════════════════════════════════
275
+ # ─── Status Bar Styling (2-line) ────────────────────────────────────────────
216
276
 
217
- # Enable mouse support
218
- tmux set-option -t "$SESSION_NAME" mouse on
277
+ # Status bar position and refresh
278
+ tmux set-option -t "$target_session" status-position bottom
279
+ # Reduce refresh rate to prevent CPU overhead and freezes (was 5s, now 30s)
280
+ tmux set-option -t "$target_session" status-interval 30
219
281
 
220
- # Fix colors - proper terminal support
221
- tmux set-option -t "$SESSION_NAME" default-terminal "xterm-256color"
222
- tmux set-option -t "$SESSION_NAME" -ga terminal-overrides ",xterm-256color:Tc"
282
+ # Enable 2-line status bar
283
+ tmux set-option -t "$target_session" status 2
223
284
 
224
- # ─── Status Bar Styling (2-line) ──────────────────────────────────────────────
285
+ # Base styling - Tokyo Night inspired dark theme
286
+ tmux set-option -t "$target_session" status-style "bg=#1a1b26,fg=#a9b1d6"
225
287
 
226
- # Status bar position and refresh
227
- tmux set-option -t "$SESSION_NAME" status-position bottom
228
- # Reduce refresh rate to prevent CPU overhead and freezes (was 5s, now 30s)
229
- tmux set-option -t "$SESSION_NAME" status-interval 30
288
+ # Capture git branch once (avoids spawning process every refresh)
289
+ local git_branch
290
+ git_branch=$(git branch --show-current 2>/dev/null || echo '-')
230
291
 
231
- # Enable 2-line status bar
232
- tmux set-option -t "$SESSION_NAME" status 2
292
+ # Line 0 (top): Session name (stripped of claude- prefix) + Keybinds + Git branch
293
+ tmux set-option -t "$target_session" status-format[0] "#[bg=#1a1b26] #[fg=#e8683a bold]#{s/claude-//:session_name} #[fg=#3b4261]· #[fg=#7aa2f7]󰘬 ${git_branch} #[align=right]#[fg=#7a7e8a]Alt+N new session Alt+k interrupt Alt+q detach "
233
294
 
234
- # Base styling - Tokyo Night inspired dark theme
235
- tmux set-option -t "$SESSION_NAME" status-style "bg=#1a1b26,fg=#a9b1d6"
295
+ # Line 1 (bottom): Window tabs with smart truncation and brand color
296
+ tmux set-option -t "$target_session" status-format[1] "#[bg=#1a1b26]#{W:#{?window_active,#[fg=#1a1b26 bg=#e8683a bold] #I #[fg=#e8683a bg=#2d2f3a]#[fg=#e0e0e0] #{=15:window_name} #[bg=#1a1b26 fg=#2d2f3a],#[fg=#8a8a8a] #I:#{=|8|...:window_name} }}"
236
297
 
237
- # Capture git branch once at startup (avoids spawning process every refresh)
238
- GIT_BRANCH=$(git branch --show-current 2>/dev/null || echo '-')
298
+ # Pane border styling - blue inactive, orange active
299
+ tmux set-option -t "$target_session" pane-border-style "fg=#3d59a1"
300
+ tmux set-option -t "$target_session" pane-active-border-style "fg=#e8683a"
239
301
 
240
- # Line 0 (top): Session name (stripped of claude- prefix) + Keybinds + Git branch
241
- tmux set-option -t "$SESSION_NAME" status-format[0] "#[bg=#1a1b26] #[fg=#e8683a bold]#{s/claude-//:session_name} #[fg=#3b4261]· #[fg=#7aa2f7]󰘬 ${GIT_BRANCH} #[align=right]#[fg=#7a7e8a]Alt+k interrupt Alt+x close Alt+q detach "
302
+ # Message styling - orange highlight
303
+ tmux set-option -t "$target_session" message-style "bg=#e8683a,fg=#1a1b26,bold"
242
304
 
243
- # Line 1 (bottom): Window tabs with smart truncation and brand color
244
- # - Active window: full name (max 15 chars), brand orange highlight
245
- # - Inactive windows: truncate to 8 chars with ... suffix, warm gray
246
- tmux set-option -t "$SESSION_NAME" status-format[1] "#[bg=#1a1b26]#{W:#{?window_active,#[fg=#1a1b26 bg=#e8683a bold] #I #[fg=#e8683a bg=#2d2f3a]#[fg=#e0e0e0] #{=15:window_name} #[bg=#1a1b26 fg=#2d2f3a],#[fg=#8a8a8a] #I:#{=|8|...:window_name} }}"
305
+ # ─── Keybindings ────────────────────────────────────────────────────────────
247
306
 
248
- # Pane border styling - blue inactive, orange active
249
- tmux set-option -t "$SESSION_NAME" pane-border-style "fg=#3d59a1"
250
- tmux set-option -t "$SESSION_NAME" pane-active-border-style "fg=#e8683a"
307
+ # Alt+number to switch windows (1-9)
308
+ for i in 1 2 3 4 5 6 7 8 9; do
309
+ tmux bind-key -n "M-$i" select-window -t ":$i"
310
+ done
251
311
 
252
- # Message styling - orange highlight
253
- tmux set-option -t "$SESSION_NAME" message-style "bg=#e8683a,fg=#1a1b26,bold"
312
+ # Alt+c to create new window
313
+ tmux bind-key -n M-c new-window -c "#{pane_current_path}"
254
314
 
255
- # ─── Keybindings ──────────────────────────────────────────────────────────────
315
+ # Alt+q to detach
316
+ tmux bind-key -n M-q detach-client
256
317
 
257
- # base-index 1 is set globally before session creation (so first window is 1)
318
+ # Alt+d to split horizontally (side by side)
319
+ tmux bind-key -n M-d split-window -h -c "#{pane_current_path}"
258
320
 
259
- # Alt+number to switch windows (1-9)
260
- for i in 1 2 3 4 5 6 7 8 9; do
261
- tmux bind-key -n "M-$i" select-window -t ":$i"
262
- done
321
+ # Alt+s to split vertically (top/bottom)
322
+ tmux bind-key -n M-s split-window -v -c "#{pane_current_path}"
323
+
324
+ # Alt+arrow to navigate panes
325
+ tmux bind-key -n M-Left select-pane -L
326
+ tmux bind-key -n M-Right select-pane -R
327
+ tmux bind-key -n M-Up select-pane -U
328
+ tmux bind-key -n M-Down select-pane -D
263
329
 
264
- # Alt+c to create new window
265
- tmux bind-key -n M-c new-window -c "#{pane_current_path}"
330
+ # Alt+x to close current pane (with confirmation)
331
+ tmux bind-key -n M-x confirm-before -p "Close pane? (y/n)" kill-pane
266
332
 
267
- # Alt+q to detach
268
- tmux bind-key -n M-q detach-client
333
+ # Alt+w to close current window (with confirmation)
334
+ tmux bind-key -n M-w confirm-before -p "Close window? (y/n)" kill-window
269
335
 
270
- # Alt+d to split horizontally (side by side)
271
- tmux bind-key -n M-d split-window -h -c "#{pane_current_path}"
336
+ # Alt+n/p for next/previous window
337
+ tmux bind-key -n M-n next-window
338
+ tmux bind-key -n M-p previous-window
272
339
 
273
- # Alt+s to split vertically (top/bottom)
274
- tmux bind-key -n M-s split-window -v -c "#{pane_current_path}"
340
+ # Alt+r to rename window
341
+ tmux bind-key -n M-r command-prompt -I "#W" "rename-window '%%'"
275
342
 
276
- # Alt+arrow to navigate panes
277
- tmux bind-key -n M-Left select-pane -L
278
- tmux bind-key -n M-Right select-pane -R
279
- tmux bind-key -n M-Up select-pane -U
280
- tmux bind-key -n M-Down select-pane -D
343
+ # Alt+z to zoom/unzoom pane (fullscreen toggle)
344
+ tmux bind-key -n M-z resize-pane -Z
281
345
 
282
- # Alt+x to close current pane (with confirmation)
283
- tmux bind-key -n M-x confirm-before -p "Close pane? (y/n)" kill-pane
346
+ # Alt+[ to enter copy mode (for scrolling)
347
+ tmux bind-key -n M-[ copy-mode
284
348
 
285
- # Alt+w to close current window (with confirmation)
286
- tmux bind-key -n M-w confirm-before -p "Close window? (y/n)" kill-window
349
+ # ─── Session Creation Keybindings ──────────────────────────────────────────
350
+ # Alt+N (shift+n) to create a new worktree session window
351
+ tmux bind-key -n M-N run-shell "node .agileflow/scripts/spawn-parallel.js add-window --name auto-\$(date +%s) 2>/dev/null && tmux display-message 'New worktree session created' || tmux display-message 'Session creation failed'"
287
352
 
288
- # Alt+n/p for next/previous window
289
- tmux bind-key -n M-n next-window
290
- tmux bind-key -n M-p previous-window
353
+ # Alt+S (shift+s) to create a same-directory Claude window (no worktree)
354
+ tmux bind-key -n M-S run-shell "tmux new-window -c '#{pane_current_path}' && tmux send-keys 'claude \$CLAUDE_SESSION_FLAGS' Enter && tmux display-message 'Same-dir session created'"
291
355
 
292
- # Alt+r to rename window
293
- tmux bind-key -n M-r command-prompt -I "#W" "rename-window '%%'"
356
+ # ─── Freeze Recovery Keybindings ───────────────────────────────────────────
357
+ # Alt+k to send Ctrl+C twice (soft interrupt for frozen processes)
358
+ tmux bind-key -n M-k run-shell "tmux send-keys C-c; sleep 0.5; tmux send-keys C-c"
294
359
 
295
- # Alt+z to zoom/unzoom pane (fullscreen toggle)
296
- tmux bind-key -n M-z resize-pane -Z
360
+ # Alt+K (shift+k) to force-kill pane immediately (nuclear option for hard freezes)
361
+ tmux bind-key -n M-K kill-pane
362
+
363
+ # Alt+R (shift+r) to respawn the pane (restart with a fresh shell)
364
+ tmux bind-key -n M-R respawn-pane -k
365
+ }
366
+
367
+ # Handle --refresh flag — re-apply config to all existing claude-* sessions
368
+ if [ "$REFRESH_CONFIG" = true ]; then
369
+ REFRESHED=0
370
+ for sid in $(tmux list-sessions -F '#{session_name}' 2>/dev/null | grep "^claude-"); do
371
+ configure_tmux_session "$sid"
372
+ REFRESHED=$((REFRESHED + 1))
373
+ done
374
+ if [ "$REFRESHED" -gt 0 ]; then
375
+ echo "Refreshed config on $REFRESHED session(s)."
376
+ else
377
+ echo "No claude-* sessions found to refresh."
378
+ fi
379
+ exit 0
380
+ fi
297
381
 
298
- # Alt+[ to enter copy mode (for scrolling)
299
- tmux bind-key -n M-[ copy-mode
382
+ # Create new tmux session with Claude
383
+ echo "Starting Claude in tmux session: $SESSION_NAME"
300
384
 
301
- # ─── Freeze Recovery Keybindings ─────────────────────────────────────────────
302
- # Alt+k to send Ctrl+C twice (soft interrupt for frozen processes)
303
- tmux bind-key -n M-k run-shell "tmux send-keys C-c; sleep 0.5; tmux send-keys C-c"
385
+ # Set base-index globally BEFORE creating session so first window gets index 1
386
+ tmux set-option -g base-index 1
304
387
 
305
- # Alt+K (shift+k) to force-kill pane immediately (nuclear option for hard freezes)
306
- tmux bind-key -n M-K kill-pane
388
+ # Create session in detached mode first (will use base-index 1)
389
+ tmux new-session -d -s "$SESSION_NAME" -n "main"
307
390
 
308
- # Alt+R (shift+r) to respawn the pane (restart with a fresh shell)
309
- tmux bind-key -n M-R respawn-pane -k
391
+ # Apply tmux configuration
392
+ configure_tmux_session "$SESSION_NAME"
310
393
 
311
394
  # Send the claude command to the first window
312
395
  CLAUDE_CMD="claude"
@@ -15,7 +15,37 @@
15
15
  * Usage: Configured as PreToolUse hook in .claude/settings.json
16
16
  */
17
17
 
18
- const { createBashHook } = require('./lib/damage-control-utils');
18
+ const fs = require('fs');
19
+ const path = require('path');
19
20
 
20
- // Run the hook using factory
21
- createBashHook()();
21
+ function loadDamageControlUtils() {
22
+ const candidates = [
23
+ path.join(__dirname, 'lib', 'damage-control-utils.js'),
24
+ path.join(process.cwd(), '.agileflow', 'scripts', 'lib', 'damage-control-utils.js'),
25
+ ];
26
+
27
+ for (const candidate of candidates) {
28
+ try {
29
+ if (fs.existsSync(candidate)) {
30
+ return require(candidate);
31
+ }
32
+ } catch (e) {
33
+ // Try next candidate
34
+ }
35
+ }
36
+
37
+ return null;
38
+ }
39
+
40
+ const utils = loadDamageControlUtils();
41
+ if (!utils || typeof utils.createBashHook !== 'function') {
42
+ // Fail-open: never block Bash tool because hook bootstrap failed.
43
+ process.exit(0);
44
+ }
45
+
46
+ try {
47
+ utils.createBashHook()();
48
+ } catch (e) {
49
+ // Fail-open on runtime errors to avoid breaking CLI workflows.
50
+ process.exit(0);
51
+ }
@@ -12,7 +12,37 @@
12
12
  * Usage: Configured as PreToolUse hook in .claude/settings.json
13
13
  */
14
14
 
15
- const { createPathHook } = require('./lib/damage-control-utils');
15
+ const fs = require('fs');
16
+ const path = require('path');
16
17
 
17
- // Run the hook using factory
18
- createPathHook('edit')();
18
+ function loadDamageControlUtils() {
19
+ const candidates = [
20
+ path.join(__dirname, 'lib', 'damage-control-utils.js'),
21
+ path.join(process.cwd(), '.agileflow', 'scripts', 'lib', 'damage-control-utils.js'),
22
+ ];
23
+
24
+ for (const candidate of candidates) {
25
+ try {
26
+ if (fs.existsSync(candidate)) {
27
+ return require(candidate);
28
+ }
29
+ } catch (e) {
30
+ // Try next candidate
31
+ }
32
+ }
33
+
34
+ return null;
35
+ }
36
+
37
+ const utils = loadDamageControlUtils();
38
+ if (!utils || typeof utils.createPathHook !== 'function') {
39
+ // Fail-open: never block Edit tool because hook bootstrap failed.
40
+ process.exit(0);
41
+ }
42
+
43
+ try {
44
+ utils.createPathHook('edit')();
45
+ } catch (e) {
46
+ // Fail-open on runtime errors to avoid breaking CLI workflows.
47
+ process.exit(0);
48
+ }
@@ -12,7 +12,37 @@
12
12
  * Usage: Configured as PreToolUse hook in .claude/settings.json
13
13
  */
14
14
 
15
- const { createPathHook } = require('./lib/damage-control-utils');
15
+ const fs = require('fs');
16
+ const path = require('path');
16
17
 
17
- // Run the hook using factory
18
- createPathHook('write')();
18
+ function loadDamageControlUtils() {
19
+ const candidates = [
20
+ path.join(__dirname, 'lib', 'damage-control-utils.js'),
21
+ path.join(process.cwd(), '.agileflow', 'scripts', 'lib', 'damage-control-utils.js'),
22
+ ];
23
+
24
+ for (const candidate of candidates) {
25
+ try {
26
+ if (fs.existsSync(candidate)) {
27
+ return require(candidate);
28
+ }
29
+ } catch (e) {
30
+ // Try next candidate
31
+ }
32
+ }
33
+
34
+ return null;
35
+ }
36
+
37
+ const utils = loadDamageControlUtils();
38
+ if (!utils || typeof utils.createPathHook !== 'function') {
39
+ // Fail-open: never block Write tool because hook bootstrap failed.
40
+ process.exit(0);
41
+ }
42
+
43
+ try {
44
+ utils.createPathHook('write')();
45
+ } catch (e) {
46
+ // Fail-open on runtime errors to avoid breaking CLI workflows.
47
+ process.exit(0);
48
+ }
@@ -287,7 +287,7 @@ function enableFeature(feature, options = {}, version) {
287
287
  features: {
288
288
  processCleanup: {
289
289
  enabled: true,
290
- autoKill: true,
290
+ autoKill: false,
291
291
  version,
292
292
  at: new Date().toISOString(),
293
293
  },
@@ -296,9 +296,10 @@ function enableFeature(feature, options = {}, version) {
296
296
  version
297
297
  );
298
298
  success('Process cleanup enabled');
299
- warn('⚠️ Duplicate Claude processes will be automatically terminated on session start');
299
+ info('Duplicate Claude processes will be detected and reported on session start');
300
+ info('Auto-kill is disabled by default for safety');
300
301
  info(' Only affects processes in the SAME working directory (worktrees are safe)');
301
- info(' Prevents freezing caused by multiple Claude instances competing for resources');
302
+ info(' Set AGILEFLOW_PROCESS_CLEANUP_AUTOKILL=1 to opt in to auto-kill at runtime');
302
303
  return true;
303
304
  }
304
305
 
@@ -1142,7 +1143,7 @@ function enableShellAliases() {
1142
1143
  const ALIAS_BLOCK_LINES = [
1143
1144
  '# AgileFlow tmux wrapper',
1144
1145
  '# AgileFlow tmux shortcuts (claude stays normal)',
1145
- '# Use \'af\' or \'agileflow\' for tmux, \'claude\' stays normal',
1146
+ "# Use 'af' or 'agileflow' for tmux, 'claude' stays normal",
1146
1147
  'alias af="bash .agileflow/scripts/af"',
1147
1148
  'alias agileflow="bash .agileflow/scripts/af"',
1148
1149
  ];
@@ -1209,7 +1210,7 @@ function disableShellAliases() {
1209
1210
  const ALIAS_BLOCK_LINES = [
1210
1211
  '# AgileFlow tmux wrapper',
1211
1212
  '# AgileFlow tmux shortcuts (claude stays normal)',
1212
- '# Use \'af\' or \'agileflow\' for tmux, \'claude\' stays normal',
1213
+ "# Use 'af' or 'agileflow' for tmux, 'claude' stays normal",
1213
1214
  'alias af="bash .agileflow/scripts/af"',
1214
1215
  'alias agileflow="bash .agileflow/scripts/af"',
1215
1216
  ];
@@ -1223,8 +1224,10 @@ function disableShellAliases() {
1223
1224
  const content = fs.readFileSync(rc.path, 'utf8');
1224
1225
 
1225
1226
  // Check for any AgileFlow alias (covers old and new markers)
1226
- if (!content.includes('alias af="bash .agileflow/scripts/af"') &&
1227
- !content.includes(SHELL_ALIAS_MARKER)) {
1227
+ if (
1228
+ !content.includes('alias af="bash .agileflow/scripts/af"') &&
1229
+ !content.includes(SHELL_ALIAS_MARKER)
1230
+ ) {
1228
1231
  continue;
1229
1232
  }
1230
1233
 
@@ -8,6 +8,7 @@ const fs = require('fs');
8
8
  const path = require('path');
9
9
  const crypto = require('crypto');
10
10
  const { execFileSync } = require('child_process');
11
+ const { feedback } = require('../../lib/feedback');
11
12
  const {
12
13
  c,
13
14
  log,
@@ -168,7 +169,9 @@ function showVersionInfo(version) {
168
169
  latestVersion = execFileSync('npm', ['view', 'agileflow', 'version'], {
169
170
  encoding: 'utf8',
170
171
  stdio: ['pipe', 'pipe', 'pipe'],
171
- }).toString().trim();
172
+ })
173
+ .toString()
174
+ .trim();
172
175
  log(`Latest: v${latestVersion}`);
173
176
 
174
177
  if (installedVersion !== 'unknown' && latestVersion && installedVersion !== latestVersion) {
@@ -262,7 +265,12 @@ function repairScripts(targetFeature = null) {
262
265
  // Ensure scripts directory exists
263
266
  ensureDir(scriptsDir);
264
267
 
265
- for (const [script, info] of scriptsToCheck) {
268
+ const bar =
269
+ scriptsToCheck.length > 5
270
+ ? feedback.progressBar('Checking scripts', scriptsToCheck.length)
271
+ : null;
272
+
273
+ for (const [script, scriptInfo] of scriptsToCheck) {
266
274
  const destPath = path.join(scriptsDir, script);
267
275
  const srcPath = path.join(sourceDir, script);
268
276
 
@@ -287,7 +295,9 @@ function repairScripts(targetFeature = null) {
287
295
  } else {
288
296
  skipped++;
289
297
  }
298
+ if (bar) bar.increment(script);
290
299
  }
300
+ if (bar) bar.complete('Script check complete');
291
301
 
292
302
  // Summary
293
303
  log('');