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.
Files changed (121) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +6 -6
  3. package/lib/skill-loader.js +0 -1
  4. package/package.json +1 -1
  5. package/scripts/agileflow-statusline.sh +81 -0
  6. package/scripts/claude-tmux.sh +113 -22
  7. package/scripts/claude-watchdog.sh +225 -0
  8. package/scripts/generators/agent-registry.js +14 -1
  9. package/scripts/generators/inject-babysit.js +22 -9
  10. package/scripts/generators/inject-help.js +19 -9
  11. package/scripts/lib/audit-cleanup.js +250 -0
  12. package/scripts/lib/audit-registry.js +248 -0
  13. package/scripts/lib/feature-catalog.js +3 -3
  14. package/scripts/lib/gate-enforcer.js +295 -0
  15. package/scripts/lib/model-profiles.js +98 -0
  16. package/scripts/lib/signal-detectors.js +1 -1
  17. package/scripts/lib/skill-catalog.js +557 -0
  18. package/scripts/lib/skill-recommender.js +311 -0
  19. package/scripts/lib/tdd-phase-manager.js +455 -0
  20. package/scripts/lib/team-events.js +34 -3
  21. package/scripts/lib/tmux-group-colors.js +113 -0
  22. package/scripts/messaging-bridge.js +209 -1
  23. package/scripts/spawn-audit-sessions.js +549 -0
  24. package/scripts/team-manager.js +37 -16
  25. package/scripts/tmux-close-windows.sh +180 -0
  26. package/src/core/agents/ads-audit-budget.md +181 -0
  27. package/src/core/agents/ads-audit-compliance.md +169 -0
  28. package/src/core/agents/ads-audit-creative.md +164 -0
  29. package/src/core/agents/ads-audit-google.md +226 -0
  30. package/src/core/agents/ads-audit-meta.md +183 -0
  31. package/src/core/agents/ads-audit-tracking.md +197 -0
  32. package/src/core/agents/ads-consensus.md +322 -0
  33. package/src/core/agents/brainstorm-analyzer-features.md +169 -0
  34. package/src/core/agents/brainstorm-analyzer-growth.md +161 -0
  35. package/src/core/agents/brainstorm-analyzer-integration.md +172 -0
  36. package/src/core/agents/brainstorm-analyzer-market.md +147 -0
  37. package/src/core/agents/brainstorm-analyzer-ux.md +167 -0
  38. package/src/core/agents/brainstorm-consensus.md +237 -0
  39. package/src/core/agents/completeness-consensus.md +5 -5
  40. package/src/core/agents/perf-consensus.md +2 -2
  41. package/src/core/agents/security-consensus.md +2 -2
  42. package/src/core/agents/seo-analyzer-content.md +167 -0
  43. package/src/core/agents/seo-analyzer-images.md +187 -0
  44. package/src/core/agents/seo-analyzer-performance.md +206 -0
  45. package/src/core/agents/seo-analyzer-schema.md +176 -0
  46. package/src/core/agents/seo-analyzer-sitemap.md +172 -0
  47. package/src/core/agents/seo-analyzer-technical.md +144 -0
  48. package/src/core/agents/seo-consensus.md +289 -0
  49. package/src/core/agents/test-consensus.md +2 -2
  50. package/src/core/commands/ads/audit.md +375 -0
  51. package/src/core/commands/ads/budget.md +97 -0
  52. package/src/core/commands/ads/competitor.md +112 -0
  53. package/src/core/commands/ads/creative.md +85 -0
  54. package/src/core/commands/ads/google.md +112 -0
  55. package/src/core/commands/ads/landing.md +119 -0
  56. package/src/core/commands/ads/linkedin.md +112 -0
  57. package/src/core/commands/ads/meta.md +91 -0
  58. package/src/core/commands/ads/microsoft.md +115 -0
  59. package/src/core/commands/ads/plan.md +321 -0
  60. package/src/core/commands/ads/tiktok.md +129 -0
  61. package/src/core/commands/ads/youtube.md +124 -0
  62. package/src/core/commands/ads.md +128 -0
  63. package/src/core/commands/babysit.md +249 -1284
  64. package/src/core/commands/{audit → code}/completeness.md +35 -25
  65. package/src/core/commands/{audit → code}/legal.md +26 -16
  66. package/src/core/commands/{audit → code}/logic.md +27 -16
  67. package/src/core/commands/{audit → code}/performance.md +30 -20
  68. package/src/core/commands/{audit → code}/security.md +32 -19
  69. package/src/core/commands/{audit → code}/test.md +30 -20
  70. package/src/core/commands/{discovery → ideate}/brief.md +12 -12
  71. package/src/core/commands/{discovery/new.md → ideate/discover.md} +13 -13
  72. package/src/core/commands/ideate/features.md +435 -0
  73. package/src/core/commands/seo/audit.md +373 -0
  74. package/src/core/commands/seo/competitor.md +174 -0
  75. package/src/core/commands/seo/content.md +107 -0
  76. package/src/core/commands/seo/geo.md +229 -0
  77. package/src/core/commands/seo/hreflang.md +140 -0
  78. package/src/core/commands/seo/images.md +96 -0
  79. package/src/core/commands/seo/page.md +198 -0
  80. package/src/core/commands/seo/plan.md +163 -0
  81. package/src/core/commands/seo/programmatic.md +131 -0
  82. package/src/core/commands/seo/references/cwv-thresholds.md +64 -0
  83. package/src/core/commands/seo/references/eeat-framework.md +110 -0
  84. package/src/core/commands/seo/references/quality-gates.md +91 -0
  85. package/src/core/commands/seo/references/schema-types.md +102 -0
  86. package/src/core/commands/seo/schema.md +183 -0
  87. package/src/core/commands/seo/sitemap.md +97 -0
  88. package/src/core/commands/seo/technical.md +100 -0
  89. package/src/core/commands/seo.md +107 -0
  90. package/src/core/commands/skill/list.md +68 -212
  91. package/src/core/commands/skill/recommend.md +216 -0
  92. package/src/core/commands/tdd-next.md +238 -0
  93. package/src/core/commands/tdd.md +210 -0
  94. package/src/core/experts/_core-expertise.yaml +105 -0
  95. package/src/core/experts/analytics/expertise.yaml +5 -99
  96. package/src/core/experts/codebase-query/expertise.yaml +3 -72
  97. package/src/core/experts/compliance/expertise.yaml +6 -72
  98. package/src/core/experts/database/expertise.yaml +9 -52
  99. package/src/core/experts/documentation/expertise.yaml +7 -140
  100. package/src/core/experts/integrations/expertise.yaml +7 -127
  101. package/src/core/experts/mentor/expertise.yaml +8 -35
  102. package/src/core/experts/monitoring/expertise.yaml +7 -49
  103. package/src/core/experts/performance/expertise.yaml +1 -26
  104. package/src/core/experts/security/expertise.yaml +9 -34
  105. package/src/core/experts/ui/expertise.yaml +6 -36
  106. package/src/core/knowledge/ads/ad-audit-checklist-scoring.md +424 -0
  107. package/src/core/knowledge/ads/ad-optimization-logic.md +590 -0
  108. package/src/core/knowledge/ads/ad-technical-specifications.md +385 -0
  109. package/src/core/knowledge/ads/definitive-advertising-reference-2026.md +506 -0
  110. package/src/core/knowledge/ads/paid-advertising-research-2026.md +445 -0
  111. package/src/core/templates/agileflow-metadata.json +15 -1
  112. package/tools/cli/installers/ide/_base-ide.js +42 -5
  113. package/tools/cli/installers/ide/claude-code.js +3 -3
  114. package/tools/cli/lib/content-injector.js +160 -12
  115. package/tools/cli/lib/docs-setup.js +1 -1
  116. package/src/core/commands/skill/create.md +0 -698
  117. package/src/core/commands/skill/delete.md +0 -316
  118. package/src/core/commands/skill/edit.md +0 -359
  119. package/src/core/commands/skill/test.md +0 -394
  120. package/src/core/commands/skill/upgrade.md +0 -552
  121. 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
  [![npm version](https://img.shields.io/npm/v/agileflow?color=brightgreen)](https://www.npmjs.com/package/agileflow)
6
- [![Commands](https://img.shields.io/badge/commands-99-blue)](https://docs.agileflow.projectquestorg.com/docs/commands)
7
- [![Agents/Experts](https://img.shields.io/badge/agents%2Fexperts-91-orange)](https://docs.agileflow.projectquestorg.com/docs/agents)
6
+ [![Commands](https://img.shields.io/badge/commands-124-blue)](https://docs.agileflow.projectquestorg.com/docs/commands)
7
+ [![Agents/Experts](https://img.shields.io/badge/agents%2Fexperts-111-orange)](https://docs.agileflow.projectquestorg.com/docs/agents)
8
8
  [![Skills](https://img.shields.io/badge/skills-dynamic-purple)](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) | 99 | Slash commands for agile workflows |
58
- | [Agents/Experts](https://docs.agileflow.projectquestorg.com/docs/agents) | 91 | Specialized agents with self-improving knowledge bases |
59
- | [Skills](https://docs.agileflow.projectquestorg.com/docs/features/skills) | Dynamic | Generated on-demand with `/agileflow:skill:create` |
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 | Custom AI prompts that learn from your feedback | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/skills) |
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) |
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
@@ -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")"
@@ -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+[ Scroll mode
126
- Alt+k Send Ctrl+C twice (unfreeze)
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
- local i0='#[fg=#8a8a8a] #I:#{p35:#{=35:window_name}} '
190
- local i1='#[fg=#8a8a8a] #I:#{p22:#{=22:window_name}} '
191
- local i2='#[fg=#8a8a8a] #I:#{p15:#{=15:window_name}} '
192
- local i3='#[fg=#8a8a8a] #I:#{p12:#{=12:window_name}} '
193
- local i4='#[fg=#8a8a8a] #I:#{p9:#{=9:window_name}} '
194
- local i5='#[fg=#8a8a8a] #I:#{p7:#{=7:window_name}} '
195
- local i6='#[fg=#8a8a8a] #I:#{p6:#{=6:window_name}} '
196
- local i7='#[fg=#8a8a8a] #I:#{p5:#{=5:window_name}} '
197
- local i8='#[fg=#8a8a8a] #I:#{p4:#{=4:window_name}} '
198
- local i9='#[fg=#8a8a8a] #I:#{p3:#{=3:window_name}} '
199
- local i10='#[fg=#8a8a8a] #I:#{p2:#{=2:window_name}} '
200
- local i11='#[fg=#8a8a8a] #I:#{p1:#{=1:window_name}} '
201
- local i12='#[fg=#8a8a8a] #I '
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+[ to enter copy mode (for scrolling)
329
- tmux bind-key -n M-[ copy-mode
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
- tmux bind-key -n M-k run-shell "tmux send-keys C-c; sleep 0.5; tmux send-keys C-c"
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 31 "\
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 Kill pane (no confirm)\\n';\
364
- printf ' Alt+R Restart pane\\n';\
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+[ Scroll mode\\n';\
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: frontmatter.model || 'haiku',
126
+ model: (getResolveModel() || ((_, fm) => fm || 'haiku'))(null, frontmatter.model),
114
127
  color: frontmatter.color || 'blue',
115
128
  category: categorizeAgent(name, frontmatter.description || ''),
116
129
  });