agileflow 2.75.0 → 2.77.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "2.75.0",
3
+ "version": "2.77.0",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
@@ -23,7 +23,7 @@
23
23
  * --detect Show current status
24
24
  * --help Show help
25
25
  *
26
- * Features: sessionstart, precompact, archival, statusline, autoupdate
26
+ * Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, autoupdate
27
27
  */
28
28
 
29
29
  const fs = require('fs');
@@ -71,7 +71,8 @@ const VERSION = getVersion();
71
71
  const FEATURES = {
72
72
  sessionstart: { hook: 'SessionStart', script: 'agileflow-welcome.js', type: 'node' },
73
73
  precompact: { hook: 'PreCompact', script: 'precompact-context.sh', type: 'bash' },
74
- // Note: Stop hook removed due to Claude Code reliability issues (see GitHub issues #6974, #11544)
74
+ ralphloop: { hook: 'Stop', script: 'ralph-loop.js', type: 'node' },
75
+ selfimprove: { hook: 'Stop', script: 'auto-self-improve.js', type: 'node' },
75
76
  archival: { script: 'archive-completed-stories.sh', requiresHook: 'sessionstart' },
76
77
  statusline: { script: 'agileflow-statusline.sh' },
77
78
  autoupdate: { metadataOnly: true }, // Stored in metadata.updates.autoUpdate
@@ -82,6 +83,8 @@ const ALL_SCRIPTS = {
82
83
  // Core feature scripts (linked to FEATURES)
83
84
  'agileflow-welcome.js': { feature: 'sessionstart', required: true },
84
85
  'precompact-context.sh': { feature: 'precompact', required: true },
86
+ 'ralph-loop.js': { feature: 'ralphloop', required: true },
87
+ 'auto-self-improve.js': { feature: 'selfimprove', required: true },
85
88
  'archive-completed-stories.sh': { feature: 'archival', required: true },
86
89
  'agileflow-statusline.sh': { feature: 'statusline', required: true },
87
90
 
@@ -119,25 +122,25 @@ const STATUSLINE_COMPONENTS = [
119
122
 
120
123
  const PROFILES = {
121
124
  full: {
122
- description: 'All features enabled',
123
- enable: ['sessionstart', 'precompact', 'archival', 'statusline'],
125
+ description: 'All features enabled (including experimental Stop hooks)',
126
+ enable: ['sessionstart', 'precompact', 'archival', 'statusline', 'ralphloop', 'selfimprove'],
124
127
  archivalDays: 30,
125
128
  },
126
129
  basic: {
127
130
  description: 'Essential hooks + archival (SessionStart + PreCompact + Archival)',
128
131
  enable: ['sessionstart', 'precompact', 'archival'],
129
- disable: ['statusline'],
132
+ disable: ['statusline', 'ralphloop', 'selfimprove'],
130
133
  archivalDays: 30,
131
134
  },
132
135
  minimal: {
133
136
  description: 'SessionStart + archival only',
134
137
  enable: ['sessionstart', 'archival'],
135
- disable: ['precompact', 'statusline'],
138
+ disable: ['precompact', 'statusline', 'ralphloop', 'selfimprove'],
136
139
  archivalDays: 30,
137
140
  },
138
141
  none: {
139
142
  description: 'Disable all AgileFlow features',
140
- disable: ['sessionstart', 'precompact', 'archival', 'statusline'],
143
+ disable: ['sessionstart', 'precompact', 'archival', 'statusline', 'ralphloop', 'selfimprove'],
141
144
  },
142
145
  };
143
146
 
@@ -208,6 +211,8 @@ function detectConfig() {
208
211
  features: {
209
212
  sessionstart: { enabled: false, valid: true, issues: [], version: null, outdated: false },
210
213
  precompact: { enabled: false, valid: true, issues: [], version: null, outdated: false },
214
+ ralphloop: { enabled: false, valid: true, issues: [], version: null, outdated: false },
215
+ selfimprove: { enabled: false, valid: true, issues: [], version: null, outdated: false },
211
216
  archival: { enabled: false, threshold: null, version: null, outdated: false },
212
217
  statusline: { enabled: false, valid: true, issues: [], version: null, outdated: false },
213
218
  },
@@ -276,7 +281,23 @@ function detectConfig() {
276
281
  }
277
282
  }
278
283
 
279
- // Note: Stop hook removed due to reliability issues
284
+ // Stop hooks (ralphloop and selfimprove)
285
+ if (settings.hooks.Stop) {
286
+ if (Array.isArray(settings.hooks.Stop) && settings.hooks.Stop.length > 0) {
287
+ const hook = settings.hooks.Stop[0];
288
+ if (hook.matcher !== undefined && hook.hooks) {
289
+ // Check for each Stop hook feature
290
+ for (const h of hook.hooks) {
291
+ if (h.command?.includes('ralph-loop')) {
292
+ status.features.ralphloop.enabled = true;
293
+ }
294
+ if (h.command?.includes('auto-self-improve')) {
295
+ status.features.selfimprove.enabled = true;
296
+ }
297
+ }
298
+ }
299
+ }
300
+ }
280
301
  }
281
302
 
282
303
  // StatusLine
@@ -370,6 +391,8 @@ function printStatus(status) {
370
391
 
371
392
  printFeature('sessionstart', 'SessionStart Hook');
372
393
  printFeature('precompact', 'PreCompact Hook');
394
+ printFeature('ralphloop', 'RalphLoop (Stop)');
395
+ printFeature('selfimprove', 'SelfImprove (Stop)');
373
396
 
374
397
  const arch = status.features.archival;
375
398
  log(
@@ -417,9 +440,9 @@ function migrateSettings() {
417
440
 
418
441
  let migrated = false;
419
442
 
420
- // Migrate hooks (Stop hook removed due to reliability issues)
443
+ // Migrate hooks to new format
421
444
  if (settings.hooks) {
422
- ['SessionStart', 'PreCompact', 'UserPromptSubmit'].forEach(hookName => {
445
+ ['SessionStart', 'PreCompact', 'UserPromptSubmit', 'Stop'].forEach(hookName => {
423
446
  const hook = settings.hooks[hookName];
424
447
  if (!hook) return;
425
448
 
@@ -556,16 +579,45 @@ function enableFeature(feature, options = {}) {
556
579
  return false;
557
580
  }
558
581
 
559
- // Configure hook
560
- const command = config.type === 'node' ? `node ${scriptPath}` : `bash ${scriptPath}`;
582
+ // Use absolute path so hooks work from any subdirectory
583
+ const absoluteScriptPath = path.join(process.cwd(), scriptPath);
584
+
585
+ // Stop hooks use error suppression to avoid blocking Claude
586
+ const isStoHook = config.hook === 'Stop';
587
+ const command =
588
+ config.type === 'node'
589
+ ? `node ${absoluteScriptPath}${isStoHook ? ' 2>/dev/null || true' : ''}`
590
+ : `bash ${absoluteScriptPath}${isStoHook ? ' 2>/dev/null || true' : ''}`;
591
+
592
+ if (isStoHook) {
593
+ // Stop hooks stack - add to existing hooks instead of replacing
594
+ if (!settings.hooks.Stop) {
595
+ settings.hooks.Stop = [{ matcher: '', hooks: [] }];
596
+ } else if (!Array.isArray(settings.hooks.Stop) || settings.hooks.Stop.length === 0) {
597
+ settings.hooks.Stop = [{ matcher: '', hooks: [] }];
598
+ } else if (!settings.hooks.Stop[0].hooks) {
599
+ settings.hooks.Stop[0].hooks = [];
600
+ }
601
+
602
+ // Check if this script is already added
603
+ const hasHook = settings.hooks.Stop[0].hooks.some(h => h.command?.includes(config.script));
561
604
 
562
- settings.hooks[config.hook] = [
563
- {
564
- matcher: '',
565
- hooks: [{ type: 'command', command }],
566
- },
567
- ];
568
- success(`${config.hook} hook enabled (${config.script})`);
605
+ if (!hasHook) {
606
+ settings.hooks.Stop[0].hooks.push({ type: 'command', command });
607
+ success(`Stop hook added (${config.script})`);
608
+ } else {
609
+ info(`${feature} already enabled`);
610
+ }
611
+ } else {
612
+ // Other hooks (SessionStart, PreCompact) replace entirely
613
+ settings.hooks[config.hook] = [
614
+ {
615
+ matcher: '',
616
+ hooks: [{ type: 'command', command }],
617
+ },
618
+ ];
619
+ success(`${config.hook} hook enabled (${config.script})`);
620
+ }
569
621
  }
570
622
 
571
623
  // Handle archival
@@ -579,7 +631,8 @@ function enableFeature(feature, options = {}) {
579
631
  return false;
580
632
  }
581
633
 
582
- // Add to SessionStart hook
634
+ // Use absolute path so hooks work from any subdirectory
635
+ const absoluteScriptPath = path.join(process.cwd(), scriptPath);
583
636
  if (settings.hooks.SessionStart?.[0]?.hooks) {
584
637
  const hasArchival = settings.hooks.SessionStart[0].hooks.some(h =>
585
638
  h.command?.includes('archive-completed-stories')
@@ -587,7 +640,7 @@ function enableFeature(feature, options = {}) {
587
640
  if (!hasArchival) {
588
641
  settings.hooks.SessionStart[0].hooks.push({
589
642
  type: 'command',
590
- command: `bash ${scriptPath} --quiet`,
643
+ command: `bash ${absoluteScriptPath} --quiet`,
591
644
  });
592
645
  }
593
646
  }
@@ -607,9 +660,11 @@ function enableFeature(feature, options = {}) {
607
660
  return false;
608
661
  }
609
662
 
663
+ // Use absolute path so hooks work from any subdirectory
664
+ const absoluteScriptPath = path.join(process.cwd(), scriptPath);
610
665
  settings.statusLine = {
611
666
  type: 'command',
612
- command: `bash ${scriptPath}`,
667
+ command: `bash ${absoluteScriptPath}`,
613
668
  padding: 0,
614
669
  };
615
670
  success('Status line enabled');
@@ -656,8 +711,28 @@ function disableFeature(feature) {
656
711
 
657
712
  // Disable hook
658
713
  if (config.hook && settings.hooks?.[config.hook]) {
659
- delete settings.hooks[config.hook];
660
- success(`${config.hook} hook disabled`);
714
+ if (config.hook === 'Stop') {
715
+ // Stop hooks stack - remove only this script, not the entire hook
716
+ if (settings.hooks.Stop?.[0]?.hooks) {
717
+ const before = settings.hooks.Stop[0].hooks.length;
718
+ settings.hooks.Stop[0].hooks = settings.hooks.Stop[0].hooks.filter(
719
+ h => !h.command?.includes(config.script)
720
+ );
721
+ const after = settings.hooks.Stop[0].hooks.length;
722
+
723
+ if (before > after) {
724
+ success(`Stop hook removed (${config.script})`);
725
+ }
726
+
727
+ // If no more Stop hooks, remove the entire Stop hook
728
+ if (settings.hooks.Stop[0].hooks.length === 0) {
729
+ delete settings.hooks.Stop;
730
+ }
731
+ }
732
+ } else {
733
+ delete settings.hooks[config.hook];
734
+ success(`${config.hook} hook disabled`);
735
+ }
661
736
  }
662
737
 
663
738
  // Disable archival
@@ -951,9 +1026,11 @@ function showVersionInfo() {
951
1026
  const installed = installedVersion.split('.').map(Number);
952
1027
  const latest = latestVersion.split('.').map(Number);
953
1028
 
954
- if (latest[0] > installed[0] ||
955
- (latest[0] === installed[0] && latest[1] > installed[1]) ||
956
- (latest[0] === installed[0] && latest[1] === installed[1] && latest[2] > installed[2])) {
1029
+ if (
1030
+ latest[0] > installed[0] ||
1031
+ (latest[0] === installed[0] && latest[1] > installed[1]) ||
1032
+ (latest[0] === installed[0] && latest[1] === installed[1] && latest[2] > installed[2])
1033
+ ) {
957
1034
  log('\n🔄 Update available! Run: npx agileflow update', c.yellow);
958
1035
  }
959
1036
  }
@@ -1142,8 +1219,8 @@ ${c.cyan}Usage:${c.reset}
1142
1219
  node .agileflow/scripts/agileflow-configure.js [options]
1143
1220
 
1144
1221
  ${c.cyan}Profiles:${c.reset}
1145
- --profile=full All features (hooks, archival, statusline)
1146
- --profile=basic SessionStart + PreCompact + archival
1222
+ --profile=full All features (hooks, Stop hooks, archival, statusline)
1223
+ --profile=basic SessionStart + PreCompact + archival (no Stop hooks)
1147
1224
  --profile=minimal SessionStart + archival only
1148
1225
  --profile=none Disable all AgileFlow features
1149
1226
 
@@ -1151,7 +1228,9 @@ ${c.cyan}Feature Control:${c.reset}
1151
1228
  --enable=<list> Enable features (comma-separated)
1152
1229
  --disable=<list> Disable features (comma-separated)
1153
1230
 
1154
- Features: sessionstart, precompact, archival, statusline
1231
+ Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline
1232
+
1233
+ Stop hooks (ralphloop, selfimprove) run when Claude completes/pauses
1155
1234
 
1156
1235
  ${c.cyan}Statusline Components:${c.reset}
1157
1236
  --show=<list> Show statusline components (comma-separated)
@@ -1273,8 +1352,7 @@ function main() {
1273
1352
  else if (arg.startsWith('--repair=')) {
1274
1353
  repair = true;
1275
1354
  repairFeature = arg.split('=')[1].trim().toLowerCase();
1276
- }
1277
- else if (arg === '--version' || arg === '-v') showVersion = true;
1355
+ } else if (arg === '--version' || arg === '-v') showVersion = true;
1278
1356
  else if (arg === '--list-scripts' || arg === '--scripts') listScriptsMode = true;
1279
1357
  });
1280
1358
 
@@ -29,7 +29,7 @@ MAGENTA="\033[35m"
29
29
  CYAN="\033[36m"
30
30
  WHITE="\033[37m"
31
31
 
32
- # Bright foreground colors
32
+ # Bright foreground colors (standard ANSI)
33
33
  BRIGHT_RED="\033[91m"
34
34
  BRIGHT_GREEN="\033[92m"
35
35
  BRIGHT_YELLOW="\033[93m"
@@ -37,9 +37,74 @@ BRIGHT_BLUE="\033[94m"
37
37
  BRIGHT_MAGENTA="\033[95m"
38
38
  BRIGHT_CYAN="\033[96m"
39
39
 
40
+ # 256-color palette (vibrant modern colors from cc-statusline)
41
+ # Use these for context/session indicators for better visibility
42
+ CTX_GREEN="\033[38;5;158m" # Mint green - healthy context
43
+ CTX_YELLOW="\033[38;5;215m" # Peach - moderate usage
44
+ CTX_ORANGE="\033[38;5;215m" # Peach/orange - high usage
45
+ CTX_RED="\033[38;5;203m" # Coral red - critical
46
+
47
+ SESSION_GREEN="\033[38;5;194m" # Light green - plenty of time
48
+ SESSION_YELLOW="\033[38;5;228m" # Light yellow - getting low
49
+ SESSION_RED="\033[38;5;210m" # Light pink/red - critical
50
+
40
51
  # Brand color (burnt orange #e8683a = RGB 232,104,58)
41
52
  BRAND="\033[38;2;232;104;58m"
42
53
 
54
+ # ============================================================================
55
+ # Helper Functions
56
+ # ============================================================================
57
+
58
+ # Progress bar with custom characters: ▓ (filled) ░ (empty)
59
+ # Usage: progress_bar <percent> <width>
60
+ # Example: progress_bar 75 10 → "▓▓▓▓▓▓▓░░░"
61
+ progress_bar() {
62
+ local pct="${1:-0}"
63
+ local width="${2:-10}"
64
+
65
+ # Validate and clamp percentage
66
+ [[ "$pct" =~ ^[0-9]+$ ]] || pct=0
67
+ ((pct < 0)) && pct=0
68
+ ((pct > 100)) && pct=100
69
+
70
+ local filled=$(( pct * width / 100 ))
71
+ local empty=$(( width - filled ))
72
+
73
+ # Build bar with custom characters
74
+ local bar=""
75
+ for ((i=0; i<filled; i++)); do bar+="▓"; done
76
+ for ((i=0; i<empty; i++)); do bar+="░"; done
77
+
78
+ echo "$bar"
79
+ }
80
+
81
+ # Convert ISO timestamp to epoch seconds (cross-platform)
82
+ to_epoch() {
83
+ local ts="$1"
84
+ # Try gdate first (macOS with coreutils)
85
+ if command -v gdate >/dev/null 2>&1; then
86
+ gdate -d "$ts" +%s 2>/dev/null && return
87
+ fi
88
+ # Try BSD date (macOS)
89
+ date -u -j -f "%Y-%m-%dT%H:%M:%S%z" "${ts/Z/+0000}" +%s 2>/dev/null && return
90
+ # Fallback to Python
91
+ python3 - "$ts" <<'PY' 2>/dev/null
92
+ import sys, datetime
93
+ s=sys.argv[1].replace('Z','+00:00')
94
+ print(int(datetime.datetime.fromisoformat(s).timestamp()))
95
+ PY
96
+ }
97
+
98
+ # Format epoch to HH:MM
99
+ fmt_time_hm() {
100
+ local epoch="$1"
101
+ if date -r 0 +%s >/dev/null 2>&1; then
102
+ date -r "$epoch" +"%H:%M"
103
+ else
104
+ date -d "@$epoch" +"%H:%M"
105
+ fi
106
+ }
107
+
43
108
  # ============================================================================
44
109
  # Read Component Configuration
45
110
  # ============================================================================
@@ -50,6 +115,8 @@ SHOW_STORY=true
50
115
  SHOW_EPIC=true
51
116
  SHOW_WIP=true
52
117
  SHOW_CONTEXT=true
118
+ SHOW_CONTEXT_BAR=true
119
+ SHOW_SESSION_TIME=true
53
120
  SHOW_COST=true
54
121
  SHOW_GIT=true
55
122
 
@@ -65,6 +132,8 @@ if [ -f "docs/00-meta/agileflow-metadata.json" ]; then
65
132
  SHOW_EPIC=$(echo "$COMPONENTS" | jq -r '.epic | if . == null then true else . end')
66
133
  SHOW_WIP=$(echo "$COMPONENTS" | jq -r '.wip | if . == null then true else . end')
67
134
  SHOW_CONTEXT=$(echo "$COMPONENTS" | jq -r '.context | if . == null then true else . end')
135
+ SHOW_CONTEXT_BAR=$(echo "$COMPONENTS" | jq -r '.context_bar | if . == null then true else . end')
136
+ SHOW_SESSION_TIME=$(echo "$COMPONENTS" | jq -r '.session_time | if . == null then true else . end')
68
137
  SHOW_COST=$(echo "$COMPONENTS" | jq -r '.cost | if . == null then true else . end')
69
138
  SHOW_GIT=$(echo "$COMPONENTS" | jq -r '.git | if . == null then true else . end')
70
139
  fi
@@ -132,24 +201,29 @@ CONTEXT_SIZE=$(echo "$input" | jq -r '.context_window.context_window_size // 200
132
201
  USAGE=$(echo "$input" | jq '.context_window.current_usage // null')
133
202
 
134
203
  CTX_DISPLAY=""
135
- CTX_COLOR="$GREEN"
204
+ CTX_BAR_DISPLAY=""
205
+ CTX_COLOR="$CTX_GREEN"
136
206
  if [ "$USAGE" != "null" ]; then
137
207
  CURRENT_TOKENS=$(echo "$USAGE" | jq '.input_tokens + (.cache_creation_input_tokens // 0) + (.cache_read_input_tokens // 0)')
138
208
  if [ "$CURRENT_TOKENS" != "null" ] && [ "$CURRENT_TOKENS" -gt 0 ] 2>/dev/null; then
139
209
  PERCENT_USED=$((CURRENT_TOKENS * 100 / CONTEXT_SIZE))
140
210
 
141
- # Color based on usage level
211
+ # Color based on usage level (using vibrant 256-color palette)
142
212
  if [ "$PERCENT_USED" -ge 80 ]; then
143
- CTX_COLOR="$BRIGHT_RED"
213
+ CTX_COLOR="$CTX_RED" # Coral red - critical
144
214
  elif [ "$PERCENT_USED" -ge 60 ]; then
145
- CTX_COLOR="$YELLOW"
215
+ CTX_COLOR="$CTX_ORANGE" # Peach - high usage
146
216
  elif [ "$PERCENT_USED" -ge 40 ]; then
147
- CTX_COLOR="$BRIGHT_YELLOW"
217
+ CTX_COLOR="$CTX_YELLOW" # Peach - moderate
148
218
  else
149
- CTX_COLOR="$GREEN"
219
+ CTX_COLOR="$CTX_GREEN" # Mint green - healthy
150
220
  fi
151
221
 
152
222
  CTX_DISPLAY="${CTX_COLOR}${PERCENT_USED}%${RESET}"
223
+
224
+ # Generate progress bar (8 chars wide for compactness)
225
+ CTX_BAR=$(progress_bar "$PERCENT_USED" 8)
226
+ CTX_BAR_DISPLAY="${DIM}[${RESET}${CTX_COLOR}${CTX_BAR}${RESET}${DIM}]${RESET}"
153
227
  fi
154
228
  fi
155
229
 
@@ -166,6 +240,67 @@ if [ "$TOTAL_COST" != "0" ] && [ "$TOTAL_COST" != "null" ]; then
166
240
  fi
167
241
  fi
168
242
 
243
+ # ============================================================================
244
+ # Session Time Remaining (via ccusage if available)
245
+ # ============================================================================
246
+ SESSION_DISPLAY=""
247
+ if [ "$SHOW_SESSION_TIME" = "true" ]; then
248
+ # Try to get session info from ccusage (fast cached check)
249
+ if command -v npx >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
250
+ # Use timeout to prevent blocking - ccusage should be fast
251
+ BLOCKS_OUTPUT=$(timeout 3 npx ccusage@latest blocks --json 2>/dev/null || true)
252
+
253
+ if [ -n "$BLOCKS_OUTPUT" ]; then
254
+ ACTIVE_BLOCK=$(echo "$BLOCKS_OUTPUT" | jq -c '.blocks[] | select(.isActive == true)' 2>/dev/null | head -n1)
255
+
256
+ if [ -n "$ACTIVE_BLOCK" ]; then
257
+ # Get reset time
258
+ RESET_TIME_STR=$(echo "$ACTIVE_BLOCK" | jq -r '.usageLimitResetTime // .endTime // empty')
259
+ START_TIME_STR=$(echo "$ACTIVE_BLOCK" | jq -r '.startTime // empty')
260
+
261
+ if [ -n "$RESET_TIME_STR" ] && [ -n "$START_TIME_STR" ]; then
262
+ START_SEC=$(to_epoch "$START_TIME_STR")
263
+ END_SEC=$(to_epoch "$RESET_TIME_STR")
264
+ NOW_SEC=$(date +%s)
265
+
266
+ if [ -n "$START_SEC" ] && [ -n "$END_SEC" ] && [ "$START_SEC" -gt 0 ] && [ "$END_SEC" -gt 0 ]; then
267
+ TOTAL=$(( END_SEC - START_SEC ))
268
+ [ "$TOTAL" -lt 1 ] && TOTAL=1
269
+
270
+ ELAPSED=$(( NOW_SEC - START_SEC ))
271
+ [ "$ELAPSED" -lt 0 ] && ELAPSED=0
272
+ [ "$ELAPSED" -gt "$TOTAL" ] && ELAPSED=$TOTAL
273
+
274
+ SESSION_PCT=$(( ELAPSED * 100 / TOTAL ))
275
+ REMAINING=$(( END_SEC - NOW_SEC ))
276
+ [ "$REMAINING" -lt 0 ] && REMAINING=0
277
+
278
+ # Format remaining time
279
+ RH=$(( REMAINING / 3600 ))
280
+ RM=$(( (REMAINING % 3600) / 60 ))
281
+
282
+ # Color based on time remaining (using vibrant 256-color palette)
283
+ if [ "$RH" -eq 0 ] && [ "$RM" -lt 30 ]; then
284
+ SESSION_COLOR="$SESSION_RED" # Light pink - critical
285
+ elif [ "$RH" -eq 0 ]; then
286
+ SESSION_COLOR="$SESSION_YELLOW" # Light yellow - getting low
287
+ else
288
+ SESSION_COLOR="$SESSION_GREEN" # Light green - plenty of time
289
+ fi
290
+
291
+ # Build compact display: "⏱2h15m" or "⏱45m"
292
+ if [ "$RH" -gt 0 ]; then
293
+ SESSION_DISPLAY="${SESSION_COLOR}⏱${RH}h${RM}m${RESET}"
294
+ else
295
+ SESSION_DISPLAY="${SESSION_COLOR}⏱${RM}m${RESET}"
296
+ fi
297
+ fi
298
+ fi
299
+ fi
300
+ fi
301
+ fi
302
+ fi
303
+
169
304
  # ============================================================================
170
305
  # AgileFlow Status - Read from status.json
171
306
  # ============================================================================
@@ -334,10 +469,21 @@ if [ "$SHOW_WIP" = "true" ] && [ -n "$WIP_DISPLAY" ]; then
334
469
  OUTPUT="${OUTPUT}${WIP_DISPLAY}"
335
470
  fi
336
471
 
337
- # Add context usage (if enabled)
472
+ # Add context usage (if enabled) - percentage and/or bar
338
473
  if [ "$SHOW_CONTEXT" = "true" ] && [ -n "$CTX_DISPLAY" ]; then
339
474
  [ -n "$OUTPUT" ] && OUTPUT="${OUTPUT}${SEP}"
340
- OUTPUT="${OUTPUT}${CTX_DISPLAY}"
475
+ if [ "$SHOW_CONTEXT_BAR" = "true" ] && [ -n "$CTX_BAR_DISPLAY" ]; then
476
+ # Show both percentage and bar: "45% [▓▓▓▓░░░░]"
477
+ OUTPUT="${OUTPUT}${CTX_DISPLAY} ${CTX_BAR_DISPLAY}"
478
+ else
479
+ OUTPUT="${OUTPUT}${CTX_DISPLAY}"
480
+ fi
481
+ fi
482
+
483
+ # Add session time remaining (if enabled and available)
484
+ if [ "$SHOW_SESSION_TIME" = "true" ] && [ -n "$SESSION_DISPLAY" ]; then
485
+ [ -n "$OUTPUT" ] && OUTPUT="${OUTPUT}${SEP}"
486
+ OUTPUT="${OUTPUT}${SESSION_DISPLAY}"
341
487
  fi
342
488
 
343
489
  # Add cost (if enabled)