agileflow 2.98.0 → 2.99.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/CHANGELOG.md +10 -0
- package/lib/api-routes.js +28 -8
- package/lib/api-server.js +21 -4
- package/lib/dashboard-server.js +173 -17
- package/package.json +1 -1
- package/scripts/agileflow-statusline.sh +17 -21
- package/scripts/claude-tmux.sh +65 -67
- package/scripts/lib/configure-features.js +90 -28
- package/scripts/lib/damage-control-utils.js +38 -0
- package/src/core/agents/code-reviewer.md +1 -3
- package/src/core/agents/error-analyzer.md +1 -4
- package/src/core/agents/logic-analyzer-edge.md +1 -3
- package/src/core/agents/logic-analyzer-flow.md +1 -3
- package/src/core/agents/logic-analyzer-invariant.md +1 -3
- package/src/core/agents/logic-analyzer-race.md +1 -3
- package/src/core/agents/logic-analyzer-type.md +1 -3
- package/src/core/agents/logic-consensus.md +1 -5
- package/src/core/experts/documentation/expertise.yaml +33 -0
- package/src/core/templates/damage-control-patterns.yaml +2 -2
- package/tools/cli/installers/core/installer.js +0 -89
package/scripts/claude-tmux.sh
CHANGED
|
@@ -2,20 +2,21 @@
|
|
|
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 #
|
|
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)
|
|
6
7
|
# ./claude-tmux.sh --no-tmux # Start without tmux (regular claude)
|
|
7
8
|
# ./claude-tmux.sh -n # Same as --no-tmux
|
|
8
|
-
# ./claude-tmux.sh --fresh # Kill and restart with latest scripts
|
|
9
|
-
# ./claude-tmux.sh -f # Same as --fresh
|
|
10
|
-
# ./claude-tmux.sh --rescue # Kill frozen session and restart fresh
|
|
11
9
|
# ./claude-tmux.sh --kill # Kill existing session completely
|
|
12
10
|
# ./claude-tmux.sh --help # Show help with keybinds
|
|
13
11
|
#
|
|
14
12
|
# When already in tmux: Just runs claude normally
|
|
15
13
|
# When not in tmux: Creates a tmux session and runs claude inside it
|
|
16
14
|
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
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.
|
|
18
|
+
#
|
|
19
|
+
# FREEZE RECOVERY (while inside tmux):
|
|
19
20
|
# - Alt+k Send Ctrl+C twice (soft interrupt)
|
|
20
21
|
# - Alt+K Force kill the pane immediately
|
|
21
22
|
# - Alt+R Respawn pane with fresh shell
|
|
@@ -25,10 +26,9 @@ set -e
|
|
|
25
26
|
|
|
26
27
|
# Parse arguments
|
|
27
28
|
NO_TMUX=false
|
|
28
|
-
RESCUE=false
|
|
29
29
|
KILL_SESSION=false
|
|
30
30
|
SHOW_HELP=false
|
|
31
|
-
|
|
31
|
+
ATTACH_ONLY=false
|
|
32
32
|
USE_RESUME=false
|
|
33
33
|
RESUME_SESSION_ID=""
|
|
34
34
|
|
|
@@ -38,12 +38,16 @@ for arg in "$@"; do
|
|
|
38
38
|
NO_TMUX=true
|
|
39
39
|
shift
|
|
40
40
|
;;
|
|
41
|
+
--attach|-a)
|
|
42
|
+
ATTACH_ONLY=true
|
|
43
|
+
shift
|
|
44
|
+
;;
|
|
41
45
|
--fresh|-f)
|
|
42
|
-
|
|
46
|
+
# Kept for backwards compat, but this is now the default behavior
|
|
43
47
|
shift
|
|
44
48
|
;;
|
|
45
49
|
--rescue|-r)
|
|
46
|
-
|
|
50
|
+
# Kept for backwards compat, same as default now
|
|
47
51
|
shift
|
|
48
52
|
;;
|
|
49
53
|
--kill)
|
|
@@ -67,12 +71,14 @@ USAGE:
|
|
|
67
71
|
agileflow [options] [claude-args...]
|
|
68
72
|
|
|
69
73
|
OPTIONS:
|
|
70
|
-
--
|
|
74
|
+
--attach, -a Reattach to existing session (skip fresh start)
|
|
71
75
|
--no-tmux, -n Run claude without tmux
|
|
72
|
-
--rescue, -r Kill frozen session and restart fresh
|
|
73
76
|
--kill Kill existing session completely
|
|
74
77
|
--help, -h Show this help
|
|
75
78
|
|
|
79
|
+
By default, af always creates a fresh tmux session and resumes your
|
|
80
|
+
Claude conversation. This prevents frozen sessions from blocking you.
|
|
81
|
+
|
|
76
82
|
TMUX KEYBINDS:
|
|
77
83
|
Alt+1-9 Switch to window N
|
|
78
84
|
Alt+c Create new window
|
|
@@ -91,11 +97,6 @@ FREEZE RECOVERY:
|
|
|
91
97
|
Alt+k Send Ctrl+C twice (soft interrupt)
|
|
92
98
|
Alt+K Force kill pane immediately
|
|
93
99
|
Alt+R Respawn pane with fresh shell
|
|
94
|
-
|
|
95
|
-
If Claude is completely frozen and keybinds don't work:
|
|
96
|
-
1. Open a new terminal
|
|
97
|
-
2. Run: af --rescue (kills and restarts)
|
|
98
|
-
3. Or: af --kill (just kills, doesn't restart)
|
|
99
100
|
EOF
|
|
100
101
|
exit 0
|
|
101
102
|
fi
|
|
@@ -105,7 +106,7 @@ if [ "$NO_TMUX" = true ]; then
|
|
|
105
106
|
exec claude "$@"
|
|
106
107
|
fi
|
|
107
108
|
|
|
108
|
-
# Generate session name based on current directory (needed for
|
|
109
|
+
# Generate session name based on current directory (needed for all modes)
|
|
109
110
|
DIR_NAME=$(basename "$(pwd)")
|
|
110
111
|
SESSION_NAME="claude-${DIR_NAME}"
|
|
111
112
|
|
|
@@ -121,47 +122,33 @@ if [ "$KILL_SESSION" = true ]; then
|
|
|
121
122
|
exit 0
|
|
122
123
|
fi
|
|
123
124
|
|
|
124
|
-
# Handle --
|
|
125
|
-
if [ "$
|
|
125
|
+
# Handle --attach flag (reattach to existing session without killing it)
|
|
126
|
+
if [ "$ATTACH_ONLY" = true ]; then
|
|
126
127
|
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
|
127
|
-
echo "
|
|
128
|
-
tmux
|
|
129
|
-
echo "Session killed. Restarting..."
|
|
130
|
-
sleep 0.5
|
|
128
|
+
echo "Attaching to existing session: $SESSION_NAME"
|
|
129
|
+
exec tmux attach-session -t "$SESSION_NAME"
|
|
131
130
|
else
|
|
132
|
-
echo "No existing session
|
|
131
|
+
echo "No existing session. Creating new one..."
|
|
132
|
+
# Fall through to create new session
|
|
133
133
|
fi
|
|
134
|
-
# Continue to create new session below
|
|
135
134
|
fi
|
|
136
135
|
|
|
137
|
-
#
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
echo "Starting fresh with latest scripts..."
|
|
143
|
-
sleep 0.3
|
|
144
|
-
else
|
|
145
|
-
echo "No existing session. Starting fresh..."
|
|
146
|
-
fi
|
|
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
|
|
147
141
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
echo "Found recent conversation: $RESUME_SESSION_ID"
|
|
159
|
-
USE_RESUME=true
|
|
160
|
-
else
|
|
161
|
-
echo "No previous conversation found. Starting fresh."
|
|
162
|
-
fi
|
|
163
|
-
else
|
|
164
|
-
echo "No session history found. Starting fresh."
|
|
142
|
+
# Find the most recent conversation to resume
|
|
143
|
+
PROJ_DIR=$(pwd | sed 's|/|-|g' | sed 's|^-||')
|
|
144
|
+
SESSIONS_DIR="$HOME/.claude/projects/-$PROJ_DIR"
|
|
145
|
+
|
|
146
|
+
if [ -d "$SESSIONS_DIR" ]; then
|
|
147
|
+
# Get most recent non-agent session (main conversations only)
|
|
148
|
+
RECENT_SESSION=$(ls -t "$SESSIONS_DIR"/*.jsonl 2>/dev/null | grep -v "agent-" | head -1)
|
|
149
|
+
if [ -n "$RECENT_SESSION" ] && [ -s "$RECENT_SESSION" ]; then
|
|
150
|
+
RESUME_SESSION_ID=$(basename "$RECENT_SESSION" .jsonl)
|
|
151
|
+
USE_RESUME=true
|
|
165
152
|
fi
|
|
166
153
|
fi
|
|
167
154
|
|
|
@@ -184,6 +171,20 @@ if [ -f "$METADATA_FILE" ]; then
|
|
|
184
171
|
fi
|
|
185
172
|
fi
|
|
186
173
|
|
|
174
|
+
# Check for default Claude flags from metadata (e.g., --dangerously-skip-permissions)
|
|
175
|
+
if [ -f "$METADATA_FILE" ]; then
|
|
176
|
+
META_FLAGS=$(node -e "
|
|
177
|
+
try {
|
|
178
|
+
const meta = JSON.parse(require('fs').readFileSync('$METADATA_FILE', 'utf8'));
|
|
179
|
+
console.log(meta.features?.claudeFlags?.enabled ? (meta.features.claudeFlags.defaultFlags || '') : '');
|
|
180
|
+
} catch(e) { console.log(''); }
|
|
181
|
+
" 2>/dev/null || echo "")
|
|
182
|
+
if [ -n "$META_FLAGS" ]; then
|
|
183
|
+
CLAUDE_SESSION_FLAGS="${CLAUDE_SESSION_FLAGS:+$CLAUDE_SESSION_FLAGS }$META_FLAGS"
|
|
184
|
+
export CLAUDE_SESSION_FLAGS
|
|
185
|
+
fi
|
|
186
|
+
fi
|
|
187
|
+
|
|
187
188
|
# Check if we're already inside tmux
|
|
188
189
|
if [ -n "$TMUX" ]; then
|
|
189
190
|
# Already in tmux, just run claude
|
|
@@ -200,18 +201,13 @@ if ! command -v tmux &> /dev/null; then
|
|
|
200
201
|
exec claude "$@"
|
|
201
202
|
fi
|
|
202
203
|
|
|
203
|
-
# SESSION_NAME already generated above (needed for --rescue and --kill)
|
|
204
|
-
|
|
205
|
-
# Check if session already exists
|
|
206
|
-
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
|
207
|
-
echo "Attaching to existing session: $SESSION_NAME"
|
|
208
|
-
exec tmux attach-session -t "$SESSION_NAME"
|
|
209
|
-
fi
|
|
210
|
-
|
|
211
204
|
# Create new tmux session with Claude
|
|
212
205
|
echo "Starting Claude in tmux session: $SESSION_NAME"
|
|
213
206
|
|
|
214
|
-
#
|
|
207
|
+
# Set base-index globally BEFORE creating session so first window gets index 1
|
|
208
|
+
tmux set-option -g base-index 1
|
|
209
|
+
|
|
210
|
+
# Create session in detached mode first (will use base-index 1)
|
|
215
211
|
tmux new-session -d -s "$SESSION_NAME" -n "main"
|
|
216
212
|
|
|
217
213
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
@@ -229,7 +225,8 @@ tmux set-option -t "$SESSION_NAME" -ga terminal-overrides ",xterm-256color:Tc"
|
|
|
229
225
|
|
|
230
226
|
# Status bar position and refresh
|
|
231
227
|
tmux set-option -t "$SESSION_NAME" status-position bottom
|
|
232
|
-
|
|
228
|
+
# Reduce refresh rate to prevent CPU overhead and freezes (was 5s, now 30s)
|
|
229
|
+
tmux set-option -t "$SESSION_NAME" status-interval 30
|
|
233
230
|
|
|
234
231
|
# Enable 2-line status bar
|
|
235
232
|
tmux set-option -t "$SESSION_NAME" status 2
|
|
@@ -237,9 +234,11 @@ tmux set-option -t "$SESSION_NAME" status 2
|
|
|
237
234
|
# Base styling - Tokyo Night inspired dark theme
|
|
238
235
|
tmux set-option -t "$SESSION_NAME" status-style "bg=#1a1b26,fg=#a9b1d6"
|
|
239
236
|
|
|
237
|
+
# Capture git branch once at startup (avoids spawning process every refresh)
|
|
238
|
+
GIT_BRANCH=$(git branch --show-current 2>/dev/null || echo '-')
|
|
239
|
+
|
|
240
240
|
# Line 0 (top): Session name (stripped of claude- prefix) + Keybinds + Git branch
|
|
241
|
-
|
|
242
|
-
tmux set-option -t "$SESSION_NAME" status-format[0] "#[bg=#1a1b26] #[fg=#e8683a bold]#{s/claude-//:session_name} #[fg=#3b4261]· #[fg=#7aa2f7] #(git branch --show-current 2>/dev/null || echo '-') #[align=right]#[fg=#7a7e8a]Alt+k freeze Alt+x close Alt+q detach "
|
|
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 "
|
|
243
242
|
|
|
244
243
|
# Line 1 (bottom): Window tabs with smart truncation and brand color
|
|
245
244
|
# - Active window: full name (max 15 chars), brand orange highlight
|
|
@@ -255,8 +254,7 @@ tmux set-option -t "$SESSION_NAME" message-style "bg=#e8683a,fg=#1a1b26,bold"
|
|
|
255
254
|
|
|
256
255
|
# ─── Keybindings ──────────────────────────────────────────────────────────────
|
|
257
256
|
|
|
258
|
-
#
|
|
259
|
-
tmux set-option -t "$SESSION_NAME" base-index 1
|
|
257
|
+
# base-index 1 is set globally before session creation (so first window is 1)
|
|
260
258
|
|
|
261
259
|
# Alt+number to switch windows (1-9)
|
|
262
260
|
for i in 1 2 3 4 5 6 7 8 9; do
|
|
@@ -52,6 +52,10 @@ const FEATURES = {
|
|
|
52
52
|
metadataOnly: true,
|
|
53
53
|
description: 'Auto-kill duplicate Claude processes in same directory to prevent freezing',
|
|
54
54
|
},
|
|
55
|
+
claudeflags: {
|
|
56
|
+
metadataOnly: true,
|
|
57
|
+
description: 'Default flags for Claude CLI (e.g., --dangerously-skip-permissions)',
|
|
58
|
+
},
|
|
55
59
|
};
|
|
56
60
|
|
|
57
61
|
const PROFILES = {
|
|
@@ -298,6 +302,27 @@ function enableFeature(feature, options = {}, version) {
|
|
|
298
302
|
return true;
|
|
299
303
|
}
|
|
300
304
|
|
|
305
|
+
// Handle claude flags (e.g., --dangerously-skip-permissions)
|
|
306
|
+
if (feature === 'claudeflags') {
|
|
307
|
+
const defaultFlags = options.flags || '--dangerously-skip-permissions';
|
|
308
|
+
updateMetadata(
|
|
309
|
+
{
|
|
310
|
+
features: {
|
|
311
|
+
claudeFlags: {
|
|
312
|
+
enabled: true,
|
|
313
|
+
defaultFlags,
|
|
314
|
+
version,
|
|
315
|
+
at: new Date().toISOString(),
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
version
|
|
320
|
+
);
|
|
321
|
+
success(`Default Claude flags configured: ${defaultFlags}`);
|
|
322
|
+
info('These flags will be passed to Claude when launched via "af" or "agileflow"');
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
|
|
301
326
|
// Handle shell aliases
|
|
302
327
|
if (feature === 'shellaliases') {
|
|
303
328
|
const result = enableShellAliases();
|
|
@@ -684,6 +709,26 @@ function disableFeature(feature, version) {
|
|
|
684
709
|
return true;
|
|
685
710
|
}
|
|
686
711
|
|
|
712
|
+
// Disable claude flags
|
|
713
|
+
if (feature === 'claudeflags') {
|
|
714
|
+
updateMetadata(
|
|
715
|
+
{
|
|
716
|
+
features: {
|
|
717
|
+
claudeFlags: {
|
|
718
|
+
enabled: false,
|
|
719
|
+
defaultFlags: '',
|
|
720
|
+
version,
|
|
721
|
+
at: new Date().toISOString(),
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
version
|
|
726
|
+
);
|
|
727
|
+
success('Default Claude flags disabled');
|
|
728
|
+
info('Claude will launch with default permissions (prompts for each action)');
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
|
|
687
732
|
// Disable shell aliases
|
|
688
733
|
if (feature === 'shellaliases') {
|
|
689
734
|
const result = disableShellAliases();
|
|
@@ -1093,6 +1138,15 @@ function enableShellAliases() {
|
|
|
1093
1138
|
{ name: 'zsh', path: path.join(homeDir, '.zshrc') },
|
|
1094
1139
|
];
|
|
1095
1140
|
|
|
1141
|
+
// Lines that belong to AgileFlow alias blocks (old and new markers)
|
|
1142
|
+
const ALIAS_BLOCK_LINES = [
|
|
1143
|
+
'# AgileFlow tmux wrapper',
|
|
1144
|
+
'# AgileFlow tmux shortcuts (claude stays normal)',
|
|
1145
|
+
'# Use \'af\' or \'agileflow\' for tmux, \'claude\' stays normal',
|
|
1146
|
+
'alias af="bash .agileflow/scripts/af"',
|
|
1147
|
+
'alias agileflow="bash .agileflow/scripts/af"',
|
|
1148
|
+
];
|
|
1149
|
+
|
|
1096
1150
|
for (const rc of rcFiles) {
|
|
1097
1151
|
try {
|
|
1098
1152
|
// Check if RC file exists
|
|
@@ -1103,13 +1157,24 @@ function enableShellAliases() {
|
|
|
1103
1157
|
|
|
1104
1158
|
const content = fs.readFileSync(rc.path, 'utf8');
|
|
1105
1159
|
|
|
1106
|
-
// Check
|
|
1107
|
-
if (content.includes(
|
|
1108
|
-
|
|
1160
|
+
// Check for ANY existing af alias (covers old and new markers)
|
|
1161
|
+
if (content.includes('alias af="bash .agileflow/scripts/af"')) {
|
|
1162
|
+
// Clean up: remove ALL existing alias block lines, then re-add one clean copy
|
|
1163
|
+
const lines = content.split('\n');
|
|
1164
|
+
const cleaned = lines.filter(line => {
|
|
1165
|
+
const trimmed = line.trim();
|
|
1166
|
+
return !ALIAS_BLOCK_LINES.includes(trimmed);
|
|
1167
|
+
});
|
|
1168
|
+
// Remove trailing empty lines from cleanup
|
|
1169
|
+
while (cleaned.length > 0 && cleaned[cleaned.length - 1].trim() === '') {
|
|
1170
|
+
cleaned.pop();
|
|
1171
|
+
}
|
|
1172
|
+
fs.writeFileSync(rc.path, cleaned.join('\n') + SHELL_ALIAS_BLOCK);
|
|
1173
|
+
result.configured.push(rc.name);
|
|
1109
1174
|
continue;
|
|
1110
1175
|
}
|
|
1111
1176
|
|
|
1112
|
-
//
|
|
1177
|
+
// First time: just append
|
|
1113
1178
|
fs.appendFileSync(rc.path, SHELL_ALIAS_BLOCK);
|
|
1114
1179
|
result.configured.push(rc.name);
|
|
1115
1180
|
} catch (err) {
|
|
@@ -1140,6 +1205,15 @@ function disableShellAliases() {
|
|
|
1140
1205
|
{ name: 'zsh', path: path.join(homeDir, '.zshrc') },
|
|
1141
1206
|
];
|
|
1142
1207
|
|
|
1208
|
+
// Lines that belong to AgileFlow alias blocks (old and new markers)
|
|
1209
|
+
const ALIAS_BLOCK_LINES = [
|
|
1210
|
+
'# AgileFlow tmux wrapper',
|
|
1211
|
+
'# AgileFlow tmux shortcuts (claude stays normal)',
|
|
1212
|
+
'# Use \'af\' or \'agileflow\' for tmux, \'claude\' stays normal',
|
|
1213
|
+
'alias af="bash .agileflow/scripts/af"',
|
|
1214
|
+
'alias agileflow="bash .agileflow/scripts/af"',
|
|
1215
|
+
];
|
|
1216
|
+
|
|
1143
1217
|
for (const rc of rcFiles) {
|
|
1144
1218
|
try {
|
|
1145
1219
|
if (!fs.existsSync(rc.path)) {
|
|
@@ -1148,37 +1222,25 @@ function disableShellAliases() {
|
|
|
1148
1222
|
|
|
1149
1223
|
const content = fs.readFileSync(rc.path, 'utf8');
|
|
1150
1224
|
|
|
1151
|
-
|
|
1225
|
+
// 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)) {
|
|
1152
1228
|
continue;
|
|
1153
1229
|
}
|
|
1154
1230
|
|
|
1155
|
-
// Remove
|
|
1231
|
+
// Remove all alias block lines
|
|
1156
1232
|
const lines = content.split('\n');
|
|
1157
|
-
const filteredLines =
|
|
1158
|
-
|
|
1233
|
+
const filteredLines = lines.filter(line => {
|
|
1234
|
+
const trimmed = line.trim();
|
|
1235
|
+
return !ALIAS_BLOCK_LINES.includes(trimmed);
|
|
1236
|
+
});
|
|
1159
1237
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
continue;
|
|
1164
|
-
}
|
|
1165
|
-
if (
|
|
1166
|
-
inBlock &&
|
|
1167
|
-
(line.startsWith('# Use ') ||
|
|
1168
|
-
line.startsWith('alias af=') ||
|
|
1169
|
-
line.startsWith('alias agileflow='))
|
|
1170
|
-
) {
|
|
1171
|
-
continue;
|
|
1172
|
-
}
|
|
1173
|
-
if (inBlock && line.trim() === '') {
|
|
1174
|
-
inBlock = false;
|
|
1175
|
-
continue;
|
|
1176
|
-
}
|
|
1177
|
-
inBlock = false;
|
|
1178
|
-
filteredLines.push(line);
|
|
1238
|
+
// Remove trailing empty lines from cleanup
|
|
1239
|
+
while (filteredLines.length > 0 && filteredLines[filteredLines.length - 1].trim() === '') {
|
|
1240
|
+
filteredLines.pop();
|
|
1179
1241
|
}
|
|
1180
1242
|
|
|
1181
|
-
fs.writeFileSync(rc.path, filteredLines.join('\n'), 'utf8');
|
|
1243
|
+
fs.writeFileSync(rc.path, filteredLines.join('\n') + '\n', 'utf8');
|
|
1182
1244
|
result.removed.push(rc.name);
|
|
1183
1245
|
} catch (err) {
|
|
1184
1246
|
result.skipped.push(`${rc.name} (error: ${err.message})`);
|
|
@@ -450,9 +450,15 @@ function createBashHook() {
|
|
|
450
450
|
|
|
451
451
|
/**
|
|
452
452
|
* Test command against a single pattern rule
|
|
453
|
+
* Skips patterns that fail ReDoS validation
|
|
453
454
|
*/
|
|
454
455
|
function matchesPattern(command, rule) {
|
|
455
456
|
try {
|
|
457
|
+
// Validate pattern for ReDoS safety before use
|
|
458
|
+
const validation = validatePattern(rule.pattern);
|
|
459
|
+
if (!validation.safe) {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
456
462
|
const flags = rule.flags || '';
|
|
457
463
|
const regex = new RegExp(rule.pattern, flags);
|
|
458
464
|
return regex.test(command);
|
|
@@ -517,6 +523,37 @@ function createBashHook() {
|
|
|
517
523
|
};
|
|
518
524
|
}
|
|
519
525
|
|
|
526
|
+
/**
|
|
527
|
+
* Detect ReDoS-vulnerable patterns (nested quantifiers)
|
|
528
|
+
*
|
|
529
|
+
* Checks for patterns like (a+)+ or (a*b?)* that cause
|
|
530
|
+
* catastrophic backtracking on pathological inputs.
|
|
531
|
+
*
|
|
532
|
+
* @param {string} pattern - Regex pattern string to validate
|
|
533
|
+
* @returns {{ safe: boolean, reason?: string }}
|
|
534
|
+
*/
|
|
535
|
+
function validatePattern(pattern) {
|
|
536
|
+
if (!pattern || typeof pattern !== 'string') {
|
|
537
|
+
return { safe: false, reason: 'Empty or invalid pattern' };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Detect nested quantifiers: a group with a quantifier containing inner quantifiers
|
|
541
|
+
// Matches patterns like (x+)*, (x+)+, (x*)+, (x+){2,}, etc.
|
|
542
|
+
const nestedQuantifierRe = /\([^)]*[+*][^)]*\)[+*{]/;
|
|
543
|
+
if (nestedQuantifierRe.test(pattern)) {
|
|
544
|
+
return { safe: false, reason: `Nested quantifier detected in: ${pattern}` };
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Try to compile the regex to catch syntax errors
|
|
548
|
+
try {
|
|
549
|
+
new RegExp(pattern);
|
|
550
|
+
} catch (e) {
|
|
551
|
+
return { safe: false, reason: `Invalid regex: ${e.message}` };
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return { safe: true };
|
|
555
|
+
}
|
|
556
|
+
|
|
520
557
|
module.exports = {
|
|
521
558
|
c,
|
|
522
559
|
findProjectRoot,
|
|
@@ -529,6 +566,7 @@ module.exports = {
|
|
|
529
566
|
parseBashPatterns,
|
|
530
567
|
parsePathPatterns,
|
|
531
568
|
validatePathAgainstPatterns,
|
|
569
|
+
validatePattern,
|
|
532
570
|
createPathHook,
|
|
533
571
|
createBashHook,
|
|
534
572
|
CONFIG_PATHS,
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: error-analyzer
|
|
3
3
|
description: Error diagnosis specialist that analyzes stack traces, correlates logs, identifies root causes, and suggests fixes before external research is needed
|
|
4
|
-
tools:
|
|
5
|
-
- Glob
|
|
6
|
-
- Grep
|
|
7
|
-
- Bash
|
|
4
|
+
tools: Read, Glob, Grep, Bash
|
|
8
5
|
model: sonnet
|
|
9
6
|
team_role: utility
|
|
10
7
|
---
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: logic-consensus
|
|
3
3
|
description: Consensus coordinator for logic analysis - validates findings, votes on issues, resolves conflicts, and generates the final audit report
|
|
4
|
-
tools:
|
|
5
|
-
- Write
|
|
6
|
-
- Edit
|
|
7
|
-
- Glob
|
|
8
|
-
- Grep
|
|
4
|
+
tools: Read, Write, Edit, Glob, Grep
|
|
9
5
|
model: sonnet
|
|
10
6
|
team_role: lead
|
|
11
7
|
---
|
|
@@ -202,3 +202,36 @@ learnings:
|
|
|
202
202
|
- "rpi: Research-Plan-Implement workflow with phase transitions"
|
|
203
203
|
- "workflow: Parameterized workflow templates (built-in + custom)"
|
|
204
204
|
notes: "All 9 files follow consistent documentation template extracted from source .md files: quick start, parameters table, detailed descriptions, usage examples with output, status indicators, best practices, error handling, and related commands. Ready for docs site publication."
|
|
205
|
+
|
|
206
|
+
- date: 2026-02-07
|
|
207
|
+
context: "Audited and fixed command documentation drift"
|
|
208
|
+
insight: "Systematically compared all 50 source commands against their documentation counterparts. Found critical drift in configure.mdx (50% outdated) and babysit.mdx (missing Smart Detection, Story Claiming, Logic Audit features). Fixed both with comprehensive updates preserving all source details."
|
|
209
|
+
modified_files:
|
|
210
|
+
- "apps/docs/content/docs/commands/configure.mdx (major rewrite - 200+ lines added)"
|
|
211
|
+
- "apps/docs/content/docs/commands/babysit.mdx (200+ lines added)"
|
|
212
|
+
drift_issues_fixed:
|
|
213
|
+
- "configure.mdx: Added 5 profile types (full, basic, minimal, experimental, none)"
|
|
214
|
+
- "configure.mdx: Added hierarchical menu system documentation (5 categories, 4 max options)"
|
|
215
|
+
- "configure.mdx: Added custom profiles (save, export, import, delete)"
|
|
216
|
+
- "configure.mdx: Added all available features (8 total: SessionStart, PreCompact, RalphLoop, SelfImprove, Archival, StatusLine, AskUserQuestion, Tmux, Shell Aliases, Auto-Update, CLAUDE.md Rules)"
|
|
217
|
+
- "configure.mdx: Added maintenance commands (detect, migrate, upgrade, repair, list-scripts)"
|
|
218
|
+
- "configure.mdx: Added troubleshooting section with solutions"
|
|
219
|
+
- "babysit.mdx: Added Smart Detection section (auto-enable Loop/Visual/Coverage/Conditions modes)"
|
|
220
|
+
- "babysit.mdx: Added Story Claiming section (multi-session coordination, prevent conflicts)"
|
|
221
|
+
- "babysit.mdx: Added Logic Audit integration (post-implementation quality check)"
|
|
222
|
+
- "babysit.mdx: Updated Loop Mode to show auto-detection instead of explicit parameters"
|
|
223
|
+
verified_good:
|
|
224
|
+
- "council.mdx: Comprehensive and current"
|
|
225
|
+
- "audit.mdx: Good alignment with source"
|
|
226
|
+
- "choose.mdx: Good alignment with source"
|
|
227
|
+
quality_gates:
|
|
228
|
+
- "Description matches source frontmatter ✓"
|
|
229
|
+
- "Parameters documented with types and defaults ✓"
|
|
230
|
+
- "Examples are copy-paste ready ✓"
|
|
231
|
+
- "Usage patterns match source implementation ✓"
|
|
232
|
+
- "Links to related commands accurate ✓"
|
|
233
|
+
- "No outdated information remains ✓"
|
|
234
|
+
- "All new features from source documented ✓"
|
|
235
|
+
- "Formatting consistent (tables, code blocks, bullets) ✓"
|
|
236
|
+
source: "packages/cli/src/core/commands/*.md vs apps/docs/content/docs/commands/*.mdx"
|
|
237
|
+
notes: "Used systematic comparison approach: extracted descriptions, parameters, and features from 50 source commands against documentation. Prioritized commands with most feature additions: babysit (Smart Detection, Story Claiming, Logic Audit), configure (profiles, hierarchical menus). All changes preserve source accuracy while making docs more discoverable and comprehensive."
|
|
@@ -16,7 +16,7 @@ version: "1.0.0"
|
|
|
16
16
|
|
|
17
17
|
bashToolPatterns:
|
|
18
18
|
# ─── File System Destruction ───
|
|
19
|
-
- pattern: '\brm\s+(-[rRf]+\s+)
|
|
19
|
+
- pattern: '\brm\s+(-[rRf]+\s+)?/'
|
|
20
20
|
reason: "rm with absolute path - could delete system files"
|
|
21
21
|
|
|
22
22
|
- pattern: '\brm\s+-[rRf]*\s+\.\.'
|
|
@@ -25,7 +25,7 @@ bashToolPatterns:
|
|
|
25
25
|
- pattern: '\brm\s+-rf\s+'
|
|
26
26
|
reason: "Recursive force delete - extremely dangerous"
|
|
27
27
|
|
|
28
|
-
- pattern: '\brmdir\s+(-p\s+)
|
|
28
|
+
- pattern: '\brmdir\s+(-p\s+)?/'
|
|
29
29
|
reason: "rmdir with absolute path"
|
|
30
30
|
|
|
31
31
|
# ─── Git Destructive Operations ───
|