agileflow 3.3.0 → 3.4.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 +6 -6
- package/lib/skill-loader.js +0 -1
- package/package.json +1 -1
- package/scripts/agileflow-statusline.sh +81 -0
- package/scripts/claude-tmux.sh +113 -22
- package/scripts/claude-watchdog.sh +225 -0
- package/scripts/generators/agent-registry.js +14 -1
- package/scripts/generators/inject-babysit.js +22 -9
- package/scripts/generators/inject-help.js +19 -9
- package/scripts/lib/audit-cleanup.js +250 -0
- package/scripts/lib/audit-registry.js +248 -0
- package/scripts/lib/feature-catalog.js +3 -3
- package/scripts/lib/gate-enforcer.js +295 -0
- package/scripts/lib/model-profiles.js +98 -0
- package/scripts/lib/signal-detectors.js +1 -1
- package/scripts/lib/skill-catalog.js +557 -0
- package/scripts/lib/skill-recommender.js +311 -0
- package/scripts/lib/tdd-phase-manager.js +455 -0
- package/scripts/lib/team-events.js +34 -3
- package/scripts/lib/tmux-group-colors.js +113 -0
- package/scripts/messaging-bridge.js +209 -1
- package/scripts/spawn-audit-sessions.js +549 -0
- package/scripts/team-manager.js +37 -16
- package/scripts/tmux-close-windows.sh +180 -0
- package/src/core/agents/ads-audit-budget.md +181 -0
- package/src/core/agents/ads-audit-compliance.md +169 -0
- package/src/core/agents/ads-audit-creative.md +164 -0
- package/src/core/agents/ads-audit-google.md +226 -0
- package/src/core/agents/ads-audit-meta.md +183 -0
- package/src/core/agents/ads-audit-tracking.md +197 -0
- package/src/core/agents/ads-consensus.md +322 -0
- package/src/core/agents/brainstorm-analyzer-features.md +169 -0
- package/src/core/agents/brainstorm-analyzer-growth.md +161 -0
- package/src/core/agents/brainstorm-analyzer-integration.md +172 -0
- package/src/core/agents/brainstorm-analyzer-market.md +147 -0
- package/src/core/agents/brainstorm-analyzer-ux.md +167 -0
- package/src/core/agents/brainstorm-consensus.md +237 -0
- package/src/core/agents/completeness-consensus.md +5 -5
- package/src/core/agents/perf-consensus.md +2 -2
- package/src/core/agents/security-consensus.md +2 -2
- package/src/core/agents/seo-analyzer-content.md +167 -0
- package/src/core/agents/seo-analyzer-images.md +187 -0
- package/src/core/agents/seo-analyzer-performance.md +206 -0
- package/src/core/agents/seo-analyzer-schema.md +176 -0
- package/src/core/agents/seo-analyzer-sitemap.md +172 -0
- package/src/core/agents/seo-analyzer-technical.md +144 -0
- package/src/core/agents/seo-consensus.md +289 -0
- package/src/core/agents/test-consensus.md +2 -2
- package/src/core/commands/ads/audit.md +375 -0
- package/src/core/commands/ads/budget.md +97 -0
- package/src/core/commands/ads/competitor.md +112 -0
- package/src/core/commands/ads/creative.md +85 -0
- package/src/core/commands/ads/google.md +112 -0
- package/src/core/commands/ads/landing.md +119 -0
- package/src/core/commands/ads/linkedin.md +112 -0
- package/src/core/commands/ads/meta.md +91 -0
- package/src/core/commands/ads/microsoft.md +115 -0
- package/src/core/commands/ads/plan.md +321 -0
- package/src/core/commands/ads/tiktok.md +129 -0
- package/src/core/commands/ads/youtube.md +124 -0
- package/src/core/commands/ads.md +128 -0
- package/src/core/commands/babysit.md +249 -1284
- package/src/core/commands/{audit → code}/completeness.md +35 -25
- package/src/core/commands/{audit → code}/legal.md +26 -16
- package/src/core/commands/{audit → code}/logic.md +27 -16
- package/src/core/commands/{audit → code}/performance.md +30 -20
- package/src/core/commands/{audit → code}/security.md +32 -19
- package/src/core/commands/{audit → code}/test.md +30 -20
- package/src/core/commands/{discovery → ideate}/brief.md +12 -12
- package/src/core/commands/{discovery/new.md → ideate/discover.md} +13 -13
- package/src/core/commands/ideate/features.md +435 -0
- package/src/core/commands/seo/audit.md +373 -0
- package/src/core/commands/seo/competitor.md +174 -0
- package/src/core/commands/seo/content.md +107 -0
- package/src/core/commands/seo/geo.md +229 -0
- package/src/core/commands/seo/hreflang.md +140 -0
- package/src/core/commands/seo/images.md +96 -0
- package/src/core/commands/seo/page.md +198 -0
- package/src/core/commands/seo/plan.md +163 -0
- package/src/core/commands/seo/programmatic.md +131 -0
- package/src/core/commands/seo/references/cwv-thresholds.md +64 -0
- package/src/core/commands/seo/references/eeat-framework.md +110 -0
- package/src/core/commands/seo/references/quality-gates.md +91 -0
- package/src/core/commands/seo/references/schema-types.md +102 -0
- package/src/core/commands/seo/schema.md +183 -0
- package/src/core/commands/seo/sitemap.md +97 -0
- package/src/core/commands/seo/technical.md +100 -0
- package/src/core/commands/seo.md +107 -0
- package/src/core/commands/skill/list.md +68 -212
- package/src/core/commands/skill/recommend.md +216 -0
- package/src/core/commands/tdd-next.md +238 -0
- package/src/core/commands/tdd.md +210 -0
- package/src/core/experts/_core-expertise.yaml +105 -0
- package/src/core/experts/analytics/expertise.yaml +5 -99
- package/src/core/experts/codebase-query/expertise.yaml +3 -72
- package/src/core/experts/compliance/expertise.yaml +6 -72
- package/src/core/experts/database/expertise.yaml +9 -52
- package/src/core/experts/documentation/expertise.yaml +7 -140
- package/src/core/experts/integrations/expertise.yaml +7 -127
- package/src/core/experts/mentor/expertise.yaml +8 -35
- package/src/core/experts/monitoring/expertise.yaml +7 -49
- package/src/core/experts/performance/expertise.yaml +1 -26
- package/src/core/experts/security/expertise.yaml +9 -34
- package/src/core/experts/ui/expertise.yaml +6 -36
- package/src/core/knowledge/ads/ad-audit-checklist-scoring.md +424 -0
- package/src/core/knowledge/ads/ad-optimization-logic.md +590 -0
- package/src/core/knowledge/ads/ad-technical-specifications.md +385 -0
- package/src/core/knowledge/ads/definitive-advertising-reference-2026.md +506 -0
- package/src/core/knowledge/ads/paid-advertising-research-2026.md +445 -0
- package/src/core/templates/agileflow-metadata.json +15 -1
- package/tools/cli/installers/ide/_base-ide.js +42 -5
- package/tools/cli/installers/ide/claude-code.js +3 -3
- package/tools/cli/lib/content-injector.js +160 -12
- package/tools/cli/lib/docs-setup.js +1 -1
- package/src/core/commands/skill/create.md +0 -698
- package/src/core/commands/skill/delete.md +0 -316
- package/src/core/commands/skill/edit.md +0 -359
- package/src/core/commands/skill/test.md +0 -394
- package/src/core/commands/skill/upgrade.md +0 -552
- package/src/core/templates/skill-template.md +0 -117
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.4.0] - 2026-02-28
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Ads audit system, SEO analysis, ultradeep concurrency controls, and domain-first command naming
|
|
14
|
+
|
|
10
15
|
## [3.3.0] - 2026-02-24
|
|
11
16
|
|
|
12
17
|
### Added
|
package/README.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/agileflow)
|
|
6
|
-
[](https://docs.agileflow.projectquestorg.com/docs/commands)
|
|
7
|
+
[](https://docs.agileflow.projectquestorg.com/docs/agents)
|
|
8
8
|
[](https://docs.agileflow.projectquestorg.com/docs/features/skills)
|
|
9
9
|
|
|
10
10
|
**AI-driven agile development for Claude Code, Cursor, Windsurf, OpenAI Codex, and more.** Combining Scrum, Kanban, ADRs, and docs-as-code principles into one framework-agnostic system.
|
|
@@ -54,9 +54,9 @@ Traditional project management tools create friction between planning and execut
|
|
|
54
54
|
|
|
55
55
|
| Component | Count | Description |
|
|
56
56
|
|-----------|-------|-------------|
|
|
57
|
-
| [Commands](https://docs.agileflow.projectquestorg.com/docs/commands) |
|
|
58
|
-
| [Agents/Experts](https://docs.agileflow.projectquestorg.com/docs/agents) |
|
|
59
|
-
| [Skills](https://docs.agileflow.projectquestorg.com/docs/features/skills) | Dynamic |
|
|
57
|
+
| [Commands](https://docs.agileflow.projectquestorg.com/docs/commands) | 124 | Slash commands for agile workflows |
|
|
58
|
+
| [Agents/Experts](https://docs.agileflow.projectquestorg.com/docs/agents) | 111 | Specialized agents with self-improving knowledge bases |
|
|
59
|
+
| [Skills](https://docs.agileflow.projectquestorg.com/docs/features/skills) | Dynamic | Browse and install from skills.sh marketplace via `/agileflow:skill:recommend` |
|
|
60
60
|
|
|
61
61
|
---
|
|
62
62
|
|
|
@@ -66,7 +66,7 @@ Traditional project management tools create friction between planning and execut
|
|
|
66
66
|
|---------|-------------|------|
|
|
67
67
|
| Agent Expertise | Self-improving agents that maintain domain knowledge | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/agent-expertise-system) |
|
|
68
68
|
| Agent Teams | Multi-domain expert coordination with quality gates | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/agent-teams) |
|
|
69
|
-
| Skills System |
|
|
69
|
+
| Skills System | Browse and install skills from the skills.sh marketplace | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/skills) |
|
|
70
70
|
| Parallel Sessions | Isolated workspaces with boundary protection | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/parallel-sessions) |
|
|
71
71
|
| Loop Mode | Autonomous story execution until epic completion | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/loop-mode) |
|
|
72
72
|
| AI Council | Three-perspective strategic decision analysis | [Learn more](https://docs.agileflow.projectquestorg.com/docs/commands/council) |
|
package/lib/skill-loader.js
CHANGED
|
@@ -126,7 +126,6 @@ function loadSkill(skillDir) {
|
|
|
126
126
|
path: skillDir,
|
|
127
127
|
hasReferences: fs.existsSync(path.join(skillDir, 'references.md')),
|
|
128
128
|
hasCookbook: fs.existsSync(path.join(skillDir, 'cookbook')),
|
|
129
|
-
hasMcp: fs.existsSync(path.join(skillDir, '.mcp.json')),
|
|
130
129
|
metadata,
|
|
131
130
|
};
|
|
132
131
|
} catch {
|
package/package.json
CHANGED
|
@@ -88,6 +88,7 @@ SHOW_SESSION_TIME=false
|
|
|
88
88
|
SHOW_COST=false
|
|
89
89
|
SHOW_GIT=true
|
|
90
90
|
SHOW_SCALE=true
|
|
91
|
+
SHOW_ULTRADEEP=true
|
|
91
92
|
|
|
92
93
|
# Check agileflow-metadata.json for component settings
|
|
93
94
|
if [ -f "docs/00-meta/agileflow-metadata.json" ]; then
|
|
@@ -106,6 +107,7 @@ if [ -f "docs/00-meta/agileflow-metadata.json" ]; then
|
|
|
106
107
|
SHOW_COST=$(echo "$COMPONENTS" | jq -r '.cost | if . == null then true else . end')
|
|
107
108
|
SHOW_GIT=$(echo "$COMPONENTS" | jq -r '.git | if . == null then true else . end')
|
|
108
109
|
SHOW_SCALE=$(echo "$COMPONENTS" | jq -r '.scale | if . == null then true else . end')
|
|
110
|
+
SHOW_ULTRADEEP=$(echo "$COMPONENTS" | jq -r '.ultradeep | if . == null then true else . end')
|
|
109
111
|
fi
|
|
110
112
|
fi
|
|
111
113
|
|
|
@@ -669,6 +671,79 @@ if [ "$SHOW_SCALE" = "true" ] && [ -f "docs/09-agents/session-state.json" ]; the
|
|
|
669
671
|
fi
|
|
670
672
|
fi
|
|
671
673
|
|
|
674
|
+
# ============================================================================
|
|
675
|
+
# Ultradeep Audit Progress Detection
|
|
676
|
+
# ============================================================================
|
|
677
|
+
ULTRADEEP_DISPLAY=""
|
|
678
|
+
if [ "$SHOW_ULTRADEEP" = "true" ]; then
|
|
679
|
+
ULTRADEEP_DIR="docs/09-agents/ultradeep"
|
|
680
|
+
if [ -d "$ULTRADEEP_DIR" ]; then
|
|
681
|
+
# Find most recent trace directory by modification time
|
|
682
|
+
LATEST_TRACE=$(ls -td "$ULTRADEEP_DIR"/*/ 2>/dev/null | head -1)
|
|
683
|
+
if [ -n "$LATEST_TRACE" ]; then
|
|
684
|
+
ULTRA_STATUS_FILE="${LATEST_TRACE}_status.json"
|
|
685
|
+
if [ -f "$ULTRA_STATUS_FILE" ]; then
|
|
686
|
+
ULTRA_STATUS=$(cat "$ULTRA_STATUS_FILE" 2>/dev/null)
|
|
687
|
+
ULTRA_AUDIT_TYPE=$(echo "$ULTRA_STATUS" | jq -r '.audit_type // empty' 2>/dev/null)
|
|
688
|
+
ULTRA_TOTAL=$(echo "$ULTRA_STATUS" | jq -r '.analyzers | length' 2>/dev/null)
|
|
689
|
+
ULTRA_COMPLETED=$(echo "$ULTRA_STATUS" | jq -r '.completed | length' 2>/dev/null)
|
|
690
|
+
ULTRA_STARTED=$(echo "$ULTRA_STATUS" | jq -r '.started_at // empty' 2>/dev/null)
|
|
691
|
+
ULTRA_LAST_CHECKED=$(echo "$ULTRA_STATUS" | jq -r '.last_checked // empty' 2>/dev/null)
|
|
692
|
+
|
|
693
|
+
# Validate we got numeric values
|
|
694
|
+
[[ "$ULTRA_TOTAL" =~ ^[0-9]+$ ]] || ULTRA_TOTAL=0
|
|
695
|
+
[[ "$ULTRA_COMPLETED" =~ ^[0-9]+$ ]] || ULTRA_COMPLETED=0
|
|
696
|
+
|
|
697
|
+
if [ "$ULTRA_TOTAL" -gt 0 ] && [ -n "$ULTRA_AUDIT_TYPE" ]; then
|
|
698
|
+
# Staleness check: skip if started >60 min ago and no recent activity
|
|
699
|
+
ULTRA_STALE=false
|
|
700
|
+
if [ -n "$ULTRA_STARTED" ]; then
|
|
701
|
+
ULTRA_START_EPOCH=$(to_epoch "$ULTRA_STARTED" 2>/dev/null)
|
|
702
|
+
ULTRA_NOW=$(date +%s)
|
|
703
|
+
if [ -n "$ULTRA_START_EPOCH" ] && [ $((ULTRA_NOW - ULTRA_START_EPOCH)) -gt 3600 ]; then
|
|
704
|
+
# Started over 60 min ago - check last_checked
|
|
705
|
+
if [ -n "$ULTRA_LAST_CHECKED" ] && [ "$ULTRA_LAST_CHECKED" != "null" ]; then
|
|
706
|
+
ULTRA_LC_EPOCH=$(to_epoch "$ULTRA_LAST_CHECKED" 2>/dev/null)
|
|
707
|
+
if [ -n "$ULTRA_LC_EPOCH" ] && [ $((ULTRA_NOW - ULTRA_LC_EPOCH)) -gt 120 ]; then
|
|
708
|
+
ULTRA_STALE=true
|
|
709
|
+
fi
|
|
710
|
+
else
|
|
711
|
+
# No last_checked at all and started >60 min ago
|
|
712
|
+
ULTRA_STALE=true
|
|
713
|
+
fi
|
|
714
|
+
fi
|
|
715
|
+
fi
|
|
716
|
+
|
|
717
|
+
if [ "$ULTRA_STALE" = "false" ]; then
|
|
718
|
+
# Map audit type to color and prefix
|
|
719
|
+
case "$ULTRA_AUDIT_TYPE" in
|
|
720
|
+
logic) ULTRA_COLOR="\033[38;2;122;162;247m"; ULTRA_PREFIX="Logic" ;;
|
|
721
|
+
security) ULTRA_COLOR="\033[38;2;247;118;142m"; ULTRA_PREFIX="Sec" ;;
|
|
722
|
+
performance) ULTRA_COLOR="\033[38;2;115;218;202m"; ULTRA_PREFIX="Perf" ;;
|
|
723
|
+
test) ULTRA_COLOR="\033[38;2;224;175;104m"; ULTRA_PREFIX="Test" ;;
|
|
724
|
+
completeness) ULTRA_COLOR="\033[38;2;187;154;247m"; ULTRA_PREFIX="Comp" ;;
|
|
725
|
+
legal) ULTRA_COLOR="\033[38;2;158;206;106m"; ULTRA_PREFIX="Legal" ;;
|
|
726
|
+
*) ULTRA_COLOR="$DIM"; ULTRA_PREFIX="Audit" ;;
|
|
727
|
+
esac
|
|
728
|
+
|
|
729
|
+
# Build display with progress coloring
|
|
730
|
+
if [ "$ULTRA_COMPLETED" -eq "$ULTRA_TOTAL" ]; then
|
|
731
|
+
# All complete - green checkmark
|
|
732
|
+
ULTRADEEP_DISPLAY="${ULTRA_COLOR}${ULTRA_PREFIX}${RESET} ${GREEN}${ULTRA_COMPLETED}/${ULTRA_TOTAL}✓${RESET}"
|
|
733
|
+
elif [ "$ULTRA_COMPLETED" -gt 0 ]; then
|
|
734
|
+
# In progress
|
|
735
|
+
ULTRADEEP_DISPLAY="${ULTRA_COLOR}${ULTRA_PREFIX}${RESET} ${ULTRA_COLOR}${ULTRA_COMPLETED}/${ULTRA_TOTAL}${RESET}"
|
|
736
|
+
else
|
|
737
|
+
# Just started (0 complete) - dim the fraction
|
|
738
|
+
ULTRADEEP_DISPLAY="${ULTRA_COLOR}${ULTRA_PREFIX}${RESET} ${DIM}${ULTRA_COMPLETED}/${ULTRA_TOTAL}${RESET}"
|
|
739
|
+
fi
|
|
740
|
+
fi
|
|
741
|
+
fi
|
|
742
|
+
fi
|
|
743
|
+
fi
|
|
744
|
+
fi
|
|
745
|
+
fi
|
|
746
|
+
|
|
672
747
|
# ============================================================================
|
|
673
748
|
# Build Status Line
|
|
674
749
|
# ============================================================================
|
|
@@ -759,6 +834,12 @@ if [ "$SHOW_SCALE" = "true" ] && [ -n "$SCALE_DISPLAY" ]; then
|
|
|
759
834
|
OUTPUT="${OUTPUT}${SCALE_DISPLAY}"
|
|
760
835
|
fi
|
|
761
836
|
|
|
837
|
+
# Add ultradeep audit progress (if enabled and active)
|
|
838
|
+
if [ "$SHOW_ULTRADEEP" = "true" ] && [ -n "$ULTRADEEP_DISPLAY" ]; then
|
|
839
|
+
[ -n "$OUTPUT" ] && OUTPUT="${OUTPUT}${SEP}"
|
|
840
|
+
OUTPUT="${OUTPUT}${ULTRADEEP_DISPLAY}"
|
|
841
|
+
fi
|
|
842
|
+
|
|
762
843
|
# Session health indicator (next to git branch)
|
|
763
844
|
if [ "$SHOW_SESSION" = "true" ]; then
|
|
764
845
|
SCRIPTS_DIR="$(dirname "$0")"
|
package/scripts/claude-tmux.sh
CHANGED
|
@@ -112,6 +112,7 @@ WINDOWS:
|
|
|
112
112
|
Alt+n/p Next/previous window
|
|
113
113
|
Alt+r Rename window
|
|
114
114
|
Alt+w Close window
|
|
115
|
+
Alt+W Batch close windows (picker)
|
|
115
116
|
Alt+t Reopen closed window
|
|
116
117
|
|
|
117
118
|
PANES:
|
|
@@ -121,9 +122,14 @@ PANES:
|
|
|
121
122
|
Alt+z Zoom/unzoom pane
|
|
122
123
|
Alt+x Close pane
|
|
123
124
|
|
|
125
|
+
FREEZE RECOVERY:
|
|
126
|
+
Alt+k Send Ctrl+C twice (soft unfreeze)
|
|
127
|
+
Alt+K Force kill Claude process (keeps tab)
|
|
128
|
+
Alt+R Respawn pane (fresh shell)
|
|
129
|
+
|
|
124
130
|
OTHER:
|
|
125
|
-
Alt+
|
|
126
|
-
Alt+
|
|
131
|
+
Alt+b Scroll mode (browse history)
|
|
132
|
+
Alt+h Show keybind help panel
|
|
127
133
|
EOF
|
|
128
134
|
exit 0
|
|
129
135
|
fi
|
|
@@ -186,19 +192,24 @@ build_tab_format() {
|
|
|
186
192
|
# ── Inactive tab: gray text ─────────────────────────────────────────────
|
|
187
193
|
# " I:" prefix = 4 visible chars (wide); " I:" = 3 chars (narrow)
|
|
188
194
|
# Width = prefix + pN(name) + 1(space)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
local
|
|
194
|
-
local
|
|
195
|
-
local
|
|
196
|
-
local
|
|
197
|
-
local
|
|
198
|
-
local
|
|
199
|
-
local
|
|
200
|
-
local
|
|
201
|
-
local
|
|
195
|
+
#
|
|
196
|
+
# Group color support: when @group_color is set on a window (e.g. by
|
|
197
|
+
# spawn-audit-sessions.js), show a colored dot prefix to visually
|
|
198
|
+
# identify grouped windows (ULTRADEEP audit tabs in main session).
|
|
199
|
+
local gc='#{?#{@group_color},#[fg=#{@group_color}]•,}'
|
|
200
|
+
local i0="${gc}"'#[fg=#8a8a8a] #I:#{p35:#{=35:window_name}} '
|
|
201
|
+
local i1="${gc}"'#[fg=#8a8a8a] #I:#{p22:#{=22:window_name}} '
|
|
202
|
+
local i2="${gc}"'#[fg=#8a8a8a] #I:#{p15:#{=15:window_name}} '
|
|
203
|
+
local i3="${gc}"'#[fg=#8a8a8a] #I:#{p12:#{=12:window_name}} '
|
|
204
|
+
local i4="${gc}"'#[fg=#8a8a8a] #I:#{p9:#{=9:window_name}} '
|
|
205
|
+
local i5="${gc}"'#[fg=#8a8a8a] #I:#{p7:#{=7:window_name}} '
|
|
206
|
+
local i6="${gc}"'#[fg=#8a8a8a] #I:#{p6:#{=6:window_name}} '
|
|
207
|
+
local i7="${gc}"'#[fg=#8a8a8a] #I:#{p5:#{=5:window_name}} '
|
|
208
|
+
local i8="${gc}"'#[fg=#8a8a8a] #I:#{p4:#{=4:window_name}} '
|
|
209
|
+
local i9="${gc}"'#[fg=#8a8a8a] #I:#{p3:#{=3:window_name}} '
|
|
210
|
+
local i10="${gc}"'#[fg=#8a8a8a] #I:#{p2:#{=2:window_name}} '
|
|
211
|
+
local i11="${gc}"'#[fg=#8a8a8a] #I:#{p1:#{=1:window_name}} '
|
|
212
|
+
local i12="${gc}"'#[fg=#8a8a8a] #I '
|
|
202
213
|
local i13='#[fg=#565a6e]#I'
|
|
203
214
|
|
|
204
215
|
# ── Tier selection: budget = width / windows ─────────────────────────────
|
|
@@ -241,6 +252,14 @@ configure_tmux_session() {
|
|
|
241
252
|
# Enable mouse support
|
|
242
253
|
tmux set-option -t "$target_session" mouse on
|
|
243
254
|
|
|
255
|
+
# Reduce escape-time from default 500ms to 10ms.
|
|
256
|
+
# The default causes two problems:
|
|
257
|
+
# 1) Arrow keys (which send \e[A etc.) get misinterpreted as Alt+[ if
|
|
258
|
+
# there's any delivery delay, accidentally triggering copy-mode.
|
|
259
|
+
# 2) Pressing Escape in Claude Code has a 500ms lag before tmux forwards it.
|
|
260
|
+
# 10ms is enough to detect genuine escape sequences on localhost/fast SSH.
|
|
261
|
+
tmux set-option -t "$target_session" escape-time 10
|
|
262
|
+
|
|
244
263
|
# Automatically renumber windows when one is closed (no gaps)
|
|
245
264
|
tmux set-option -t "$target_session" renumber-windows on
|
|
246
265
|
|
|
@@ -312,6 +331,9 @@ configure_tmux_session() {
|
|
|
312
331
|
# Alt+w to close current window (save state for Alt+T restore, then kill)
|
|
313
332
|
tmux bind-key -n M-w confirm-before -p "Close window? (y/n)" "run-shell '\"\$AGILEFLOW_SCRIPTS/tmux-save-closed-window.sh\"' ; kill-window"
|
|
314
333
|
|
|
334
|
+
# Alt+W (uppercase) to batch-close windows via multi-select picker
|
|
335
|
+
tmux bind-key -n M-W display-popup -E -w 60 -h 20 "\"$AGILEFLOW_SCRIPTS/tmux-close-windows.sh\""
|
|
336
|
+
|
|
315
337
|
# Alt+t to restore the most recently closed window (like Ctrl+Shift+T in browsers)
|
|
316
338
|
tmux bind-key -n M-t run-shell '"$AGILEFLOW_SCRIPTS/tmux-restore-window.sh"'
|
|
317
339
|
|
|
@@ -325,8 +347,39 @@ configure_tmux_session() {
|
|
|
325
347
|
# Alt+z to zoom/unzoom pane (fullscreen toggle)
|
|
326
348
|
tmux bind-key -n M-z resize-pane -Z
|
|
327
349
|
|
|
328
|
-
# Alt+
|
|
329
|
-
|
|
350
|
+
# Alt+b to enter copy mode (for scrolling / browsing history)
|
|
351
|
+
# NOTE: Do NOT use Alt+[ here — \e[ is the CSI prefix for arrow keys and
|
|
352
|
+
# function keys. Binding M-[ in the root table causes accidental copy-mode
|
|
353
|
+
# entry whenever an escape sequence is split by network latency, making the
|
|
354
|
+
# terminal appear to "lose focus" until Escape is pressed.
|
|
355
|
+
tmux bind-key -n M-b copy-mode
|
|
356
|
+
|
|
357
|
+
# Disable ALL command-prompt bindings in copy mode — they open a text input
|
|
358
|
+
# in the status bar which intercepts keystrokes and confuses the workflow.
|
|
359
|
+
# Navigation (arrows, scroll, mouse, PgUp/PgDn) and exit (q/Escape) still work.
|
|
360
|
+
#
|
|
361
|
+
# Emacs copy-mode: goto-line, search, jump-to-char, repeat-count
|
|
362
|
+
tmux unbind-key -T copy-mode g
|
|
363
|
+
tmux unbind-key -T copy-mode C-r
|
|
364
|
+
tmux unbind-key -T copy-mode C-s
|
|
365
|
+
tmux unbind-key -T copy-mode f
|
|
366
|
+
tmux unbind-key -T copy-mode F
|
|
367
|
+
tmux unbind-key -T copy-mode t
|
|
368
|
+
tmux unbind-key -T copy-mode T
|
|
369
|
+
for i in 1 2 3 4 5 6 7 8 9; do
|
|
370
|
+
tmux unbind-key -T copy-mode "M-$i"
|
|
371
|
+
done
|
|
372
|
+
# Vi copy-mode: goto-line, search, jump-to-char, repeat-count
|
|
373
|
+
tmux unbind-key -T copy-mode-vi :
|
|
374
|
+
tmux unbind-key -T copy-mode-vi /
|
|
375
|
+
tmux unbind-key -T copy-mode-vi ?
|
|
376
|
+
tmux unbind-key -T copy-mode-vi f
|
|
377
|
+
tmux unbind-key -T copy-mode-vi F
|
|
378
|
+
tmux unbind-key -T copy-mode-vi t
|
|
379
|
+
tmux unbind-key -T copy-mode-vi T
|
|
380
|
+
for i in 1 2 3 4 5 6 7 8 9; do
|
|
381
|
+
tmux unbind-key -T copy-mode-vi "$i"
|
|
382
|
+
done
|
|
330
383
|
|
|
331
384
|
# ─── Session Creation Keybindings ──────────────────────────────────────────
|
|
332
385
|
# Alt+s to create a new Claude window (starts fresh, future re-runs in same pane resume)
|
|
@@ -335,11 +388,31 @@ configure_tmux_session() {
|
|
|
335
388
|
|
|
336
389
|
# ─── Freeze Recovery Keybindings ───────────────────────────────────────────
|
|
337
390
|
# Alt+k to send Ctrl+C twice (soft interrupt for frozen processes)
|
|
338
|
-
|
|
391
|
+
# Uses #{pane_id} to target the correct pane even if focus shifts during the sleep
|
|
392
|
+
tmux bind-key -n M-k run-shell "tmux send-keys -t '#{pane_id}' C-c; sleep 0.5; tmux send-keys -t '#{pane_id}' C-c"
|
|
393
|
+
|
|
394
|
+
# Alt+K (uppercase) to force kill Claude process in pane without closing tab
|
|
395
|
+
# Finds the claude child process under the pane's shell and kills it,
|
|
396
|
+
# leaving the shell prompt intact so the user can restart or continue.
|
|
397
|
+
# Uses ps -o pid=,args= with grep to find claude, then cuts the PID (avoids awk quoting issues in tmux run-shell).
|
|
398
|
+
tmux bind-key -n M-K run-shell '\
|
|
399
|
+
PANE_PID=$(tmux display-message -p "#{pane_pid}"); \
|
|
400
|
+
CLAUDE_PID=$(ps --ppid "$PANE_PID" -o pid=,args= 2>/dev/null | grep -v "watchdog" | grep "claude" | head -1 | sed "s/^[[:space:]]*//" | cut -d" " -f1); \
|
|
401
|
+
if [ -n "$CLAUDE_PID" ]; then \
|
|
402
|
+
kill -TERM "$CLAUDE_PID" 2>/dev/null; \
|
|
403
|
+
sleep 2; \
|
|
404
|
+
kill -0 "$CLAUDE_PID" 2>/dev/null && kill -KILL "$CLAUDE_PID" 2>/dev/null; \
|
|
405
|
+
tmux display-message "Killed Claude process (PID $CLAUDE_PID)"; \
|
|
406
|
+
else \
|
|
407
|
+
tmux display-message "No Claude process found in this pane"; \
|
|
408
|
+
fi'
|
|
409
|
+
|
|
410
|
+
# Alt+R to respawn pane (kills everything in the pane, starts fresh shell)
|
|
411
|
+
tmux bind-key -n M-R respawn-pane -k
|
|
339
412
|
|
|
340
413
|
# ─── Help Panel ──────────────────────────────────────────────────────────
|
|
341
414
|
# Alt+h to show all Alt keybindings in a popup
|
|
342
|
-
tmux bind-key -n M-h display-popup -E -w 52 -h
|
|
415
|
+
tmux bind-key -n M-h display-popup -E -w 52 -h 32 "\
|
|
343
416
|
printf '\\n';\
|
|
344
417
|
printf ' \\033[1;38;5;208mSESSIONS\\033[0m\\n';\
|
|
345
418
|
printf ' Alt+s New Claude session\\n';\
|
|
@@ -352,6 +425,7 @@ configure_tmux_session() {
|
|
|
352
425
|
printf ' Alt+n/p Next / previous window\\n';\
|
|
353
426
|
printf ' Alt+r Rename window\\n';\
|
|
354
427
|
printf ' Alt+w Close window\\n';\
|
|
428
|
+
printf ' Alt+W Batch close windows\\n';\
|
|
355
429
|
printf ' Alt+t Reopen closed window\\n';\
|
|
356
430
|
printf '\\n';\
|
|
357
431
|
printf ' \\033[1;38;5;208mPANES\\033[0m\\n';\
|
|
@@ -360,11 +434,11 @@ configure_tmux_session() {
|
|
|
360
434
|
printf ' Alt+arrows Navigate panes\\n';\
|
|
361
435
|
printf ' Alt+z Zoom / unzoom\\n';\
|
|
362
436
|
printf ' Alt+x Close pane (confirm)\\n';\
|
|
363
|
-
printf ' Alt+K
|
|
364
|
-
printf ' Alt+R
|
|
437
|
+
printf ' Alt+K Force kill process\\n';\
|
|
438
|
+
printf ' Alt+R Respawn pane (fresh)\\n';\
|
|
365
439
|
printf '\\n';\
|
|
366
440
|
printf ' \\033[1;38;5;208mOTHER\\033[0m\\n';\
|
|
367
|
-
printf ' Alt+
|
|
441
|
+
printf ' Alt+b Scroll mode\\n';\
|
|
368
442
|
printf ' Alt+k Unfreeze (Ctrl+C x2)\\n';\
|
|
369
443
|
printf ' Alt+h This help\\n';\
|
|
370
444
|
printf '\\n';\
|
|
@@ -378,6 +452,14 @@ if [ "$REFRESH_CONFIG" = true ]; then
|
|
|
378
452
|
configure_tmux_session "$sid"
|
|
379
453
|
# Ensure AGILEFLOW_SCRIPTS is set (needed by Alt+S keybind)
|
|
380
454
|
tmux set-environment -t "$sid" AGILEFLOW_SCRIPTS "$SCRIPT_DIR" 2>/dev/null || true
|
|
455
|
+
# (Re)start watchdog if not running for this session
|
|
456
|
+
_EXISTING_WD=$(tmux show-environment -t "$sid" WATCHDOG_PID 2>/dev/null | cut -d= -f2)
|
|
457
|
+
if [ -z "$_EXISTING_WD" ] || ! kill -0 "$_EXISTING_WD" 2>/dev/null; then
|
|
458
|
+
"$SCRIPT_DIR/claude-watchdog.sh" "$sid" &
|
|
459
|
+
_WD_PID=$!
|
|
460
|
+
tmux set-environment -t "$sid" WATCHDOG_PID "$_WD_PID"
|
|
461
|
+
disown "$_WD_PID"
|
|
462
|
+
fi
|
|
381
463
|
REFRESHED=$((REFRESHED + 1))
|
|
382
464
|
done
|
|
383
465
|
if [ "$REFRESHED" -gt 0 ]; then
|
|
@@ -650,6 +732,15 @@ if [ -n "$CLAUDE_SESSION_FLAGS" ]; then
|
|
|
650
732
|
tmux set-environment -t "$SESSION_NAME" CLAUDE_SESSION_FLAGS "$CLAUDE_SESSION_FLAGS"
|
|
651
733
|
fi
|
|
652
734
|
|
|
735
|
+
# Start watchdog to auto-detect and kill frozen Claude processes
|
|
736
|
+
EXISTING_WD=$(tmux show-environment -t "$SESSION_NAME" WATCHDOG_PID 2>/dev/null | cut -d= -f2)
|
|
737
|
+
if [ -z "$EXISTING_WD" ] || ! kill -0 "$EXISTING_WD" 2>/dev/null; then
|
|
738
|
+
"$SCRIPT_DIR/claude-watchdog.sh" "$SESSION_NAME" &
|
|
739
|
+
_WD_PID=$!
|
|
740
|
+
tmux set-environment -t "$SESSION_NAME" WATCHDOG_PID "$_WD_PID"
|
|
741
|
+
disown "$_WD_PID"
|
|
742
|
+
fi
|
|
743
|
+
|
|
653
744
|
# Pre-seed @claude_uuid on initial pane if we found a recent conversation
|
|
654
745
|
if [ "$USE_RESUME" = true ] && [ -n "$RESUME_SESSION_ID" ]; then
|
|
655
746
|
tmux set-option -p -t "$SESSION_NAME" @claude_uuid "$RESUME_SESSION_ID" 2>/dev/null || true
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# claude-watchdog.sh - Background watchdog that detects and kills frozen Claude processes
|
|
3
|
+
#
|
|
4
|
+
# Usage: claude-watchdog.sh <session-name>
|
|
5
|
+
#
|
|
6
|
+
# Runs in the background, checking every WATCHDOG_INTERVAL seconds for Claude
|
|
7
|
+
# processes that appear frozen. A process is considered frozen when ALL of:
|
|
8
|
+
# 1. Running for > WATCHDOG_MAX_AGE_HOURS hours
|
|
9
|
+
# 2. Using > WATCHDOG_MAX_MEMORY_MB megabytes of RSS
|
|
10
|
+
# 3. No JSONL file modification in > WATCHDOG_MAX_IDLE_MINUTES minutes
|
|
11
|
+
#
|
|
12
|
+
# Recovery sequence: Ctrl+C x2 (5s wait) → SIGTERM (5s wait) → SIGKILL
|
|
13
|
+
# After kill, the pane stays open with a shell prompt (tab is NOT closed).
|
|
14
|
+
#
|
|
15
|
+
# Logs kills to ~/.claude/watchdog.log
|
|
16
|
+
#
|
|
17
|
+
# Environment variable configuration:
|
|
18
|
+
# WATCHDOG_INTERVAL Check interval in seconds (default: 120)
|
|
19
|
+
# WATCHDOG_MAX_AGE_HOURS Minimum process age to consider (default: 6)
|
|
20
|
+
# WATCHDOG_MAX_MEMORY_MB Minimum RSS in MB to consider (default: 400)
|
|
21
|
+
# WATCHDOG_MAX_IDLE_MINUTES Minutes since last JSONL write (default: 60)
|
|
22
|
+
#
|
|
23
|
+
# NOTE: Linux-only (requires /proc filesystem and GNU ps). macOS not supported.
|
|
24
|
+
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
SESSION_NAME="${1:-}"
|
|
28
|
+
if [ -z "$SESSION_NAME" ]; then
|
|
29
|
+
echo "Usage: claude-watchdog.sh <session-name>" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Configuration (all overridable via environment)
|
|
34
|
+
INTERVAL="${WATCHDOG_INTERVAL:-120}"
|
|
35
|
+
MAX_AGE_HOURS="${WATCHDOG_MAX_AGE_HOURS:-6}"
|
|
36
|
+
MAX_MEMORY_MB="${WATCHDOG_MAX_MEMORY_MB:-400}"
|
|
37
|
+
MAX_IDLE_MINUTES="${WATCHDOG_MAX_IDLE_MINUTES:-60}"
|
|
38
|
+
|
|
39
|
+
# Validate numeric configuration
|
|
40
|
+
for _var_name in INTERVAL MAX_AGE_HOURS MAX_MEMORY_MB MAX_IDLE_MINUTES; do
|
|
41
|
+
_var_val="${!_var_name}"
|
|
42
|
+
if ! [[ "$_var_val" =~ ^[0-9]+$ ]]; then
|
|
43
|
+
echo "ERROR: $_var_name must be a positive integer, got: '$_var_val'" >&2
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
LOG_FILE="$HOME/.claude/watchdog.log"
|
|
49
|
+
mkdir -p "$(dirname "$LOG_FILE")"
|
|
50
|
+
|
|
51
|
+
log() {
|
|
52
|
+
local timestamp
|
|
53
|
+
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
54
|
+
echo "[$timestamp] [watchdog:$SESSION_NAME] $*" >> "$LOG_FILE"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
log "Started (interval=${INTERVAL}s, max_age=${MAX_AGE_HOURS}h, max_mem=${MAX_MEMORY_MB}MB, max_idle=${MAX_IDLE_MINUTES}m)"
|
|
58
|
+
|
|
59
|
+
# Clean exit when session is destroyed
|
|
60
|
+
cleanup() {
|
|
61
|
+
log "Stopping (session gone or signal received)" || true
|
|
62
|
+
exit 0
|
|
63
|
+
}
|
|
64
|
+
trap cleanup INT TERM
|
|
65
|
+
|
|
66
|
+
# Check if a process's JSONL files have been modified recently
|
|
67
|
+
# Returns 0 if idle (no recent writes), 1 if active
|
|
68
|
+
is_process_idle() {
|
|
69
|
+
local pid="$1"
|
|
70
|
+
local idle_minutes="$2"
|
|
71
|
+
|
|
72
|
+
# Find the working directory of the process to locate .claude/ project dirs
|
|
73
|
+
local cwd
|
|
74
|
+
cwd=$(readlink "/proc/$pid/cwd" 2>/dev/null) || return 1
|
|
75
|
+
|
|
76
|
+
# Build the project path Claude Code uses for JSONL session files
|
|
77
|
+
local proj_dir
|
|
78
|
+
proj_dir=$(echo "$cwd" | sed 's|/|-|g' | sed 's|^-||')
|
|
79
|
+
local sessions_dir="$HOME/.claude/projects/-$proj_dir"
|
|
80
|
+
|
|
81
|
+
if [ ! -d "$sessions_dir" ]; then
|
|
82
|
+
# No session dir means no activity tracking — assume idle
|
|
83
|
+
return 0
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Check if any JSONL file was modified within idle_minutes
|
|
87
|
+
local recent_file
|
|
88
|
+
recent_file=$(find "$sessions_dir" -name '*.jsonl' -mmin "-$idle_minutes" -print -quit 2>/dev/null)
|
|
89
|
+
|
|
90
|
+
if [ -n "$recent_file" ]; then
|
|
91
|
+
# Recently modified — process is active
|
|
92
|
+
return 1
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# No recent modifications — process is idle
|
|
96
|
+
return 0
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Get process RSS in MB (Linux /proc-based, ps fallback)
|
|
100
|
+
get_rss_mb() {
|
|
101
|
+
local pid="$1"
|
|
102
|
+
local rss_kb
|
|
103
|
+
if [ -f "/proc/$pid/status" ]; then
|
|
104
|
+
rss_kb=$(awk '/^VmRSS:/ { print $2 }' "/proc/$pid/status" 2>/dev/null) || return 1
|
|
105
|
+
[ -z "$rss_kb" ] && return 1 # process died between stat and read
|
|
106
|
+
echo $(( rss_kb / 1024 ))
|
|
107
|
+
else
|
|
108
|
+
rss_kb=$(ps -o rss= -p "$pid" 2>/dev/null | tr -d ' ') || return 1
|
|
109
|
+
[ -z "$rss_kb" ] && return 1
|
|
110
|
+
echo $(( rss_kb / 1024 ))
|
|
111
|
+
fi
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Get process age in hours
|
|
115
|
+
get_age_hours() {
|
|
116
|
+
local pid="$1"
|
|
117
|
+
local elapsed_seconds
|
|
118
|
+
elapsed_seconds=$(ps -o etimes= -p "$pid" 2>/dev/null | tr -d ' ') || return 1
|
|
119
|
+
[ -z "$elapsed_seconds" ] && return 1
|
|
120
|
+
echo $(( elapsed_seconds / 3600 ))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# Gracefully kill a frozen process with escalating signals
|
|
124
|
+
kill_frozen_process() {
|
|
125
|
+
local pid="$1"
|
|
126
|
+
local pane_id="$2"
|
|
127
|
+
local age_h="$3"
|
|
128
|
+
local mem_mb="$4"
|
|
129
|
+
|
|
130
|
+
log "FROZEN DETECTED: PID=$pid pane=$pane_id age=${age_h}h mem=${mem_mb}MB"
|
|
131
|
+
log "Recovery: sending Ctrl+C x2 to pane $pane_id"
|
|
132
|
+
|
|
133
|
+
# Step 1: Try Ctrl+C twice via tmux (gentlest approach)
|
|
134
|
+
tmux send-keys -t "$pane_id" C-c 2>/dev/null || true
|
|
135
|
+
sleep 1
|
|
136
|
+
tmux send-keys -t "$pane_id" C-c 2>/dev/null || true
|
|
137
|
+
sleep 4
|
|
138
|
+
|
|
139
|
+
# Check if process is still alive
|
|
140
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
141
|
+
log "Process $pid terminated after Ctrl+C"
|
|
142
|
+
return 0
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
# Step 2: SIGTERM
|
|
146
|
+
log "Recovery: sending SIGTERM to PID $pid"
|
|
147
|
+
kill -TERM "$pid" 2>/dev/null || true
|
|
148
|
+
sleep 5
|
|
149
|
+
|
|
150
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
151
|
+
log "Process $pid terminated after SIGTERM"
|
|
152
|
+
return 0
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# Step 3: SIGKILL (last resort)
|
|
156
|
+
log "Recovery: sending SIGKILL to PID $pid"
|
|
157
|
+
kill -KILL "$pid" 2>/dev/null || true
|
|
158
|
+
sleep 1
|
|
159
|
+
|
|
160
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
161
|
+
log "Process $pid terminated after SIGKILL"
|
|
162
|
+
else
|
|
163
|
+
log "WARNING: Process $pid survived SIGKILL — zombie or kernel issue"
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
return 0
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
# Main check loop
|
|
170
|
+
while true; do
|
|
171
|
+
# Exit if session no longer exists
|
|
172
|
+
if ! tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
|
173
|
+
log "Session '$SESSION_NAME' no longer exists"
|
|
174
|
+
break
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
# Iterate over all panes in the session
|
|
178
|
+
while IFS= read -r pane_info; do
|
|
179
|
+
[ -z "$pane_info" ] && continue
|
|
180
|
+
|
|
181
|
+
pane_id="${pane_info%% *}"
|
|
182
|
+
pane_pid="${pane_info#* }"
|
|
183
|
+
|
|
184
|
+
# Find claude child processes under this pane's shell
|
|
185
|
+
# Look for processes whose command contains "claude" under the pane PID
|
|
186
|
+
while IFS= read -r child_line; do
|
|
187
|
+
[ -z "$child_line" ] && continue
|
|
188
|
+
|
|
189
|
+
child_pid="${child_line%% *}"
|
|
190
|
+
child_cmd="${child_line#* }"
|
|
191
|
+
|
|
192
|
+
# Skip non-claude processes and skip ourselves
|
|
193
|
+
case "$child_cmd" in
|
|
194
|
+
*claude-watchdog*|*watchdog*) continue ;;
|
|
195
|
+
*claude*) ;; # This is a Claude process — check it
|
|
196
|
+
*) continue ;;
|
|
197
|
+
esac
|
|
198
|
+
|
|
199
|
+
# Criterion 1: Age check
|
|
200
|
+
age_h=$(get_age_hours "$child_pid" 2>/dev/null) || continue
|
|
201
|
+
if [ "$age_h" -lt "$MAX_AGE_HOURS" ]; then
|
|
202
|
+
continue
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
# Criterion 2: Memory check
|
|
206
|
+
mem_mb=$(get_rss_mb "$child_pid" 2>/dev/null) || continue
|
|
207
|
+
if [ "$mem_mb" -lt "$MAX_MEMORY_MB" ]; then
|
|
208
|
+
continue
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# Criterion 3: Idle check (most important — prevents killing active work)
|
|
212
|
+
if ! is_process_idle "$child_pid" "$MAX_IDLE_MINUTES"; then
|
|
213
|
+
# Process has recent JSONL activity — NOT frozen, just long-running
|
|
214
|
+
continue
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
# All three criteria met — this process is frozen
|
|
218
|
+
kill_frozen_process "$child_pid" "$pane_id" "$age_h" "$mem_mb"
|
|
219
|
+
|
|
220
|
+
done < <(ps --ppid "$pane_pid" -o pid=,args= 2>/dev/null || true)
|
|
221
|
+
|
|
222
|
+
done < <(tmux list-panes -t "$SESSION_NAME" -F '#{pane_id} #{pane_pid}' 2>/dev/null || true)
|
|
223
|
+
|
|
224
|
+
sleep "$INTERVAL"
|
|
225
|
+
done
|
|
@@ -15,6 +15,19 @@ const path = require('path');
|
|
|
15
15
|
const { extractFrontmatter, normalizeTools } = require('../lib/frontmatter-parser');
|
|
16
16
|
const { getCached, setCached } = require('../../lib/registry-cache');
|
|
17
17
|
|
|
18
|
+
// Lazy-load model-profiles to avoid circular deps and missing-module crashes
|
|
19
|
+
let _resolveModel;
|
|
20
|
+
function getResolveModel() {
|
|
21
|
+
if (_resolveModel === undefined) {
|
|
22
|
+
try {
|
|
23
|
+
_resolveModel = require('../lib/model-profiles').resolveModel;
|
|
24
|
+
} catch (_) {
|
|
25
|
+
_resolveModel = null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return _resolveModel;
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
// Debug mode: set DEBUG_REGISTRY=1 to see why files are skipped
|
|
19
32
|
const DEBUG = process.env.DEBUG_REGISTRY === '1';
|
|
20
33
|
|
|
@@ -110,7 +123,7 @@ async function scanAgents(agentsDir) {
|
|
|
110
123
|
displayName: frontmatter.name || name,
|
|
111
124
|
description: frontmatter.description || '',
|
|
112
125
|
tools,
|
|
113
|
-
model:
|
|
126
|
+
model: (getResolveModel() || ((_, fm) => fm || 'haiku'))(null, frontmatter.model),
|
|
114
127
|
color: frontmatter.color || 'blue',
|
|
115
128
|
category: categorizeAgent(name, frontmatter.description || ''),
|
|
116
129
|
});
|