agileflow 3.1.0 → 3.2.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 +5 -0
- package/README.md +57 -85
- package/lib/dashboard-automations.js +130 -0
- package/lib/dashboard-git.js +254 -0
- package/lib/dashboard-inbox.js +64 -0
- package/lib/dashboard-protocol.js +1 -0
- package/lib/dashboard-server.js +114 -924
- package/lib/dashboard-session.js +136 -0
- package/lib/dashboard-status.js +72 -0
- package/lib/dashboard-terminal.js +354 -0
- package/lib/dashboard-websocket.js +88 -0
- package/lib/drivers/codex-driver.ts +4 -4
- package/lib/logger.js +106 -0
- package/package.json +4 -2
- package/scripts/agileflow-configure.js +2 -2
- package/scripts/agileflow-welcome.js +409 -434
- package/scripts/claude-tmux.sh +80 -2
- package/scripts/context-loader.js +4 -9
- package/scripts/lib/command-prereqs.js +280 -0
- package/scripts/lib/configure-detect.js +92 -2
- package/scripts/lib/configure-features.js +295 -1
- package/scripts/lib/context-formatter.js +468 -233
- package/scripts/lib/context-loader.js +27 -15
- package/scripts/lib/damage-control-utils.js +8 -1
- package/scripts/lib/feature-catalog.js +321 -0
- package/scripts/lib/portable-tasks-cli.js +274 -0
- package/scripts/lib/portable-tasks.js +479 -0
- package/scripts/lib/signal-detectors.js +1 -1
- package/scripts/lib/team-events.js +86 -1
- package/scripts/obtain-context.js +28 -4
- package/scripts/smart-detect.js +17 -0
- package/scripts/strip-ai-attribution.js +63 -0
- package/scripts/team-manager.js +7 -2
- package/scripts/welcome-deferred.js +437 -0
- package/src/core/agents/perf-analyzer-assets.md +174 -0
- package/src/core/agents/perf-analyzer-bundle.md +165 -0
- package/src/core/agents/perf-analyzer-caching.md +160 -0
- package/src/core/agents/perf-analyzer-compute.md +165 -0
- package/src/core/agents/perf-analyzer-memory.md +182 -0
- package/src/core/agents/perf-analyzer-network.md +157 -0
- package/src/core/agents/perf-analyzer-queries.md +155 -0
- package/src/core/agents/perf-analyzer-rendering.md +156 -0
- package/src/core/agents/perf-consensus.md +280 -0
- package/src/core/agents/security-analyzer-api.md +199 -0
- package/src/core/agents/security-analyzer-auth.md +160 -0
- package/src/core/agents/security-analyzer-authz.md +168 -0
- package/src/core/agents/security-analyzer-deps.md +147 -0
- package/src/core/agents/security-analyzer-infra.md +176 -0
- package/src/core/agents/security-analyzer-injection.md +148 -0
- package/src/core/agents/security-analyzer-input.md +191 -0
- package/src/core/agents/security-analyzer-secrets.md +175 -0
- package/src/core/agents/security-consensus.md +276 -0
- package/src/core/agents/test-analyzer-assertions.md +181 -0
- package/src/core/agents/test-analyzer-coverage.md +183 -0
- package/src/core/agents/test-analyzer-fragility.md +185 -0
- package/src/core/agents/test-analyzer-integration.md +155 -0
- package/src/core/agents/test-analyzer-maintenance.md +173 -0
- package/src/core/agents/test-analyzer-mocking.md +178 -0
- package/src/core/agents/test-analyzer-patterns.md +189 -0
- package/src/core/agents/test-analyzer-structure.md +177 -0
- package/src/core/agents/test-consensus.md +294 -0
- package/src/core/commands/{legal/audit.md → audit/legal.md} +13 -13
- package/src/core/commands/{logic/audit.md → audit/logic.md} +12 -12
- package/src/core/commands/audit/performance.md +443 -0
- package/src/core/commands/audit/security.md +443 -0
- package/src/core/commands/audit/test.md +442 -0
- package/src/core/commands/babysit.md +505 -463
- package/src/core/commands/configure.md +8 -8
- package/src/core/commands/research/ask.md +42 -9
- package/src/core/commands/research/import.md +14 -8
- package/src/core/commands/research/list.md +17 -16
- package/src/core/commands/research/synthesize.md +8 -8
- package/src/core/commands/research/view.md +28 -4
- package/src/core/commands/whats-new.md +2 -2
- package/src/core/experts/devops/expertise.yaml +13 -2
- package/src/core/experts/documentation/expertise.yaml +26 -4
- package/src/core/profiles/COMPARISON.md +170 -0
- package/src/core/profiles/README.md +178 -0
- package/src/core/profiles/claude-code.yaml +111 -0
- package/src/core/profiles/codex.yaml +103 -0
- package/src/core/profiles/cursor.yaml +134 -0
- package/src/core/profiles/examples.js +250 -0
- package/src/core/profiles/loader.js +235 -0
- package/src/core/profiles/windsurf.yaml +159 -0
- package/src/core/teams/logic-audit.json +6 -0
- package/src/core/teams/perf-audit.json +71 -0
- package/src/core/teams/security-audit.json +71 -0
- package/src/core/teams/test-audit.json +71 -0
- package/src/core/templates/command-prerequisites.yaml +169 -0
- package/src/core/templates/damage-control-patterns.yaml +9 -0
- package/tools/cli/installers/ide/_base-ide.js +33 -3
- package/tools/cli/installers/ide/claude-code.js +2 -69
- package/tools/cli/installers/ide/codex.js +9 -9
- package/tools/cli/installers/ide/cursor.js +165 -4
- package/tools/cli/installers/ide/windsurf.js +237 -6
- package/tools/cli/lib/content-transformer.js +234 -9
- package/tools/cli/lib/docs-setup.js +1 -1
- package/tools/cli/lib/ide-generator.js +357 -0
- package/tools/cli/lib/ide-registry.js +2 -2
- package/scripts/tmux-task-name.sh +0 -105
- package/scripts/tmux-task-watcher.sh +0 -344
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# tmux-task-name.sh - Rename current tmux window based on task/work description
|
|
3
|
-
#
|
|
4
|
-
# Called by Claude Code when starting work on a task (via TaskCreate/TaskUpdate).
|
|
5
|
-
# Reads the task subject from ~/.claude/tasks/ or accepts it as an argument.
|
|
6
|
-
#
|
|
7
|
-
# Usage:
|
|
8
|
-
# tmux-task-name.sh "Fix auth middleware" # Rename to task subject
|
|
9
|
-
# tmux-task-name.sh --scan # Auto-detect from task files
|
|
10
|
-
# tmux-task-name.sh --scan --session <UUID> # Scan only one session's tasks
|
|
11
|
-
# tmux-task-name.sh --reset # Reset to default "claude-N"
|
|
12
|
-
#
|
|
13
|
-
# The script is best-effort: silently exits if not inside tmux.
|
|
14
|
-
|
|
15
|
-
set -euo pipefail
|
|
16
|
-
|
|
17
|
-
# Exit silently if not in tmux
|
|
18
|
-
[ -n "${TMUX:-}" ] || exit 0
|
|
19
|
-
|
|
20
|
-
MAX_LEN=30
|
|
21
|
-
|
|
22
|
-
truncate_name() {
|
|
23
|
-
local name="$1"
|
|
24
|
-
if [ ${#name} -gt $MAX_LEN ]; then
|
|
25
|
-
echo "${name:0:$((MAX_LEN - 1))}…"
|
|
26
|
-
else
|
|
27
|
-
echo "$name"
|
|
28
|
-
fi
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
# Mode: reset to default sequential name
|
|
32
|
-
if [ "${1:-}" = "--reset" ]; then
|
|
33
|
-
N=$(( $(tmux list-windows -F '#{window_name}' 2>/dev/null | grep -c '^claude') ))
|
|
34
|
-
[ "$N" -eq 0 ] && N=1
|
|
35
|
-
tmux rename-window "claude-$N" 2>/dev/null || true
|
|
36
|
-
exit 0
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
|
-
# Mode: scan ~/.claude/tasks/ for most recently modified in-progress task
|
|
40
|
-
if [ "${1:-}" = "--scan" ]; then
|
|
41
|
-
TASKS_BASE="${HOME}/.claude/tasks"
|
|
42
|
-
[ -d "$TASKS_BASE" ] || exit 0
|
|
43
|
-
|
|
44
|
-
# Determine session scope: --session param, pane option, or global scan
|
|
45
|
-
SESSION_ID=""
|
|
46
|
-
if [ "${2:-}" = "--session" ] && [ -n "${3:-}" ]; then
|
|
47
|
-
# Validate session ID is alphanumeric + hyphens only (prevent path traversal)
|
|
48
|
-
if [[ "$3" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
49
|
-
SESSION_ID="$3"
|
|
50
|
-
fi
|
|
51
|
-
elif [ -n "${TMUX:-}" ]; then
|
|
52
|
-
SESSION_ID=$(tmux show-options -pqv @claude_session_id 2>/dev/null || true)
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
BEST_SUBJECT=""
|
|
56
|
-
BEST_MTIME=0
|
|
57
|
-
|
|
58
|
-
if [ -n "$SESSION_ID" ]; then
|
|
59
|
-
# Scoped scan: only look at this session's tasks
|
|
60
|
-
SCAN_DIR="$TASKS_BASE/$SESSION_ID"
|
|
61
|
-
if [ -d "$SCAN_DIR" ]; then
|
|
62
|
-
for f in "$SCAN_DIR"/*.json; do
|
|
63
|
-
[ -f "$f" ] || continue
|
|
64
|
-
status=$(python3 -c "import json,sys; d=json.load(open('$f')); print(d.get('status',''))" 2>/dev/null || echo "")
|
|
65
|
-
if [ "$status" = "in_progress" ]; then
|
|
66
|
-
mtime=$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f" 2>/dev/null || echo "0")
|
|
67
|
-
if [ "$mtime" -gt "$BEST_MTIME" ]; then
|
|
68
|
-
BEST_MTIME=$mtime
|
|
69
|
-
BEST_SUBJECT=$(python3 -c "import json; d=json.load(open('$f')); print(d.get('subject',''))" 2>/dev/null || echo "")
|
|
70
|
-
fi
|
|
71
|
-
fi
|
|
72
|
-
done
|
|
73
|
-
fi
|
|
74
|
-
else
|
|
75
|
-
# Global scan: all sessions (fallback for no session context)
|
|
76
|
-
for dir in "$TASKS_BASE"/*/; do
|
|
77
|
-
[ -d "$dir" ] || continue
|
|
78
|
-
for f in "$dir"*.json; do
|
|
79
|
-
[ -f "$f" ] || continue
|
|
80
|
-
status=$(python3 -c "import json,sys; d=json.load(open('$f')); print(d.get('status',''))" 2>/dev/null || echo "")
|
|
81
|
-
if [ "$status" = "in_progress" ]; then
|
|
82
|
-
mtime=$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f" 2>/dev/null || echo "0")
|
|
83
|
-
if [ "$mtime" -gt "$BEST_MTIME" ]; then
|
|
84
|
-
BEST_MTIME=$mtime
|
|
85
|
-
BEST_SUBJECT=$(python3 -c "import json; d=json.load(open('$f')); print(d.get('subject',''))" 2>/dev/null || echo "")
|
|
86
|
-
fi
|
|
87
|
-
fi
|
|
88
|
-
done
|
|
89
|
-
done
|
|
90
|
-
fi
|
|
91
|
-
|
|
92
|
-
if [ -n "$BEST_SUBJECT" ]; then
|
|
93
|
-
tmux rename-window "$(truncate_name "$BEST_SUBJECT")" 2>/dev/null || true
|
|
94
|
-
fi
|
|
95
|
-
exit 0
|
|
96
|
-
fi
|
|
97
|
-
|
|
98
|
-
# Mode: direct - rename to provided argument
|
|
99
|
-
if [ -n "${1:-}" ]; then
|
|
100
|
-
tmux rename-window "$(truncate_name "$1")" 2>/dev/null || true
|
|
101
|
-
exit 0
|
|
102
|
-
fi
|
|
103
|
-
|
|
104
|
-
echo "Usage: tmux-task-name.sh <task-subject> | --scan | --reset" >&2
|
|
105
|
-
exit 1
|
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# tmux-task-watcher.sh - Auto-rename tmux window based on Claude Code tasks
|
|
3
|
-
#
|
|
4
|
-
# Launched automatically by SessionStart hook. Self-backgrounds immediately.
|
|
5
|
-
# Polls ~/.claude/tasks/<session-id>/ for in-progress tasks every few seconds.
|
|
6
|
-
# Renames the tmux window to match the active task subject.
|
|
7
|
-
#
|
|
8
|
-
# Usage:
|
|
9
|
-
# tmux-task-watcher.sh # Start watcher (backgrounds itself)
|
|
10
|
-
# tmux-task-watcher.sh stop # Stop watcher for current pane
|
|
11
|
-
#
|
|
12
|
-
# Requirements:
|
|
13
|
-
# - Must be inside tmux ($TMUX set)
|
|
14
|
-
# - Node.js available (for JSON parsing)
|
|
15
|
-
# - Claude Code session active ($CLAUDECODE=1)
|
|
16
|
-
|
|
17
|
-
set -euo pipefail
|
|
18
|
-
|
|
19
|
-
# Only run inside tmux
|
|
20
|
-
[ -n "${TMUX:-}" ] || exit 0
|
|
21
|
-
|
|
22
|
-
MODE="${1:-start}"
|
|
23
|
-
MAX_LEN=30
|
|
24
|
-
|
|
25
|
-
# Get current pane ID for per-pane tracking
|
|
26
|
-
PANE_ID=$(tmux display-message -p '#{pane_id}' 2>/dev/null || true)
|
|
27
|
-
[ -n "$PANE_ID" ] || exit 0
|
|
28
|
-
|
|
29
|
-
# PID file keyed by pane to allow multiple watchers (one per window)
|
|
30
|
-
SAFE_PANE_ID="${PANE_ID//[^a-zA-Z0-9]/_}"
|
|
31
|
-
PID_FILE="/tmp/tmux-task-watcher-${SAFE_PANE_ID}.pid"
|
|
32
|
-
|
|
33
|
-
# Claim file: marks which pane owns which session to prevent cross-contamination
|
|
34
|
-
CLAIM_DIR="/tmp/tmux-session-claims"
|
|
35
|
-
|
|
36
|
-
cleanup_claims() {
|
|
37
|
-
[ -d "$CLAIM_DIR" ] || return 0
|
|
38
|
-
for cf in "$CLAIM_DIR"/claim-*; do
|
|
39
|
-
[ -f "$cf" ] || continue
|
|
40
|
-
local claimer
|
|
41
|
-
claimer=$(cat "$cf" 2>/dev/null || true)
|
|
42
|
-
if [ "$claimer" = "$PANE_ID" ]; then
|
|
43
|
-
rm -f "$cf"
|
|
44
|
-
fi
|
|
45
|
-
done
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
# --- Stop mode ---
|
|
49
|
-
if [ "$MODE" = "stop" ]; then
|
|
50
|
-
if [ -f "$PID_FILE" ]; then
|
|
51
|
-
kill "$(cat "$PID_FILE")" 2>/dev/null || true
|
|
52
|
-
rm "$PID_FILE" 2>/dev/null || true
|
|
53
|
-
fi
|
|
54
|
-
cleanup_claims
|
|
55
|
-
exit 0
|
|
56
|
-
fi
|
|
57
|
-
|
|
58
|
-
# --- Start mode ---
|
|
59
|
-
if [ "$MODE" = "start" ]; then
|
|
60
|
-
# Kill any existing watcher for this pane first
|
|
61
|
-
if [ -f "$PID_FILE" ]; then
|
|
62
|
-
kill "$(cat "$PID_FILE")" 2>/dev/null || true
|
|
63
|
-
rm "$PID_FILE" 2>/dev/null || true
|
|
64
|
-
fi
|
|
65
|
-
|
|
66
|
-
# Self-background: parent exits immediately so hook completes fast
|
|
67
|
-
_WATCHER_BG=1 nohup "$0" "_run" >/dev/null 2>&1 &
|
|
68
|
-
echo $! > "$PID_FILE"
|
|
69
|
-
exit 0
|
|
70
|
-
fi
|
|
71
|
-
|
|
72
|
-
# --- _run mode: skip to background worker below ---
|
|
73
|
-
|
|
74
|
-
# ============================================================
|
|
75
|
-
# Background watcher logic (runs in forked process)
|
|
76
|
-
# ============================================================
|
|
77
|
-
|
|
78
|
-
# Disable strict mode for background worker - we handle errors ourselves
|
|
79
|
-
set +eu
|
|
80
|
-
|
|
81
|
-
# Derive Claude Code project dir from PWD
|
|
82
|
-
CLAUDE_PROJECT_DIR="${HOME}/.claude/projects/$(pwd | sed 's|/|-|g')"
|
|
83
|
-
|
|
84
|
-
# --- Session claim helpers ---
|
|
85
|
-
mkdir -p "$CLAIM_DIR" 2>/dev/null || true
|
|
86
|
-
|
|
87
|
-
claim_session() {
|
|
88
|
-
local sid="$1"
|
|
89
|
-
echo "$PANE_ID" > "$CLAIM_DIR/claim-${sid}" 2>/dev/null || true
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
is_claimed_by_other() {
|
|
93
|
-
local sid="$1"
|
|
94
|
-
local cf="$CLAIM_DIR/claim-${sid}"
|
|
95
|
-
[ -f "$cf" ] || return 1 # not claimed
|
|
96
|
-
local claimer
|
|
97
|
-
claimer=$(cat "$cf" 2>/dev/null || true)
|
|
98
|
-
# Empty or unreadable claim file - treat as unclaimed
|
|
99
|
-
[ -n "$claimer" ] || return 1
|
|
100
|
-
[ "$claimer" != "$PANE_ID" ] || return 1 # claimed by us = not "other"
|
|
101
|
-
# Check if claimer pane is still alive
|
|
102
|
-
if tmux list-panes -a -F '#{pane_id}' 2>/dev/null | grep -qF "$claimer"; then
|
|
103
|
-
return 0 # claimed by another live pane
|
|
104
|
-
fi
|
|
105
|
-
# Claimer is dead - remove stale claim
|
|
106
|
-
rm -f "$cf"
|
|
107
|
-
return 1
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
# Find Claude PID running inside this tmux pane
|
|
111
|
-
find_claude_pid() {
|
|
112
|
-
local pane_pid
|
|
113
|
-
pane_pid=$(tmux display-message -p -t "$PANE_ID" '#{pane_pid}' 2>/dev/null || true)
|
|
114
|
-
[ -n "$pane_pid" ] || return 1
|
|
115
|
-
|
|
116
|
-
# Walk descendants of the pane shell looking for claude/claude-code process
|
|
117
|
-
# Process tree: pane_shell -> (possibly bash/claude-smart.sh) -> claude
|
|
118
|
-
local candidates
|
|
119
|
-
candidates=$(ps -eo pid,ppid,comm 2>/dev/null | awk -v root="$pane_pid" '
|
|
120
|
-
BEGIN { pids[root]=1 }
|
|
121
|
-
{ child=$1; parent=$2; comm=$3; children[parent]=children[parent] " " child; comms[child]=comm }
|
|
122
|
-
END {
|
|
123
|
-
# BFS through process tree
|
|
124
|
-
queue[1]=root; qi=1; qn=1
|
|
125
|
-
while (qi <= qn) {
|
|
126
|
-
p = queue[qi++]
|
|
127
|
-
n = split(children[p], kids, " ")
|
|
128
|
-
for (i=1; i<=n; i++) {
|
|
129
|
-
if (kids[i] != "") {
|
|
130
|
-
qn++; queue[qn] = kids[i]
|
|
131
|
-
pids[kids[i]] = 1
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
for (p in pids) {
|
|
136
|
-
c = comms[p]
|
|
137
|
-
if (c == "claude" || c == "claude-code" || c ~ /^node.*claude/) {
|
|
138
|
-
print p
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
' || true)
|
|
143
|
-
|
|
144
|
-
# Return the first match
|
|
145
|
-
echo "$candidates" | head -n1 | tr -d '[:space:]'
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
# Get process start time (epoch seconds) from /proc
|
|
149
|
-
get_pid_start_time() {
|
|
150
|
-
local pid="$1"
|
|
151
|
-
# /proc/<pid>/stat field 22 is starttime in clock ticks since boot
|
|
152
|
-
# Simpler: use stat on /proc/<pid> - ctime ≈ process creation
|
|
153
|
-
if [ -d "/proc/$pid" ]; then
|
|
154
|
-
stat -c '%Z' "/proc/$pid" 2>/dev/null && return
|
|
155
|
-
fi
|
|
156
|
-
# Fallback: ps-based start time
|
|
157
|
-
ps -o lstart= -p "$pid" 2>/dev/null | xargs -I{} date -d '{}' +%s 2>/dev/null || echo "0"
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
# --- 3-tier session finder ---
|
|
161
|
-
# Tier 1: Check tmux pane option (set by previous run or claude-smart.sh)
|
|
162
|
-
# Tier 2: Correlate Claude PID start time with JSONL birth time
|
|
163
|
-
# Tier 3: Newest unclaimed JSONL as fallback
|
|
164
|
-
find_session_id() {
|
|
165
|
-
[ -d "$CLAUDE_PROJECT_DIR" ] || return 1
|
|
166
|
-
|
|
167
|
-
# -- Tier 1: Cached pane option --
|
|
168
|
-
local cached_id
|
|
169
|
-
cached_id=$(tmux show-options -pqv @claude_session_id 2>/dev/null || true)
|
|
170
|
-
if [ -n "$cached_id" ]; then
|
|
171
|
-
local cached_dir="${HOME}/.claude/tasks/${cached_id}"
|
|
172
|
-
if [ -d "$cached_dir" ] || [ -f "$CLAUDE_PROJECT_DIR/${cached_id}.jsonl" ]; then
|
|
173
|
-
# Verify not claimed by another live pane
|
|
174
|
-
if ! is_claimed_by_other "$cached_id"; then
|
|
175
|
-
echo "$cached_id"
|
|
176
|
-
return 0
|
|
177
|
-
fi
|
|
178
|
-
fi
|
|
179
|
-
fi
|
|
180
|
-
# Also check @claude_uuid (set by claude-smart.sh)
|
|
181
|
-
cached_id=$(tmux show-options -pqv @claude_uuid 2>/dev/null || true)
|
|
182
|
-
if [ -n "$cached_id" ] && [ -f "$CLAUDE_PROJECT_DIR/${cached_id}.jsonl" ]; then
|
|
183
|
-
if ! is_claimed_by_other "$cached_id"; then
|
|
184
|
-
echo "$cached_id"
|
|
185
|
-
return 0
|
|
186
|
-
fi
|
|
187
|
-
fi
|
|
188
|
-
|
|
189
|
-
# -- Tier 2: PID-to-JSONL time correlation --
|
|
190
|
-
local claude_pid
|
|
191
|
-
claude_pid=$(find_claude_pid || true)
|
|
192
|
-
if [ -n "$claude_pid" ]; then
|
|
193
|
-
local pid_start
|
|
194
|
-
pid_start=$(get_pid_start_time "$claude_pid" || echo "0")
|
|
195
|
-
if [ "$pid_start" -gt 0 ] 2>/dev/null; then
|
|
196
|
-
local best_file="" best_delta=999999
|
|
197
|
-
for f in "$CLAUDE_PROJECT_DIR"/*.jsonl; do
|
|
198
|
-
[ -f "$f" ] || continue
|
|
199
|
-
local birth
|
|
200
|
-
birth=$(stat -c '%W' "$f" 2>/dev/null || echo "0")
|
|
201
|
-
[ "$birth" -gt 0 ] 2>/dev/null || continue
|
|
202
|
-
# JSONL should be born AFTER the PID started (within 120s window)
|
|
203
|
-
local delta=$(( birth - pid_start ))
|
|
204
|
-
if [ "$delta" -ge 0 ] && [ "$delta" -le 120 ] && [ "$delta" -lt "$best_delta" ]; then
|
|
205
|
-
best_delta=$delta
|
|
206
|
-
best_file=$f
|
|
207
|
-
fi
|
|
208
|
-
done
|
|
209
|
-
if [ -n "$best_file" ]; then
|
|
210
|
-
basename "$best_file" .jsonl
|
|
211
|
-
return 0
|
|
212
|
-
fi
|
|
213
|
-
fi
|
|
214
|
-
fi
|
|
215
|
-
|
|
216
|
-
# -- Tier 3: Newest unclaimed JSONL --
|
|
217
|
-
local best_file="" best_birth=0
|
|
218
|
-
for f in "$CLAUDE_PROJECT_DIR"/*.jsonl; do
|
|
219
|
-
[ -f "$f" ] || continue
|
|
220
|
-
local sid
|
|
221
|
-
sid=$(basename "$f" .jsonl)
|
|
222
|
-
# Skip if claimed by another live pane
|
|
223
|
-
if is_claimed_by_other "$sid"; then
|
|
224
|
-
continue
|
|
225
|
-
fi
|
|
226
|
-
local birth
|
|
227
|
-
birth=$(stat -c '%W' "$f" 2>/dev/null || echo "0")
|
|
228
|
-
if [ "$birth" -gt "$best_birth" ] 2>/dev/null; then
|
|
229
|
-
best_birth=$birth
|
|
230
|
-
best_file=$f
|
|
231
|
-
fi
|
|
232
|
-
done
|
|
233
|
-
[ -n "$best_file" ] || return 1
|
|
234
|
-
basename "$best_file" .jsonl
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
# Wait briefly for session file to be created/updated
|
|
238
|
-
sleep 2
|
|
239
|
-
|
|
240
|
-
SESSION_ID=$(find_session_id || true)
|
|
241
|
-
if [ -z "$SESSION_ID" ]; then
|
|
242
|
-
# Retry after a longer wait
|
|
243
|
-
sleep 5
|
|
244
|
-
SESSION_ID=$(find_session_id || true)
|
|
245
|
-
fi
|
|
246
|
-
|
|
247
|
-
# Store session ID and claim it for this pane
|
|
248
|
-
if [ -n "$SESSION_ID" ]; then
|
|
249
|
-
tmux set-option -p @claude_session_id "$SESSION_ID" 2>/dev/null || true
|
|
250
|
-
claim_session "$SESSION_ID"
|
|
251
|
-
fi
|
|
252
|
-
|
|
253
|
-
# Only set TASKS_DIR if we have a valid session (avoid scanning parent dir)
|
|
254
|
-
TASKS_DIR=""
|
|
255
|
-
if [ -n "$SESSION_ID" ]; then
|
|
256
|
-
TASKS_DIR="${HOME}/.claude/tasks/${SESSION_ID}"
|
|
257
|
-
fi
|
|
258
|
-
LAST_WINDOW_NAME=""
|
|
259
|
-
POLL_INTERVAL=5
|
|
260
|
-
MAX_RUNTIME=$((12 * 3600)) # 12 hours safety limit
|
|
261
|
-
START_TIME=$(date +%s)
|
|
262
|
-
|
|
263
|
-
# Parse task files and find the most recent in-progress task subject
|
|
264
|
-
get_active_task() {
|
|
265
|
-
[ -d "$TASKS_DIR" ] || return
|
|
266
|
-
node -e "
|
|
267
|
-
const fs = require('fs'), path = require('path');
|
|
268
|
-
const dir = process.argv[1];
|
|
269
|
-
let best = { mtime: 0, subject: '' };
|
|
270
|
-
try {
|
|
271
|
-
for (const f of fs.readdirSync(dir)) {
|
|
272
|
-
if (!f.endsWith('.json') || f === '.lock') continue;
|
|
273
|
-
const fp = path.join(dir, f);
|
|
274
|
-
const data = JSON.parse(fs.readFileSync(fp, 'utf8'));
|
|
275
|
-
if (data.status === 'in_progress') {
|
|
276
|
-
const mt = fs.statSync(fp).mtimeMs;
|
|
277
|
-
if (mt > best.mtime) {
|
|
278
|
-
best = { mtime: mt, subject: data.subject || data.activeForm || '' };
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
} catch(e) {}
|
|
283
|
-
if (best.subject) process.stdout.write(best.subject);
|
|
284
|
-
" "$TASKS_DIR" 2>/dev/null || true
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
truncate_name() {
|
|
288
|
-
local name="$1"
|
|
289
|
-
if [ ${#name} -gt $MAX_LEN ]; then
|
|
290
|
-
printf '%s' "${name:0:$((MAX_LEN - 1))}"
|
|
291
|
-
else
|
|
292
|
-
printf '%s' "$name"
|
|
293
|
-
fi
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
# Main polling loop
|
|
297
|
-
while true; do
|
|
298
|
-
# Safety timeout
|
|
299
|
-
NOW=$(date +%s)
|
|
300
|
-
if [ $((NOW - START_TIME)) -gt $MAX_RUNTIME ]; then
|
|
301
|
-
break
|
|
302
|
-
fi
|
|
303
|
-
|
|
304
|
-
# Check if our pane is still alive
|
|
305
|
-
if ! tmux list-panes -a -F '#{pane_id}' 2>/dev/null | grep -qF "$PANE_ID"; then
|
|
306
|
-
break
|
|
307
|
-
fi
|
|
308
|
-
|
|
309
|
-
# Re-validate that our session hasn't been claimed by another pane
|
|
310
|
-
if [ -n "$SESSION_ID" ] && is_claimed_by_other "$SESSION_ID"; then
|
|
311
|
-
SESSION_ID=""
|
|
312
|
-
TASKS_DIR=""
|
|
313
|
-
fi
|
|
314
|
-
|
|
315
|
-
# If we don't have a session ID yet, retry finding it
|
|
316
|
-
if [ -z "$SESSION_ID" ]; then
|
|
317
|
-
SESSION_ID=$(find_session_id || true)
|
|
318
|
-
if [ -n "$SESSION_ID" ]; then
|
|
319
|
-
tmux set-option -p @claude_session_id "$SESSION_ID" 2>/dev/null || true
|
|
320
|
-
claim_session "$SESSION_ID"
|
|
321
|
-
TASKS_DIR="${HOME}/.claude/tasks/${SESSION_ID}"
|
|
322
|
-
fi
|
|
323
|
-
fi
|
|
324
|
-
|
|
325
|
-
# Get active task subject (only if we have a valid session)
|
|
326
|
-
ACTIVE_TASK=""
|
|
327
|
-
if [ -n "$TASKS_DIR" ]; then
|
|
328
|
-
ACTIVE_TASK=$(get_active_task 2>/dev/null || true)
|
|
329
|
-
fi
|
|
330
|
-
|
|
331
|
-
if [ -n "$ACTIVE_TASK" ]; then
|
|
332
|
-
NEW_NAME=$(truncate_name "$ACTIVE_TASK")
|
|
333
|
-
if [ "$NEW_NAME" != "$LAST_WINDOW_NAME" ]; then
|
|
334
|
-
tmux rename-window -t "$PANE_ID" "$NEW_NAME" 2>/dev/null || true
|
|
335
|
-
LAST_WINDOW_NAME="$NEW_NAME"
|
|
336
|
-
fi
|
|
337
|
-
fi
|
|
338
|
-
|
|
339
|
-
sleep "$POLL_INTERVAL"
|
|
340
|
-
done
|
|
341
|
-
|
|
342
|
-
# Cleanup
|
|
343
|
-
cleanup_claims
|
|
344
|
-
rm -f "$PID_FILE"
|