agileflow 2.77.0 → 2.78.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 (113) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -1
  3. package/scripts/agileflow-configure.js +174 -2
  4. package/scripts/agileflow-statusline.sh +171 -78
  5. package/scripts/agileflow-welcome.js +79 -2
  6. package/scripts/damage-control-bash.js +232 -0
  7. package/scripts/damage-control-edit.js +243 -0
  8. package/scripts/damage-control-write.js +243 -0
  9. package/src/core/agents/accessibility.md +124 -53
  10. package/src/core/agents/adr-writer.md +192 -52
  11. package/src/core/agents/analytics.md +139 -60
  12. package/src/core/agents/api.md +173 -63
  13. package/src/core/agents/ci.md +139 -57
  14. package/src/core/agents/compliance.md +159 -68
  15. package/src/core/agents/configuration/damage-control.md +356 -0
  16. package/src/core/agents/database.md +162 -61
  17. package/src/core/agents/datamigration.md +179 -66
  18. package/src/core/agents/design.md +179 -57
  19. package/src/core/agents/devops.md +160 -3
  20. package/src/core/agents/documentation.md +204 -60
  21. package/src/core/agents/epic-planner.md +147 -55
  22. package/src/core/agents/integrations.md +197 -69
  23. package/src/core/agents/mentor.md +158 -57
  24. package/src/core/agents/mobile.md +159 -67
  25. package/src/core/agents/monitoring.md +154 -65
  26. package/src/core/agents/multi-expert.md +115 -43
  27. package/src/core/agents/orchestrator.md +77 -24
  28. package/src/core/agents/performance.md +130 -75
  29. package/src/core/agents/product.md +151 -55
  30. package/src/core/agents/qa.md +162 -74
  31. package/src/core/agents/readme-updater.md +178 -76
  32. package/src/core/agents/refactor.md +148 -95
  33. package/src/core/agents/research.md +143 -72
  34. package/src/core/agents/security.md +154 -65
  35. package/src/core/agents/testing.md +176 -97
  36. package/src/core/agents/ui.md +170 -79
  37. package/src/core/commands/adr/list.md +171 -0
  38. package/src/core/commands/adr/update.md +235 -0
  39. package/src/core/commands/adr/view.md +252 -0
  40. package/src/core/commands/adr.md +207 -50
  41. package/src/core/commands/agent.md +16 -0
  42. package/src/core/commands/assign.md +148 -44
  43. package/src/core/commands/auto.md +18 -1
  44. package/src/core/commands/babysit.md +361 -36
  45. package/src/core/commands/baseline.md +14 -0
  46. package/src/core/commands/blockers.md +170 -51
  47. package/src/core/commands/board.md +144 -66
  48. package/src/core/commands/changelog.md +15 -0
  49. package/src/core/commands/ci.md +179 -69
  50. package/src/core/commands/compress.md +18 -0
  51. package/src/core/commands/configure.md +16 -0
  52. package/src/core/commands/context/export.md +193 -4
  53. package/src/core/commands/context/full.md +191 -18
  54. package/src/core/commands/context/note.md +248 -4
  55. package/src/core/commands/debt.md +17 -0
  56. package/src/core/commands/deploy.md +208 -65
  57. package/src/core/commands/deps.md +15 -0
  58. package/src/core/commands/diagnose.md +16 -0
  59. package/src/core/commands/docs.md +196 -64
  60. package/src/core/commands/epic/list.md +170 -0
  61. package/src/core/commands/epic/view.md +242 -0
  62. package/src/core/commands/epic.md +192 -69
  63. package/src/core/commands/feedback.md +191 -71
  64. package/src/core/commands/handoff.md +162 -48
  65. package/src/core/commands/help.md +9 -0
  66. package/src/core/commands/ideate.md +446 -0
  67. package/src/core/commands/impact.md +16 -0
  68. package/src/core/commands/metrics.md +141 -37
  69. package/src/core/commands/multi-expert.md +77 -0
  70. package/src/core/commands/packages.md +16 -0
  71. package/src/core/commands/pr.md +161 -67
  72. package/src/core/commands/readme-sync.md +16 -0
  73. package/src/core/commands/research/analyze.md +568 -0
  74. package/src/core/commands/research/ask.md +345 -20
  75. package/src/core/commands/research/import.md +562 -19
  76. package/src/core/commands/research/list.md +173 -5
  77. package/src/core/commands/research/view.md +181 -8
  78. package/src/core/commands/retro.md +135 -48
  79. package/src/core/commands/review.md +219 -47
  80. package/src/core/commands/session/end.md +209 -0
  81. package/src/core/commands/session/history.md +210 -0
  82. package/src/core/commands/session/init.md +116 -0
  83. package/src/core/commands/session/new.md +296 -0
  84. package/src/core/commands/session/resume.md +166 -0
  85. package/src/core/commands/session/status.md +166 -0
  86. package/src/core/commands/skill/create.md +115 -17
  87. package/src/core/commands/skill/delete.md +117 -0
  88. package/src/core/commands/skill/edit.md +104 -0
  89. package/src/core/commands/skill/list.md +128 -0
  90. package/src/core/commands/skill/test.md +135 -0
  91. package/src/core/commands/skill/upgrade.md +542 -0
  92. package/src/core/commands/sprint.md +17 -1
  93. package/src/core/commands/status.md +133 -21
  94. package/src/core/commands/story/list.md +176 -0
  95. package/src/core/commands/story/view.md +265 -0
  96. package/src/core/commands/story-validate.md +101 -1
  97. package/src/core/commands/story.md +204 -51
  98. package/src/core/commands/template.md +16 -1
  99. package/src/core/commands/tests.md +226 -64
  100. package/src/core/commands/update.md +17 -1
  101. package/src/core/commands/validate-expertise.md +16 -0
  102. package/src/core/commands/velocity.md +140 -36
  103. package/src/core/commands/verify.md +14 -0
  104. package/src/core/commands/whats-new.md +30 -0
  105. package/src/core/skills/_learnings/README.md +91 -0
  106. package/src/core/skills/_learnings/_template.yaml +106 -0
  107. package/src/core/skills/_learnings/commit.yaml +69 -0
  108. package/src/core/templates/damage-control-patterns.yaml +234 -0
  109. package/src/core/templates/skill-template.md +53 -11
  110. package/tools/cli/commands/start.js +180 -0
  111. package/tools/cli/tui/Dashboard.js +66 -0
  112. package/tools/cli/tui/StoryList.js +69 -0
  113. package/tools/cli/tui/index.js +16 -0
package/README.md CHANGED
@@ -3,7 +3,7 @@
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-58-blue)](docs/04-architecture/commands.md)
6
+ [![Commands](https://img.shields.io/badge/commands-68-blue)](docs/04-architecture/commands.md)
7
7
  [![Agents/Experts](https://img.shields.io/badge/agents%2Fexperts-27-orange)](docs/04-architecture/subagents.md)
8
8
  [![Skills](https://img.shields.io/badge/skills-dynamic-purple)](docs/04-architecture/skills.md)
9
9
 
@@ -65,7 +65,7 @@ AgileFlow combines three proven methodologies:
65
65
 
66
66
  | Component | Count | Description |
67
67
  |-----------|-------|-------------|
68
- | [Commands](docs/04-architecture/commands.md) | 58 | Slash commands for agile workflows |
68
+ | [Commands](docs/04-architecture/commands.md) | 68 | Slash commands for agile workflows |
69
69
  | [Agents/Experts](docs/04-architecture/subagents.md) | 27 | Specialized agents with self-improving knowledge bases |
70
70
  | [Skills](docs/04-architecture/skills.md) | Dynamic | Generated on-demand with `/agileflow:skill:create` |
71
71
 
@@ -76,7 +76,7 @@ AgileFlow combines three proven methodologies:
76
76
  Full documentation lives in [`docs/04-architecture/`](docs/04-architecture/):
77
77
 
78
78
  ### Reference
79
- - [Commands](docs/04-architecture/commands.md) - All 58 slash commands
79
+ - [Commands](docs/04-architecture/commands.md) - All 68 slash commands
80
80
  - [Agents/Experts](docs/04-architecture/subagents.md) - 27 specialized agents with self-improving knowledge
81
81
  - [Skills](docs/04-architecture/skills.md) - Dynamic skill generator with MCP integration
82
82
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "2.77.0",
3
+ "version": "2.78.0",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
@@ -51,6 +51,8 @@
51
51
  "test:coverage": "jest --coverage"
52
52
  },
53
53
  "dependencies": {
54
+ "blessed": "^0.1.81",
55
+ "blessed-contrib": "^4.10.1",
54
56
  "chalk": "^4.1.2",
55
57
  "commander": "^12.1.0",
56
58
  "fs-extra": "^11.2.0",
@@ -59,6 +61,9 @@
59
61
  "ora": "^5.4.1",
60
62
  "semver": "^7.6.3"
61
63
  },
64
+ "optionalDependencies": {
65
+ "node-pty": "^1.0.0"
66
+ },
62
67
  "engines": {
63
68
  "node": ">=18.0.0"
64
69
  },
@@ -23,7 +23,7 @@
23
23
  * --detect Show current status
24
24
  * --help Show help
25
25
  *
26
- * Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, autoupdate
26
+ * Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, autoupdate, damagecontrol
27
27
  */
28
28
 
29
29
  const fs = require('fs');
@@ -76,6 +76,11 @@ const FEATURES = {
76
76
  archival: { script: 'archive-completed-stories.sh', requiresHook: 'sessionstart' },
77
77
  statusline: { script: 'agileflow-statusline.sh' },
78
78
  autoupdate: { metadataOnly: true }, // Stored in metadata.updates.autoUpdate
79
+ damagecontrol: {
80
+ preToolUseHooks: true,
81
+ scripts: ['damage-control-bash.js', 'damage-control-edit.js', 'damage-control-write.js'],
82
+ patternsFile: 'damage-control-patterns.yaml',
83
+ },
79
84
  };
80
85
 
81
86
  // Complete registry of all scripts that may need repair
@@ -87,6 +92,9 @@ const ALL_SCRIPTS = {
87
92
  'auto-self-improve.js': { feature: 'selfimprove', required: true },
88
93
  'archive-completed-stories.sh': { feature: 'archival', required: true },
89
94
  'agileflow-statusline.sh': { feature: 'statusline', required: true },
95
+ 'damage-control-bash.js': { feature: 'damagecontrol', required: true },
96
+ 'damage-control-edit.js': { feature: 'damagecontrol', required: true },
97
+ 'damage-control-write.js': { feature: 'damagecontrol', required: true },
90
98
 
91
99
  // Support scripts (used by commands/agents)
92
100
  'obtain-context.js': { usedBy: ['/babysit', '/mentor', '/sprint'] },
@@ -215,6 +223,7 @@ function detectConfig() {
215
223
  selfimprove: { enabled: false, valid: true, issues: [], version: null, outdated: false },
216
224
  archival: { enabled: false, threshold: null, version: null, outdated: false },
217
225
  statusline: { enabled: false, valid: true, issues: [], version: null, outdated: false },
226
+ damagecontrol: { enabled: false, valid: true, issues: [], version: null, outdated: false, level: null, patternCount: 0 },
218
227
  },
219
228
  metadata: { exists: false, version: null },
220
229
  currentVersion: VERSION,
@@ -298,6 +307,32 @@ function detectConfig() {
298
307
  }
299
308
  }
300
309
  }
310
+
311
+ // PreToolUse hooks (damage control)
312
+ if (settings.hooks.PreToolUse) {
313
+ if (Array.isArray(settings.hooks.PreToolUse) && settings.hooks.PreToolUse.length > 0) {
314
+ // Check for damage-control hooks by looking for damage-control scripts
315
+ const hasBashHook = settings.hooks.PreToolUse.some(
316
+ h => h.matcher === 'Bash' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
317
+ );
318
+ const hasEditHook = settings.hooks.PreToolUse.some(
319
+ h => h.matcher === 'Edit' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
320
+ );
321
+ const hasWriteHook = settings.hooks.PreToolUse.some(
322
+ h => h.matcher === 'Write' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
323
+ );
324
+
325
+ if (hasBashHook || hasEditHook || hasWriteHook) {
326
+ status.features.damagecontrol.enabled = true;
327
+ // Count how many of the 3 hooks are present
328
+ const hookCount = [hasBashHook, hasEditHook, hasWriteHook].filter(Boolean).length;
329
+ if (hookCount < 3) {
330
+ status.features.damagecontrol.valid = false;
331
+ status.features.damagecontrol.issues.push(`Only ${hookCount}/3 hooks configured`);
332
+ }
333
+ }
334
+ }
335
+ }
301
336
  }
302
337
 
303
338
  // StatusLine
@@ -326,6 +361,11 @@ function detectConfig() {
326
361
  status.features.archival.threshold = meta.archival.threshold_days;
327
362
  }
328
363
 
364
+ // Damage control metadata
365
+ if (meta.features?.damagecontrol?.enabled) {
366
+ status.features.damagecontrol.level = meta.features.damagecontrol.protectionLevel || 'standard';
367
+ }
368
+
329
369
  // Read feature versions from metadata and check if outdated
330
370
  if (meta.features) {
331
371
  Object.entries(meta.features).forEach(([feature, data]) => {
@@ -402,6 +442,22 @@ function printStatus(status) {
402
442
 
403
443
  printFeature('statusline', 'Status Line');
404
444
 
445
+ // Damage Control (special display with level info)
446
+ const dc = status.features.damagecontrol;
447
+ if (dc.enabled) {
448
+ let dcStatusText = 'enabled';
449
+ if (dc.level) dcStatusText += ` (${dc.level})`;
450
+ if (!dc.valid) dcStatusText = 'INCOMPLETE';
451
+ const dcIcon = dc.enabled && dc.valid ? '🛡️' : '⚠️';
452
+ const dcColor = dc.enabled && dc.valid ? c.green : c.yellow;
453
+ log(` ${dcIcon} Damage Control: ${dcStatusText}`, dcColor);
454
+ if (dc.issues?.length > 0) {
455
+ dc.issues.forEach(issue => log(` └─ ${issue}`, c.yellow));
456
+ }
457
+ } else {
458
+ log(` ❌ Damage Control: disabled`, c.dim);
459
+ }
460
+
405
461
  // Metadata
406
462
  if (status.metadata.exists) {
407
463
  log(`\nMetadata: v${status.metadata.version}`, c.dim);
@@ -685,6 +741,79 @@ function enableFeature(feature, options = {}) {
685
741
  return true; // Skip settings.json write for this feature
686
742
  }
687
743
 
744
+ // Handle damage control (PreToolUse hooks)
745
+ if (feature === 'damagecontrol') {
746
+ const level = options.protectionLevel || 'standard';
747
+
748
+ // Verify all required scripts exist
749
+ const requiredScripts = ['damage-control-bash.js', 'damage-control-edit.js', 'damage-control-write.js'];
750
+ for (const script of requiredScripts) {
751
+ if (!scriptExists(script)) {
752
+ error(`Script not found: ${getScriptPath(script)}`);
753
+ info('Run "npx agileflow update" to reinstall scripts');
754
+ return false;
755
+ }
756
+ }
757
+
758
+ // Deploy patterns file if not exists
759
+ const patternsDir = path.join(process.cwd(), '.agileflow', 'config');
760
+ const patternsDest = path.join(patternsDir, 'damage-control-patterns.yaml');
761
+ if (!fs.existsSync(patternsDest)) {
762
+ ensureDir(patternsDir);
763
+ // Try to copy from templates
764
+ const templatePath = path.join(process.cwd(), '.agileflow', 'templates', 'damage-control-patterns.yaml');
765
+ if (fs.existsSync(templatePath)) {
766
+ fs.copyFileSync(templatePath, patternsDest);
767
+ success('Deployed damage control patterns');
768
+ } else {
769
+ warn('No patterns template found - hooks will use defaults');
770
+ }
771
+ }
772
+
773
+ // Initialize PreToolUse array if not exists
774
+ if (!settings.hooks.PreToolUse) {
775
+ settings.hooks.PreToolUse = [];
776
+ }
777
+
778
+ // Helper to add or update a PreToolUse hook
779
+ const addPreToolUseHook = (matcher, scriptName) => {
780
+ const scriptPath = path.join(process.cwd(), '.agileflow', 'scripts', scriptName);
781
+
782
+ // Remove existing hook for this matcher if present
783
+ settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(h => h.matcher !== matcher);
784
+
785
+ // Add new hook
786
+ settings.hooks.PreToolUse.push({
787
+ matcher,
788
+ hooks: [{ type: 'command', command: `node ${scriptPath}`, timeout: 5 }],
789
+ });
790
+ };
791
+
792
+ // Add hooks for Bash, Edit, Write tools
793
+ addPreToolUseHook('Bash', 'damage-control-bash.js');
794
+ addPreToolUseHook('Edit', 'damage-control-edit.js');
795
+ addPreToolUseHook('Write', 'damage-control-write.js');
796
+
797
+ success('Damage control PreToolUse hooks enabled');
798
+
799
+ // Update metadata with protection level
800
+ updateMetadata({
801
+ features: {
802
+ damagecontrol: {
803
+ enabled: true,
804
+ protectionLevel: level,
805
+ version: VERSION,
806
+ at: new Date().toISOString(),
807
+ },
808
+ },
809
+ });
810
+
811
+ writeJSON('.claude/settings.json', settings);
812
+ updateGitignore();
813
+
814
+ return true;
815
+ }
816
+
688
817
  writeJSON('.claude/settings.json', settings);
689
818
  updateMetadata({
690
819
  features: { [feature]: { enabled: true, version: VERSION, at: new Date().toISOString() } },
@@ -764,6 +893,45 @@ function disableFeature(feature) {
764
893
  return true; // Skip settings.json write for this feature
765
894
  }
766
895
 
896
+ // Disable damage control (PreToolUse hooks)
897
+ if (feature === 'damagecontrol') {
898
+ if (settings.hooks?.PreToolUse && Array.isArray(settings.hooks.PreToolUse)) {
899
+ const before = settings.hooks.PreToolUse.length;
900
+
901
+ // Remove damage-control hooks (Bash, Edit, Write matchers with damage-control scripts)
902
+ settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(h => {
903
+ const isDamageControlHook = h.hooks?.some(hk => hk.command?.includes('damage-control'));
904
+ return !isDamageControlHook;
905
+ });
906
+
907
+ const after = settings.hooks.PreToolUse.length;
908
+
909
+ if (before > after) {
910
+ success(`Removed ${before - after} damage control PreToolUse hook(s)`);
911
+ }
912
+
913
+ // If no more PreToolUse hooks, remove the entire array
914
+ if (settings.hooks.PreToolUse.length === 0) {
915
+ delete settings.hooks.PreToolUse;
916
+ }
917
+ }
918
+
919
+ // Update metadata
920
+ updateMetadata({
921
+ features: {
922
+ damagecontrol: {
923
+ enabled: false,
924
+ version: VERSION,
925
+ at: new Date().toISOString(),
926
+ },
927
+ },
928
+ });
929
+
930
+ writeJSON('.claude/settings.json', settings);
931
+ success('Damage control disabled');
932
+ return true;
933
+ }
934
+
767
935
  writeJSON('.claude/settings.json', settings);
768
936
  updateMetadata({
769
937
  features: { [feature]: { enabled: false, version: VERSION, at: new Date().toISOString() } },
@@ -1228,9 +1396,10 @@ ${c.cyan}Feature Control:${c.reset}
1228
1396
  --enable=<list> Enable features (comma-separated)
1229
1397
  --disable=<list> Disable features (comma-separated)
1230
1398
 
1231
- Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline
1399
+ Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, damagecontrol
1232
1400
 
1233
1401
  Stop hooks (ralphloop, selfimprove) run when Claude completes/pauses
1402
+ Damage control (damagecontrol) uses PreToolUse hooks to block dangerous commands
1234
1403
 
1235
1404
  ${c.cyan}Statusline Components:${c.reset}
1236
1405
  --show=<list> Show statusline components (comma-separated)
@@ -1293,6 +1462,9 @@ ${c.cyan}Examples:${c.reset}
1293
1462
 
1294
1463
  # Repair scripts for a specific feature
1295
1464
  node .agileflow/scripts/agileflow-configure.js --repair=statusline
1465
+
1466
+ # Enable damage control (PreToolUse hooks to block dangerous commands)
1467
+ node .agileflow/scripts/agileflow-configure.js --enable=damagecontrol
1296
1468
  `);
1297
1469
  }
1298
1470
 
@@ -193,40 +193,75 @@ fi
193
193
  # ============================================================================
194
194
  input=$(cat)
195
195
 
196
- # Parse model info
197
- MODEL_DISPLAY=$(echo "$input" | jq -r '.model.display_name // "Claude"')
196
+ # Parse model info (fallback to empty if no input)
197
+ MODEL_DISPLAY=$(echo "$input" | jq -r '.model.display_name // empty' 2>/dev/null)
198
+ [ -z "$MODEL_DISPLAY" ] && MODEL_DISPLAY=""
198
199
 
199
- # Parse context usage
200
- CONTEXT_SIZE=$(echo "$input" | jq -r '.context_window.context_window_size // 200000')
201
- USAGE=$(echo "$input" | jq '.context_window.current_usage // null')
200
+ # ============================================================================
201
+ # Context Window Usage (reads from session JSONL file like cc-statusline)
202
+ # ============================================================================
203
+ # Claude Code doesn't pass context usage via stdin reliably, so we read it
204
+ # directly from the session's JSONL file (same approach as cc-statusline)
202
205
 
203
206
  CTX_DISPLAY=""
204
207
  CTX_BAR_DISPLAY=""
205
208
  CTX_COLOR="$CTX_GREEN"
206
- if [ "$USAGE" != "null" ]; then
207
- CURRENT_TOKENS=$(echo "$USAGE" | jq '.input_tokens + (.cache_creation_input_tokens // 0) + (.cache_read_input_tokens // 0)')
208
- if [ "$CURRENT_TOKENS" != "null" ] && [ "$CURRENT_TOKENS" -gt 0 ] 2>/dev/null; then
209
- PERCENT_USED=$((CURRENT_TOKENS * 100 / CONTEXT_SIZE))
210
-
211
- # Color based on usage level (using vibrant 256-color palette)
212
- if [ "$PERCENT_USED" -ge 80 ]; then
213
- CTX_COLOR="$CTX_RED" # Coral red - critical
214
- elif [ "$PERCENT_USED" -ge 60 ]; then
215
- CTX_COLOR="$CTX_ORANGE" # Peach - high usage
216
- elif [ "$PERCENT_USED" -ge 40 ]; then
217
- CTX_COLOR="$CTX_YELLOW" # Peach - moderate
218
- else
219
- CTX_COLOR="$CTX_GREEN" # Mint green - healthy
220
- fi
209
+ PERCENT_USED=0
210
+
211
+ # Get session_id and current_dir from input
212
+ SESSION_ID=$(echo "$input" | jq -r '.session_id // empty' 2>/dev/null)
213
+ CURRENT_DIR=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // empty' 2>/dev/null)
214
+
215
+ # Determine max context based on model (all modern models use 200K)
216
+ get_max_context() {
217
+ local model="$1"
218
+ case "$model" in
219
+ *"Claude 3 Haiku"*|*"claude 3 haiku"*)
220
+ echo "100000" # 100K for original Claude 3 Haiku
221
+ ;;
222
+ *)
223
+ echo "200000" # 200K for Opus, Sonnet, modern Haiku
224
+ ;;
225
+ esac
226
+ }
227
+
228
+ MAX_CONTEXT=$(get_max_context "$MODEL_DISPLAY")
229
+
230
+ if [ -n "$SESSION_ID" ] && [ -n "$CURRENT_DIR" ]; then
231
+ # Convert current dir to session file path (same as cc-statusline)
232
+ # e.g., /home/coder/AgileFlow -> home-coder-AgileFlow
233
+ PROJECT_DIR=$(echo "$CURRENT_DIR" | sed "s|^$HOME|~|g" | sed "s|~|$HOME|g" | sed 's|/|-|g' | sed 's|^-||')
234
+ SESSION_FILE="$HOME/.claude/projects/-${PROJECT_DIR}/${SESSION_ID}.jsonl"
221
235
 
222
- CTX_DISPLAY="${CTX_COLOR}${PERCENT_USED}%${RESET}"
236
+ if [ -f "$SESSION_FILE" ]; then
237
+ # Get the latest input token count from the session file (last 20 lines)
238
+ LATEST_TOKENS=$(tail -20 "$SESSION_FILE" | jq -r 'select(.message.usage) | .message.usage | ((.input_tokens // 0) + (.cache_read_input_tokens // 0))' 2>/dev/null | tail -1)
223
239
 
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}"
240
+ if [ -n "$LATEST_TOKENS" ] && [ "$LATEST_TOKENS" -gt 0 ] 2>/dev/null; then
241
+ PERCENT_USED=$((LATEST_TOKENS * 100 / MAX_CONTEXT))
242
+ # Cap at 100%
243
+ [ "$PERCENT_USED" -gt 100 ] && PERCENT_USED=100
244
+ fi
227
245
  fi
228
246
  fi
229
247
 
248
+ # Color based on usage level (using vibrant 256-color palette)
249
+ if [ "$PERCENT_USED" -ge 80 ]; then
250
+ CTX_COLOR="$CTX_RED" # Coral red - critical
251
+ elif [ "$PERCENT_USED" -ge 60 ]; then
252
+ CTX_COLOR="$CTX_ORANGE" # Peach - high usage
253
+ elif [ "$PERCENT_USED" -ge 40 ]; then
254
+ CTX_COLOR="$CTX_YELLOW" # Peach - moderate
255
+ else
256
+ CTX_COLOR="$CTX_GREEN" # Mint green - healthy
257
+ fi
258
+
259
+ CTX_DISPLAY="${CTX_COLOR}${PERCENT_USED}%${RESET}"
260
+
261
+ # Generate progress bar (8 chars wide for compactness)
262
+ CTX_BAR=$(progress_bar "$PERCENT_USED" 8)
263
+ CTX_BAR_DISPLAY="${DIM}[${RESET}${CTX_COLOR}${CTX_BAR}${RESET}${DIM}]${RESET}"
264
+
230
265
  # Parse cost
231
266
  TOTAL_COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
232
267
  COST_DISPLAY=""
@@ -241,64 +276,122 @@ if [ "$TOTAL_COST" != "0" ] && [ "$TOTAL_COST" != "null" ]; then
241
276
  fi
242
277
 
243
278
  # ============================================================================
244
- # Session Time Remaining (via ccusage if available)
279
+ # Session Time Remaining (Native - reads Claude Code's local JSONL files)
245
280
  # ============================================================================
281
+ # Claude Code uses 5-hour billing blocks. Block starts with first message activity.
282
+ # We scan ~/.claude/projects/*/*.jsonl to find the earliest message in the current
283
+ # 5-hour window and calculate remaining time.
284
+ #
285
+ # Algorithm (like ccusage):
286
+ # 1. Find all timestamps from JSONL files modified in last 5 hours
287
+ # 2. Sort and find the earliest timestamp >= (now - 5 hours)
288
+ # 3. That's the block start; block end = block start + 5 hours
289
+ # 4. Remaining = block end - now
290
+ #
291
+ # Optimization: Cache result for 60 seconds to avoid repeated scans
246
292
  SESSION_DISPLAY=""
247
293
  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
294
+ SESSION_CACHE="/tmp/agileflow-session-cache"
295
+ CACHE_MAX_AGE=60 # seconds
296
+ NOW_SEC=$(date +%s)
297
+
298
+ # Check cache first
299
+ CACHED_BLOCK_START=""
300
+ if [ -f "$SESSION_CACHE" ]; then
301
+ CACHE_DATA=$(cat "$SESSION_CACHE" 2>/dev/null)
302
+ CACHE_TIME=$(echo "$CACHE_DATA" | cut -d: -f1)
303
+ CACHE_VALUE=$(echo "$CACHE_DATA" | cut -d: -f2)
304
+ if [ -n "$CACHE_TIME" ] && [ $((NOW_SEC - CACHE_TIME)) -lt "$CACHE_MAX_AGE" ] 2>/dev/null; then
305
+ CACHED_BLOCK_START="$CACHE_VALUE"
306
+ fi
307
+ fi
308
+
309
+ BLOCK_START_SEC=""
310
+
311
+ if [ -n "$CACHED_BLOCK_START" ] && [ "$CACHED_BLOCK_START" != "none" ]; then
312
+ BLOCK_START_SEC="$CACHED_BLOCK_START"
313
+ elif [ "$CACHED_BLOCK_START" != "none" ]; then
314
+ # Need to scan - use Python for speed (handles ISO timestamps natively)
315
+ CLAUDE_DATA_DIR="$HOME/.claude/projects"
316
+ BLOCK_DURATION=$((5 * 60 * 60)) # 5 hours in seconds
317
+ WINDOW_START=$((NOW_SEC - BLOCK_DURATION))
318
+
319
+ if [ -d "$CLAUDE_DATA_DIR" ]; then
320
+ BLOCK_START_SEC=$(python3 - "$CLAUDE_DATA_DIR" "$WINDOW_START" <<'PYTHON' 2>/dev/null
321
+ import sys, os, json, glob
322
+ from datetime import datetime
323
+
324
+ data_dir = sys.argv[1]
325
+ window_start = int(sys.argv[2])
326
+
327
+ earliest = None
328
+ # Only check files modified in last 5 hours (300 minutes)
329
+ for jsonl_path in glob.glob(f"{data_dir}/*/*.jsonl"):
330
+ try:
331
+ mtime = os.path.getmtime(jsonl_path)
332
+ if mtime < window_start:
333
+ continue # Skip old files
334
+ with open(jsonl_path, 'r') as f:
335
+ for line in f:
336
+ if '"timestamp"' not in line:
337
+ continue
338
+ try:
339
+ data = json.loads(line)
340
+ ts_str = data.get('timestamp', '')
341
+ if ts_str:
342
+ # Parse ISO timestamp
343
+ ts_str = ts_str.replace('Z', '+00:00')
344
+ dt = datetime.fromisoformat(ts_str)
345
+ epoch = int(dt.timestamp())
346
+ if epoch >= window_start:
347
+ if earliest is None or epoch < earliest:
348
+ earliest = epoch
349
+ except:
350
+ pass
351
+ except:
352
+ pass
353
+
354
+ if earliest:
355
+ print(earliest)
356
+ PYTHON
357
+ )
358
+
359
+ # Cache the result
360
+ if [ -n "$BLOCK_START_SEC" ] && [ "$BLOCK_START_SEC" -gt 0 ] 2>/dev/null; then
361
+ echo "${NOW_SEC}:${BLOCK_START_SEC}" > "$SESSION_CACHE"
362
+ else
363
+ echo "${NOW_SEC}:none" > "$SESSION_CACHE"
299
364
  fi
300
365
  fi
301
366
  fi
367
+
368
+ # Calculate remaining time if we found a block start
369
+ if [ -n "$BLOCK_START_SEC" ] && [ "$BLOCK_START_SEC" -gt 0 ] 2>/dev/null; then
370
+ BLOCK_DURATION=$((5 * 60 * 60))
371
+ BLOCK_END_SEC=$((BLOCK_START_SEC + BLOCK_DURATION))
372
+ REMAINING=$((BLOCK_END_SEC - NOW_SEC))
373
+ [ "$REMAINING" -lt 0 ] && REMAINING=0
374
+
375
+ # Format remaining time
376
+ RH=$((REMAINING / 3600))
377
+ RM=$(((REMAINING % 3600) / 60))
378
+
379
+ # Color based on time remaining (using vibrant 256-color palette)
380
+ if [ "$RH" -eq 0 ] && [ "$RM" -lt 30 ]; then
381
+ SESSION_COLOR="$SESSION_RED" # Light pink - critical
382
+ elif [ "$RH" -eq 0 ]; then
383
+ SESSION_COLOR="$SESSION_YELLOW" # Light yellow - getting low
384
+ else
385
+ SESSION_COLOR="$SESSION_GREEN" # Light green - plenty of time
386
+ fi
387
+
388
+ # Build compact display: "⏱2h15m" or "⏱45m"
389
+ if [ "$RH" -gt 0 ]; then
390
+ SESSION_DISPLAY="${SESSION_COLOR}⏱${RH}h${RM}m${RESET}"
391
+ else
392
+ SESSION_DISPLAY="${SESSION_COLOR}⏱${RM}m${RESET}"
393
+ fi
394
+ fi
302
395
  fi
303
396
 
304
397
  # ============================================================================
@@ -440,8 +533,8 @@ if [ "$SHOW_AGILEFLOW" = "true" ] && [ -n "$AGILEFLOW_DISPLAY" ]; then
440
533
  OUTPUT="${AGILEFLOW_DISPLAY}"
441
534
  fi
442
535
 
443
- # Model with subtle styling (if enabled)
444
- if [ "$SHOW_MODEL" = "true" ]; then
536
+ # Model with subtle styling (if enabled and available)
537
+ if [ "$SHOW_MODEL" = "true" ] && [ -n "$MODEL_DISPLAY" ]; then
445
538
  [ -n "$OUTPUT" ] && OUTPUT="${OUTPUT}${SEP}"
446
539
  OUTPUT="${OUTPUT}${DIM}[${RESET}${BOLD}${MODEL_DISPLAY}${RESET}${DIM}]${RESET}"
447
540
  fi