aiwcli 0.10.2 → 0.11.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 (249) hide show
  1. package/bin/run.js +1 -1
  2. package/dist/commands/clear.d.ts +11 -6
  3. package/dist/commands/clear.js +229 -381
  4. package/dist/commands/init/index.d.ts +1 -17
  5. package/dist/commands/init/index.js +22 -107
  6. package/dist/lib/gitignore-manager.d.ts +32 -0
  7. package/dist/lib/gitignore-manager.js +141 -2
  8. package/dist/lib/template-installer.d.ts +7 -12
  9. package/dist/lib/template-installer.js +69 -193
  10. package/dist/lib/template-settings-reconstructor.d.ts +35 -0
  11. package/dist/lib/template-settings-reconstructor.js +130 -0
  12. package/dist/templates/CLAUDE.md +8 -8
  13. package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
  14. package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
  15. package/dist/templates/_shared/.claude/settings.json +7 -7
  16. package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
  17. package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
  18. package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
  19. package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
  20. package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
  21. package/dist/templates/_shared/hooks-ts/session_end.ts +104 -0
  22. package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
  23. package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
  24. package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
  25. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
  26. package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
  27. package/dist/templates/_shared/lib-ts/base/atomic-write.ts +138 -0
  28. package/dist/templates/_shared/lib-ts/base/constants.ts +306 -0
  29. package/dist/templates/_shared/lib-ts/base/git-state.ts +58 -0
  30. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +439 -0
  31. package/dist/templates/_shared/lib-ts/base/inference.ts +252 -0
  32. package/dist/templates/_shared/lib-ts/base/logger.ts +250 -0
  33. package/dist/templates/_shared/lib-ts/base/state-io.ts +116 -0
  34. package/dist/templates/_shared/lib-ts/base/stop-words.ts +184 -0
  35. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +162 -0
  36. package/dist/templates/_shared/lib-ts/base/utils.ts +184 -0
  37. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +438 -0
  38. package/dist/templates/_shared/lib-ts/context/context-selector.ts +515 -0
  39. package/dist/templates/_shared/lib-ts/context/context-store.ts +707 -0
  40. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +316 -0
  41. package/dist/templates/_shared/lib-ts/context/task-tracker.ts +185 -0
  42. package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +216 -0
  43. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +159 -0
  44. package/dist/templates/_shared/lib-ts/package.json +21 -0
  45. package/dist/templates/_shared/lib-ts/templates/formatters.ts +104 -0
  46. package/dist/templates/_shared/{lib/templates/plan_context.py → lib-ts/templates/plan-context.ts} +14 -22
  47. package/dist/templates/_shared/lib-ts/tsconfig.json +13 -0
  48. package/dist/templates/_shared/lib-ts/types.ts +164 -0
  49. package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
  50. package/dist/templates/_shared/scripts/resume_handoff.ts +321 -0
  51. package/dist/templates/_shared/scripts/save_handoff.ts +359 -0
  52. package/dist/templates/_shared/scripts/status_line.ts +733 -0
  53. package/dist/templates/cc-native/.claude/settings.json +175 -185
  54. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
  55. package/dist/templates/cc-native/_cc-native/agents/ARCH-EVOLUTION.md +63 -0
  56. package/dist/templates/cc-native/_cc-native/agents/ARCH-PATTERNS.md +62 -0
  57. package/dist/templates/cc-native/_cc-native/agents/ARCH-STRUCTURE.md +63 -0
  58. package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-CHAIN-TRACER.md → ASSUMPTION-TRACER.md} +6 -10
  59. package/dist/templates/cc-native/_cc-native/agents/CLARITY-AUDITOR.md +6 -10
  60. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +74 -3
  61. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-FEASIBILITY.md +67 -0
  62. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-GAPS.md +71 -0
  63. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-ORDERING.md +63 -0
  64. package/dist/templates/cc-native/_cc-native/agents/CONSTRAINT-VALIDATOR.md +73 -0
  65. package/dist/templates/cc-native/_cc-native/agents/DESIGN-ADR-VALIDATOR.md +62 -0
  66. package/dist/templates/cc-native/_cc-native/agents/DESIGN-SCALE-MATCHER.md +65 -0
  67. package/dist/templates/cc-native/_cc-native/agents/DEVILS-ADVOCATE.md +6 -9
  68. package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-PHILOSOPHY.md +87 -0
  69. package/dist/templates/cc-native/_cc-native/agents/HANDOFF-READINESS.md +5 -9
  70. package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY-DETECTOR.md → HIDDEN-COMPLEXITY.md} +6 -10
  71. package/dist/templates/cc-native/_cc-native/agents/INCREMENTAL-DELIVERY.md +67 -0
  72. package/dist/templates/cc-native/_cc-native/agents/PLAN-ORCHESTRATOR.md +91 -18
  73. package/dist/templates/cc-native/_cc-native/agents/RISK-DEPENDENCY.md +63 -0
  74. package/dist/templates/cc-native/_cc-native/agents/RISK-FMEA.md +67 -0
  75. package/dist/templates/cc-native/_cc-native/agents/RISK-PREMORTEM.md +72 -0
  76. package/dist/templates/cc-native/_cc-native/agents/RISK-REVERSIBILITY.md +75 -0
  77. package/dist/templates/cc-native/_cc-native/agents/SCOPE-BOUNDARY.md +78 -0
  78. package/dist/templates/cc-native/_cc-native/agents/SIMPLICITY-GUARDIAN.md +5 -9
  79. package/dist/templates/cc-native/_cc-native/agents/SKEPTIC.md +16 -12
  80. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-BEHAVIOR-AUDITOR.md +62 -0
  81. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-CHARACTERIZATION.md +72 -0
  82. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-FIRST-VALIDATOR.md +62 -0
  83. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-PYRAMID-ANALYZER.md +62 -0
  84. package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-COSTS.md +68 -0
  85. package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-STAKEHOLDERS.md +66 -0
  86. package/dist/templates/cc-native/_cc-native/agents/VERIFY-COVERAGE.md +75 -0
  87. package/dist/templates/cc-native/_cc-native/agents/VERIFY-STRENGTH.md +70 -0
  88. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
  89. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
  90. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +921 -0
  91. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
  92. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +157 -0
  93. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +709 -0
  94. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
  95. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +124 -0
  96. package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
  97. package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
  98. package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
  99. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +119 -0
  100. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +162 -0
  101. package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
  102. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +249 -0
  103. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +155 -0
  104. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
  105. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +106 -0
  106. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
  107. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
  108. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +243 -0
  109. package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
  110. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +310 -0
  111. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
  112. package/dist/templates/cc-native/_cc-native/plan-review.config.json +12 -16
  113. package/oclif.manifest.json +1 -1
  114. package/package.json +1 -1
  115. package/dist/lib/template-merger.d.ts +0 -47
  116. package/dist/lib/template-merger.js +0 -162
  117. package/dist/templates/_shared/hooks/__init__.py +0 -16
  118. package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  119. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  120. package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
  121. package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
  122. package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
  123. package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
  124. package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
  125. package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
  126. package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
  127. package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
  128. package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
  129. package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  130. package/dist/templates/_shared/hooks/archive_plan.py +0 -169
  131. package/dist/templates/_shared/hooks/context_monitor.py +0 -270
  132. package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
  133. package/dist/templates/_shared/hooks/pre_compact.py +0 -104
  134. package/dist/templates/_shared/hooks/session_end.py +0 -173
  135. package/dist/templates/_shared/hooks/session_start.py +0 -206
  136. package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
  137. package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
  138. package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
  139. package/dist/templates/_shared/lib/__init__.py +0 -1
  140. package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  141. package/dist/templates/_shared/lib/base/__init__.py +0 -65
  142. package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
  143. package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
  144. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  145. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  146. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  147. package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
  148. package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
  149. package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
  150. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  151. package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
  152. package/dist/templates/_shared/lib/base/constants.py +0 -358
  153. package/dist/templates/_shared/lib/base/hook_utils.py +0 -341
  154. package/dist/templates/_shared/lib/base/inference.py +0 -318
  155. package/dist/templates/_shared/lib/base/logger.py +0 -291
  156. package/dist/templates/_shared/lib/base/stop_words.py +0 -213
  157. package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
  158. package/dist/templates/_shared/lib/base/utils.py +0 -242
  159. package/dist/templates/_shared/lib/context/__init__.py +0 -102
  160. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  161. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  162. package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
  163. package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
  164. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  165. package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
  166. package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
  167. package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
  168. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  169. package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
  170. package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
  171. package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
  172. package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
  173. package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
  174. package/dist/templates/_shared/lib/context/context_selector.py +0 -508
  175. package/dist/templates/_shared/lib/context/context_store.py +0 -653
  176. package/dist/templates/_shared/lib/context/plan_manager.py +0 -204
  177. package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
  178. package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
  179. package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
  180. package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
  181. package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
  182. package/dist/templates/_shared/lib/templates/README.md +0 -206
  183. package/dist/templates/_shared/lib/templates/__init__.py +0 -36
  184. package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
  185. package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
  186. package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
  187. package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
  188. package/dist/templates/_shared/lib/templates/formatters.py +0 -146
  189. package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
  190. package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
  191. package/dist/templates/_shared/scripts/save_handoff.py +0 -357
  192. package/dist/templates/_shared/scripts/status_line.py +0 -701
  193. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
  194. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
  195. package/dist/templates/cc-native/MIGRATION.md +0 -86
  196. package/dist/templates/cc-native/_cc-native/agents/ACCESSIBILITY-TESTER.md +0 -79
  197. package/dist/templates/cc-native/_cc-native/agents/ARCHITECT-REVIEWER.md +0 -48
  198. package/dist/templates/cc-native/_cc-native/agents/CODE-REVIEWER.md +0 -70
  199. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-CHECKER.md +0 -59
  200. package/dist/templates/cc-native/_cc-native/agents/CONTEXT-EXTRACTOR.md +0 -92
  201. package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-REVIEWER.md +0 -51
  202. package/dist/templates/cc-native/_cc-native/agents/FEASIBILITY-ANALYST.md +0 -57
  203. package/dist/templates/cc-native/_cc-native/agents/FRESH-PERSPECTIVE.md +0 -54
  204. package/dist/templates/cc-native/_cc-native/agents/INCENTIVE-MAPPER.md +0 -61
  205. package/dist/templates/cc-native/_cc-native/agents/PENETRATION-TESTER.md +0 -79
  206. package/dist/templates/cc-native/_cc-native/agents/PERFORMANCE-ENGINEER.md +0 -75
  207. package/dist/templates/cc-native/_cc-native/agents/PRECEDENT-FINDER.md +0 -70
  208. package/dist/templates/cc-native/_cc-native/agents/REVERSIBILITY-ANALYST.md +0 -61
  209. package/dist/templates/cc-native/_cc-native/agents/RISK-ASSESSOR.md +0 -58
  210. package/dist/templates/cc-native/_cc-native/agents/SECOND-ORDER-ANALYST.md +0 -61
  211. package/dist/templates/cc-native/_cc-native/agents/STAKEHOLDER-ADVOCATE.md +0 -55
  212. package/dist/templates/cc-native/_cc-native/agents/TRADE-OFF-ILLUMINATOR.md +0 -204
  213. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  214. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  215. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
  216. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
  217. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
  218. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
  219. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
  220. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -869
  221. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
  222. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
  223. package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
  224. package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
  225. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  226. package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
  227. package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
  228. package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
  229. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  230. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  231. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  232. package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
  233. package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
  234. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
  235. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
  236. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  237. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  238. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  239. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  240. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  241. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
  242. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
  243. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
  244. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
  245. package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
  246. package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1027
  247. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  248. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
  249. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
@@ -0,0 +1,733 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Status line for Claude Code sessions.
4
+ *
5
+ * Renders context window usage and git status with ANSI colors.
6
+ * Optionally persists context_window data to the session's state.json.
7
+ *
8
+ * Ported from status_line.py — context and git sections only.
9
+ *
10
+ * Usage: echo '{"session_id":"...","model":{"display_name":"Opus"},...}' | bun status_line.ts
11
+ */
12
+ import { execFileSync } from "node:child_process";
13
+ import * as fs from "node:fs";
14
+ import { homedir } from "node:os";
15
+ import * as path from "node:path";
16
+
17
+ import { CONTEXT_BASELINE_TOKENS } from "../lib-ts/base/hook-utils.js";
18
+ import { getContext, getContextBySessionId, loadState, saveState } from "../lib-ts/context/context-store.js";
19
+ import { findLatestPlan } from "../lib-ts/context/plan-manager.js";
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Path setup
23
+ // ---------------------------------------------------------------------------
24
+ const _SCRIPT_DIR = path.dirname(new URL(import.meta.url).pathname);
25
+ const OUTPUT_DIR = path.join(".", "_output");
26
+ const CACHE_DIR = path.join(OUTPUT_DIR, "cache");
27
+ const STATUSLINE_CACHE = path.join(CACHE_DIR, ".statusline-cache.json");
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // NO_COLOR support (https://no-color.org)
31
+ // ---------------------------------------------------------------------------
32
+ const NO_COLOR = Boolean(process.env.NO_COLOR);
33
+
34
+ const RESET = NO_COLOR ? "" : "\u001B[0m";
35
+
36
+ // Structural
37
+ const SLATE_300 = NO_COLOR ? "" : "\u001B[38;2;203;213;225m";
38
+ const SLATE_400 = NO_COLOR ? "" : "\u001B[38;2;148;163;184m";
39
+ const SLATE_500 = NO_COLOR ? "" : "\u001B[38;2;100;116;139m";
40
+ const SLATE_600 = NO_COLOR ? "" : "\u001B[38;2;71;85;105m";
41
+
42
+ // Semantic
43
+ const EMERALD = NO_COLOR ? "" : "\u001B[38;2;74;222;128m";
44
+ const ROSE = NO_COLOR ? "" : "\u001B[38;2;251;113;133m";
45
+ const AMBER = NO_COLOR ? "" : "\u001B[38;2;251;191;36m";
46
+
47
+ // Context colors
48
+ const CTX_PRIMARY = NO_COLOR ? "" : "\u001B[38;2;129;140;248m";
49
+ const CTX_SECONDARY = NO_COLOR ? "" : "\u001B[38;2;165;180;252m";
50
+ const CTX_ACCENT = NO_COLOR ? "" : "\u001B[38;2;139;92;246m";
51
+ const CTX_BUCKET_EMPTY = NO_COLOR ? "" : "\u001B[38;2;75;82;95m";
52
+
53
+ // Git colors
54
+ const GIT_PRIMARY = NO_COLOR ? "" : "\u001B[38;2;56;189;248m";
55
+ const GIT_VALUE = NO_COLOR ? "" : "\u001B[38;2;186;230;253m";
56
+ const GIT_DIR = NO_COLOR ? "" : "\u001B[38;2;147;197;253m";
57
+ const GIT_CLEAN = NO_COLOR ? "" : "\u001B[38;2;125;211;252m";
58
+ const GIT_MODIFIED = NO_COLOR ? "" : "\u001B[38;2;96;165;250m";
59
+ const GIT_ADDED = NO_COLOR ? "" : "\u001B[38;2;59;130;246m";
60
+ const GIT_STASH = NO_COLOR ? "" : "\u001B[38;2;165;180;252m";
61
+ const GIT_AGE_FRESH = NO_COLOR ? "" : "\u001B[38;2;125;211;252m";
62
+ const GIT_AGE_RECENT = NO_COLOR ? "" : "\u001B[38;2;96;165;250m";
63
+ const GIT_AGE_STALE = NO_COLOR ? "" : "\u001B[38;2;59;130;246m";
64
+ const GIT_AGE_OLD = NO_COLOR ? "" : "\u001B[38;2;99;102;241m";
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Display modes
68
+ // ---------------------------------------------------------------------------
69
+
70
+ function getTerminalWidth(): number {
71
+ const colsEnv = process.env.COLUMNS;
72
+ if (colsEnv) {
73
+ const cols = Number.parseInt(colsEnv, 10);
74
+ if (cols > 0) return cols;
75
+ }
76
+
77
+ try {
78
+ if (process.stdout.columns && process.stdout.columns > 0) {
79
+ return process.stdout.columns;
80
+ }
81
+ } catch { /* ignore */ }
82
+
83
+ return 80;
84
+ }
85
+
86
+ function getDisplayMode(width: number): string {
87
+ if (width < 35) return "nano";
88
+ if (width < 55) return "micro";
89
+ if (width < 80) return "mini";
90
+ return "normal";
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Color helpers
95
+ // ---------------------------------------------------------------------------
96
+
97
+ function getBucketColor(pos: number, maxPos: number): string {
98
+ if (NO_COLOR) return "";
99
+ const pct = Math.floor((pos * 100) / maxPos);
100
+
101
+ let b: number; let g: number; let r: number;
102
+
103
+ if (pct <= 33) {
104
+ r = 74 + Math.floor(((250 - 74) * pct) / 33);
105
+ g = 222 + Math.floor(((204 - 222) * pct) / 33);
106
+ b = 128 + Math.floor(((21 - 128) * pct) / 33);
107
+ } else if (pct <= 66) {
108
+ const t = pct - 33;
109
+ r = 250 + Math.floor(((251 - 250) * t) / 33);
110
+ g = 204 + Math.floor(((146 - 204) * t) / 33);
111
+ b = 21 + Math.floor(((60 - 21) * t) / 33);
112
+ } else {
113
+ const t = pct - 66;
114
+ r = 251 + Math.floor(((239 - 251) * t) / 34);
115
+ g = 146 + Math.floor(((68 - 146) * t) / 34);
116
+ b = 60 + Math.floor(((68 - 60) * t) / 34);
117
+ }
118
+
119
+ return `\u001B[38;2;${r};${g};${b}m`;
120
+ }
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // Context bar rendering
124
+ // ---------------------------------------------------------------------------
125
+
126
+ function renderContextBar(width: number, pct: number): [string, string] {
127
+ pct = Math.max(0, Math.min(100, pct));
128
+ const filled = Math.floor((pct * width) / 100);
129
+ let lastColor = EMERALD;
130
+ const parts: string[] = [];
131
+
132
+ for (let i = 1; i <= width; i++) {
133
+ if (i <= filled) {
134
+ const color = getBucketColor(i, width);
135
+ lastColor = color;
136
+ parts.push(`${color}\u26C1${RESET}`);
137
+ } else {
138
+ parts.push(`${CTX_BUCKET_EMPTY}\u26C1${RESET}`);
139
+ }
140
+
141
+ if (width > 8) {
142
+ parts.push(" ");
143
+ }
144
+ }
145
+
146
+ return [parts.join("").trimEnd(), lastColor];
147
+ }
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // Separator
151
+ // ---------------------------------------------------------------------------
152
+
153
+ const SEPARATOR = `${SLATE_600}${"─".repeat(72)}${RESET}`;
154
+
155
+ // ---------------------------------------------------------------------------
156
+ // Context section
157
+ // ---------------------------------------------------------------------------
158
+
159
+ function shortenModel(name: string): string {
160
+ const replacements: [string, string][] = [
161
+ ["claude-opus-4-6", "opus-4.6"],
162
+ ["claude-opus-4-5", "opus-4.5"],
163
+ ["claude-sonnet-4", "sonnet-4"],
164
+ ["claude-3-5-sonnet", "sonnet-3.5"],
165
+ ["claude-3-5-haiku", "haiku-3.5"],
166
+ ["claude-", ""],
167
+ ];
168
+ let result = name;
169
+ for (const [old, replacement] of replacements) {
170
+ result = result.replace(old, replacement);
171
+ }
172
+
173
+ return result;
174
+ }
175
+
176
+ function renderContext(
177
+ mode: string,
178
+ contextPct: number,
179
+ contextK: number,
180
+ maxK: number,
181
+ timeDisplay: string,
182
+ modelName: string,
183
+ ): void {
184
+ let pctColor: string;
185
+ if (contextPct <= 33) pctColor = EMERALD;
186
+ else if (contextPct <= 66) pctColor = AMBER;
187
+ else pctColor = ROSE;
188
+
189
+ const shortModel = shortenModel(modelName);
190
+
191
+ switch (mode) {
192
+ case "micro": {
193
+ const [bar] = renderContextBar(6, contextPct);
194
+ console.log(
195
+ `${CTX_PRIMARY}\u25C9${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
196
+ `${SLATE_600}\u2502${RESET} ` +
197
+ `${bar} ${pctColor}${contextPct}%${RESET} ${SLATE_500}(${contextK}k)${RESET} ` +
198
+ `${CTX_ACCENT}\u23F1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
199
+ );
200
+
201
+ break;
202
+ }
203
+
204
+ case "mini": {
205
+ const [bar] = renderContextBar(8, contextPct);
206
+ console.log(
207
+ `${CTX_PRIMARY}\u25C9${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
208
+ `${SLATE_600}\u2502${RESET} ` +
209
+ `${CTX_SECONDARY}CTX:${RESET} ${bar} ` +
210
+ `${pctColor}${contextPct}%${RESET} ${SLATE_500}(${contextK}k/${maxK}k)${RESET} ` +
211
+ `${CTX_ACCENT}\u23F1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
212
+ );
213
+
214
+ break;
215
+ }
216
+
217
+ case "nano": {
218
+ const [bar] = renderContextBar(5, contextPct);
219
+ console.log(
220
+ `${CTX_PRIMARY}\u25C9${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
221
+ `${bar} ${pctColor}${contextPct}%${RESET} ` +
222
+ `${CTX_ACCENT}\u23F1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
223
+ );
224
+
225
+ break;
226
+ }
227
+
228
+ default: {
229
+ const [bar, lastColor] = renderContextBar(16, contextPct);
230
+ console.log(
231
+ `${CTX_PRIMARY}\u25C9${RESET} ${CTX_SECONDARY}Model:${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
232
+ `${SLATE_600}\u2502${RESET} ` +
233
+ `${CTX_SECONDARY}Context:${RESET} ${bar} ` +
234
+ `${lastColor}${contextPct}%${RESET} ${SLATE_500}(${contextK}k/${maxK}k)${RESET} ` +
235
+ `${SLATE_600}\u2502${RESET} ` +
236
+ `${CTX_ACCENT}\u23F1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
237
+ );
238
+ }
239
+ }
240
+
241
+ console.log(SEPARATOR);
242
+ }
243
+
244
+ // ---------------------------------------------------------------------------
245
+ // Git status
246
+ // ---------------------------------------------------------------------------
247
+
248
+ interface GitStatus {
249
+ age_color: string;
250
+ age_display: string;
251
+ ahead: number;
252
+ behind: number;
253
+ branch: string;
254
+ modified: number;
255
+ staged: number;
256
+ stash_count: number;
257
+ untracked: number;
258
+ }
259
+
260
+ function runGit(args: string[], cwd: string, timeout = 2000): null | string {
261
+ try {
262
+ const result = execFileSync("git", args, {
263
+ cwd,
264
+ timeout,
265
+ encoding: "utf-8",
266
+ stdio: ["pipe", "pipe", "pipe"],
267
+ windowsHide: true,
268
+ });
269
+ return result.trim();
270
+ } catch {
271
+ return null;
272
+ }
273
+ }
274
+
275
+ function getGitStatus(cwd: string): GitStatus | null {
276
+ if (runGit(["rev-parse", "--git-dir"], cwd) === null) {
277
+ return null;
278
+ }
279
+
280
+ const status: GitStatus = {
281
+ branch: "detached",
282
+ modified: 0,
283
+ staged: 0,
284
+ untracked: 0,
285
+ stash_count: 0,
286
+ ahead: 0,
287
+ behind: 0,
288
+ age_display: "",
289
+ age_color: GIT_AGE_FRESH,
290
+ };
291
+
292
+ // Branch
293
+ const branch = runGit(["branch", "--show-current"], cwd);
294
+ if (branch) status.branch = branch;
295
+
296
+ // Modified files
297
+ const diff = runGit(["diff", "--name-only"], cwd);
298
+ if (diff) status.modified = diff.split("\n").filter(Boolean).length;
299
+
300
+ // Staged files
301
+ const staged = runGit(["diff", "--cached", "--name-only"], cwd);
302
+ if (staged) status.staged = staged.split("\n").filter(Boolean).length;
303
+
304
+ // Untracked files
305
+ const untracked = runGit(["ls-files", "--others", "--exclude-standard"], cwd);
306
+ if (untracked) status.untracked = untracked.split("\n").filter(Boolean).length;
307
+
308
+ // Stash count
309
+ const stash = runGit(["stash", "list"], cwd);
310
+ if (stash) status.stash_count = stash.split("\n").filter(Boolean).length;
311
+
312
+ // Ahead/behind
313
+ const ab = runGit(["rev-list", "--left-right", "--count", "HEAD...@{u}"], cwd);
314
+ if (ab) {
315
+ const parts = ab.split(/\s+/);
316
+ if (parts.length >= 2) {
317
+ status.ahead = Number.parseInt(parts[0]!, 10) || 0;
318
+ status.behind = Number.parseInt(parts[1]!, 10) || 0;
319
+ }
320
+ }
321
+
322
+ // Commit age
323
+ const log = runGit(["log", "-1", "--format=%ct"], cwd);
324
+ if (log) {
325
+ try {
326
+ const lastEpoch = Number.parseInt(log, 10);
327
+ const nowEpoch = Math.floor(Date.now() / 1000);
328
+ const ageSec = nowEpoch - lastEpoch;
329
+ const ageMin = Math.floor(ageSec / 60);
330
+ const ageHrs = Math.floor(ageSec / 3600);
331
+ const ageDays = Math.floor(ageSec / 86_400);
332
+
333
+ if (ageMin < 1) {
334
+ status.age_display = "now";
335
+ status.age_color = GIT_AGE_FRESH;
336
+ } else if (ageHrs < 1) {
337
+ status.age_display = `${ageMin}m`;
338
+ status.age_color = GIT_AGE_FRESH;
339
+ } else if (ageHrs < 24) {
340
+ status.age_display = `${ageHrs}h`;
341
+ status.age_color = GIT_AGE_RECENT;
342
+ } else if (ageDays < 7) {
343
+ status.age_display = `${ageDays}d`;
344
+ status.age_color = GIT_AGE_STALE;
345
+ } else {
346
+ status.age_display = `${ageDays}d`;
347
+ status.age_color = GIT_AGE_OLD;
348
+ }
349
+ } catch { /* ignore */ }
350
+ }
351
+
352
+ return status;
353
+ }
354
+
355
+ function renderGit(mode: string, git: GitStatus, dirName: string): void {
356
+ const totalChanged = git.modified + git.staged;
357
+ const statusIcon = (totalChanged > 0 || git.untracked > 0) ? "*" : "\u2713";
358
+
359
+ switch (mode) {
360
+ case "micro": {
361
+ let line = `${GIT_PRIMARY}\u25C8${RESET} ${GIT_DIR}${dirName}${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
362
+ if (git.age_display) {
363
+ line += ` ${git.age_color}${git.age_display}${RESET}`;
364
+ }
365
+
366
+ line += " ";
367
+ line += statusIcon === "\u2713" ? `${GIT_CLEAN}${statusIcon}${RESET}` : `${GIT_MODIFIED}${statusIcon}${totalChanged}${RESET}`;
368
+ console.log(line);
369
+
370
+ break;
371
+ }
372
+
373
+ case "mini": {
374
+ let line =
375
+ `${GIT_PRIMARY}\u25C8${RESET} ${GIT_DIR}${dirName}${RESET} ` +
376
+ `${SLATE_600}\u2502${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
377
+ if (git.age_display) {
378
+ line += ` ${SLATE_600}\u2502${RESET} ${git.age_color}${git.age_display}${RESET}`;
379
+ }
380
+
381
+ line += ` ${SLATE_600}\u2502${RESET} `;
382
+ if (statusIcon === "\u2713") {
383
+ line += `${GIT_CLEAN}${statusIcon}${RESET}`;
384
+ } else {
385
+ line += `${GIT_MODIFIED}${statusIcon}${totalChanged}${RESET}`;
386
+ if (git.untracked > 0) {
387
+ line += ` ${GIT_ADDED}+${git.untracked}${RESET}`;
388
+ }
389
+ }
390
+
391
+ console.log(line);
392
+
393
+ break;
394
+ }
395
+
396
+ case "nano": {
397
+ let line = `${GIT_PRIMARY}\u25C8${RESET} ${GIT_DIR}${dirName}${RESET} ${GIT_VALUE}${git.branch}${RESET} `;
398
+ line += statusIcon === "\u2713" ? `${GIT_CLEAN}\u2713${RESET}` : `${GIT_MODIFIED}*${totalChanged}${RESET}`;
399
+ console.log(line);
400
+
401
+ break;
402
+ }
403
+
404
+ default: {
405
+ let line =
406
+ `${GIT_PRIMARY}\u25C8${RESET} ${GIT_PRIMARY}PWD:${RESET} ${GIT_DIR}${dirName}${RESET} ` +
407
+ `${SLATE_600}\u2502${RESET} ` +
408
+ `${GIT_PRIMARY}Branch:${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
409
+ if (git.age_display) {
410
+ line += ` ${SLATE_600}\u2502${RESET} ${GIT_PRIMARY}Age:${RESET} ${git.age_color}${git.age_display}${RESET}`;
411
+ }
412
+
413
+ if (git.stash_count > 0) {
414
+ line += ` ${SLATE_600}\u2502${RESET} ${GIT_PRIMARY}Stash:${RESET} ${GIT_STASH}${git.stash_count}${RESET}`;
415
+ }
416
+
417
+ if (totalChanged > 0 || git.untracked > 0) {
418
+ line += ` ${SLATE_600}\u2502${RESET} `;
419
+ if (totalChanged > 0) {
420
+ line += `${GIT_PRIMARY}Mod:${RESET} ${GIT_MODIFIED}${totalChanged}${RESET}`;
421
+ }
422
+
423
+ if (git.untracked > 0) {
424
+ if (totalChanged > 0) line += " ";
425
+ line += `${GIT_PRIMARY}New:${RESET} ${GIT_ADDED}${git.untracked}${RESET}`;
426
+ }
427
+ } else {
428
+ line += ` ${SLATE_600}\u2502${RESET} ${GIT_CLEAN}\u2713 clean${RESET}`;
429
+ }
430
+
431
+ if (git.ahead > 0 || git.behind > 0) {
432
+ line += ` ${SLATE_600}\u2502${RESET} ${GIT_PRIMARY}Sync:${RESET} `;
433
+ if (git.ahead > 0) {
434
+ line += `${GIT_CLEAN}\u2191${git.ahead}${RESET}`;
435
+ }
436
+
437
+ if (git.behind > 0) {
438
+ line += `${GIT_STASH}\u2193${git.behind}${RESET}`;
439
+ }
440
+ }
441
+
442
+ console.log(line);
443
+ }
444
+ }
445
+ }
446
+
447
+ // ---------------------------------------------------------------------------
448
+ // Context manager line (line 3)
449
+ // ---------------------------------------------------------------------------
450
+
451
+ function findActivePlanFile(): null | string {
452
+ try {
453
+ const plansDir = path.join(homedir(), ".claude", "plans");
454
+ if (!fs.existsSync(plansDir)) return null;
455
+ const planFiles = fs.readdirSync(plansDir)
456
+ .filter(f => f.endsWith(".md"))
457
+ .map(f => {
458
+ const fullPath = path.join(plansDir, f);
459
+ return { path: fullPath, mtime: fs.statSync(fullPath).mtimeMs };
460
+ })
461
+ .sort((a, b) => b.mtime - a.mtime);
462
+ return planFiles.length > 0 ? planFiles[0]!.path : null;
463
+ } catch {
464
+ return null;
465
+ }
466
+ }
467
+
468
+ function renderContextManager(
469
+ mode: string,
470
+ contextId: string,
471
+ contextState: null | Record<string, any>,
472
+ ): void {
473
+ // Strip YYMMDD-HHMM- timestamp prefix from context ID for display
474
+ let displayId = contextId.replace(/^\d{6}-\d{4}-/, "");
475
+ if (!displayId) displayId = contextId;
476
+
477
+ // Truncate display_id per mode
478
+ const maxIdLen: Record<string, number> = { nano: 14, micro: 18, mini: 22, normal: 30 };
479
+ const maxLen = maxIdLen[mode] ?? 30;
480
+ let truncatedId = displayId.slice(0, maxLen);
481
+ if (displayId.length > maxLen) truncatedId += "\u2026";
482
+
483
+ // Read state fields
484
+ const stateMode = contextState?.mode ?? "idle";
485
+ const statePlanPath = contextState?.plan_path ?? null;
486
+
487
+ // Detect plan mode heuristic
488
+ const activePlanFile = findActivePlanFile();
489
+ const isPlanning = stateMode === "idle" && activePlanFile !== null;
490
+
491
+ // Build mode badge
492
+ let modeBadge = "";
493
+ if (isPlanning) {
494
+ const label = mode === "nano" ? "Plan" : "Planning";
495
+ modeBadge = ` ${SLATE_600}\u2502${RESET} ${CTX_SECONDARY}Mode:${RESET} ${AMBER}${label}${RESET}`;
496
+ } else if (stateMode === "has_plan") {
497
+ const label = mode === "nano" ? "Ready" : "Plan Ready";
498
+ modeBadge = ` ${SLATE_600}\u2502${RESET} ${CTX_SECONDARY}Mode:${RESET} ${EMERALD}${label}${RESET}`;
499
+ } else if (stateMode === "active") {
500
+ const label = "Active";
501
+ modeBadge = ` ${SLATE_600}\u2502${RESET} ${CTX_SECONDARY}Mode:${RESET} ${CTX_ACCENT}${label}${RESET}`;
502
+ }
503
+
504
+ // Resolve plan file path for display
505
+ let planFilePath: null | string = null;
506
+ if (isPlanning) {
507
+ planFilePath = activePlanFile;
508
+ } else if (statePlanPath) {
509
+ planFilePath = statePlanPath;
510
+ } else if (stateMode === "has_plan" || stateMode === "active") {
511
+ try {
512
+ planFilePath = findLatestPlan(contextId) ?? null;
513
+ } catch { /* ignore */ }
514
+ }
515
+
516
+ // Build plan name (mini/normal only)
517
+ let planPart = "";
518
+ if ((mode === "mini" || mode === "normal") && planFilePath) {
519
+ const planStem = path.basename(planFilePath, path.extname(planFilePath))
520
+ .replace(/^\d{4}-\d{2}-\d{2}-(\d{4}-)?/, "");
521
+ const maxPlanLen = mode === "mini" ? 20 : 30;
522
+ let truncatedPlan = planStem.slice(0, maxPlanLen);
523
+ if (planStem.length > maxPlanLen) truncatedPlan += "\u2026";
524
+ planPart = ` ${SLATE_600}\u2502${RESET} ${CTX_SECONDARY}Plan:${RESET} ${SLATE_300}${truncatedPlan}${RESET}`;
525
+ }
526
+
527
+ switch (mode) {
528
+ case "micro": {
529
+ console.log(`${CTX_ACCENT}\u25C6${RESET} ${SLATE_400}${truncatedId}${RESET}${modeBadge}`);
530
+
531
+ break;
532
+ }
533
+
534
+ case "mini": {
535
+ console.log(
536
+ `${CTX_ACCENT}\u25C6${RESET} ${SLATE_400}${truncatedId}${RESET}` +
537
+ `${modeBadge}${planPart}`,
538
+ );
539
+
540
+ break;
541
+ }
542
+
543
+ case "nano": {
544
+ console.log(`${CTX_ACCENT}\u25C6${RESET} ${SLATE_400}${truncatedId}${RESET}${modeBadge}`);
545
+
546
+ break;
547
+ }
548
+
549
+ default: {
550
+ console.log(
551
+ `${CTX_ACCENT}\u25C6${RESET} ${CTX_SECONDARY}Context:${RESET} ${SLATE_300}${truncatedId}${RESET}` +
552
+ `${modeBadge}${planPart}`,
553
+ );
554
+ }
555
+ }
556
+ }
557
+
558
+ function renderNoContext(mode: string): void {
559
+ const warn = `${ROSE}\u26A0 ${RESET}`;
560
+ if (mode === "normal") {
561
+ console.log(`${warn} ${ROSE}NO CONTEXT${RESET} ${SLATE_500}\u2014 type ^ for context manager${RESET}`);
562
+ } else {
563
+ console.log(`${warn} ${ROSE}NO CONTEXT${RESET}`);
564
+ }
565
+ }
566
+
567
+ // ---------------------------------------------------------------------------
568
+ // Context persistence
569
+ // ---------------------------------------------------------------------------
570
+
571
+ interface StatuslineCache {
572
+ sessions?: Record<string, { context_id: null | string }>;
573
+ }
574
+
575
+ function loadCache(): StatuslineCache {
576
+ try {
577
+ if (fs.existsSync(STATUSLINE_CACHE)) {
578
+ return JSON.parse(fs.readFileSync(STATUSLINE_CACHE, "utf8"));
579
+ }
580
+ } catch { /* ignore */ }
581
+
582
+ return {};
583
+ }
584
+
585
+ function saveCache(cache: StatuslineCache): void {
586
+ try {
587
+ fs.mkdirSync(path.dirname(STATUSLINE_CACHE), { recursive: true });
588
+ fs.writeFileSync(STATUSLINE_CACHE, JSON.stringify(cache, null, 2), "utf-8");
589
+ } catch { /* ignore */ }
590
+ }
591
+
592
+ function resolveContextId(sessionId: string): null | string {
593
+ if (!sessionId || sessionId === "unknown") return null;
594
+
595
+ // Check cache first
596
+ const cache = loadCache();
597
+ const cachedEntry = cache.sessions?.[sessionId];
598
+ if (cachedEntry && cachedEntry.context_id !== undefined) {
599
+ return cachedEntry.context_id;
600
+ }
601
+
602
+ // Cache miss — look up via context manager
603
+ try {
604
+ const context = getContextBySessionId(sessionId);
605
+ if (context) {
606
+ if (!cache.sessions) cache.sessions = {};
607
+ cache.sessions[sessionId] = { context_id: (context as any).id };
608
+ saveCache(cache);
609
+ return (context as any).id;
610
+ }
611
+ } catch { /* ignore */ }
612
+
613
+ // Don't cache negative results — context may be bound by a later hook
614
+ return null;
615
+ }
616
+
617
+ function loadContextState(contextId: string): null | Record<string, any> {
618
+ try {
619
+ return loadState(contextId) as null | Record<string, any>;
620
+ } catch {
621
+ return null;
622
+ }
623
+ }
624
+
625
+ function writeContextWindow(contextId: string, contextWindowData: Record<string, any>): void {
626
+ try {
627
+ const state = getContext(contextId) as null | Record<string, any>;
628
+ if (state) {
629
+ if (!state.last_session) state.last_session = {};
630
+ state.last_session.context_remaining_pct = contextWindowData.remaining_percentage;
631
+ saveState(contextId, state as any);
632
+ }
633
+ } catch { /* ignore */ }
634
+ }
635
+
636
+ // ---------------------------------------------------------------------------
637
+ // Main
638
+ // ---------------------------------------------------------------------------
639
+
640
+ function main(): void {
641
+ // Read JSON from stdin
642
+ let inputData: Record<string, any>;
643
+ try {
644
+ inputData = JSON.parse(fs.readFileSync(0, "utf8"));
645
+ } catch {
646
+ inputData = {};
647
+ }
648
+
649
+ // Terminal width and mode
650
+ const termWidth = getTerminalWidth();
651
+ const mode = getDisplayMode(termWidth);
652
+
653
+ // Extract input fields
654
+ const sessionId = inputData.session_id ?? "";
655
+ const modelName = inputData.model?.display_name ?? "unknown";
656
+ const cost = inputData.cost ?? {};
657
+ const durationMs: number = cost.total_duration_ms ?? 0;
658
+ const workspace = inputData.workspace ?? {};
659
+ const currentDir: string = workspace.project_dir ?? process.cwd();
660
+ const dirName = path.basename(currentDir);
661
+
662
+ // Context window data
663
+ const ctxWin = inputData.context_window ?? {};
664
+ const usage = ctxWin.current_usage ?? {};
665
+ const cacheRead: number = usage.cache_read_input_tokens ?? 0;
666
+ const inputTokens: number = usage.input_tokens ?? 0;
667
+ const cacheCreation: number = usage.cache_creation_input_tokens ?? 0;
668
+ const outputTokens: number = usage.output_tokens ?? 0;
669
+ const contextMax: number = ctxWin.context_window_size ?? 200_000;
670
+
671
+ // Calculate context percentage
672
+ const usedPct = ctxWin.used_percentage;
673
+ let contextPct: number;
674
+ const totalInput = cacheRead + inputTokens + cacheCreation;
675
+ const contextUsed = totalInput + outputTokens + CONTEXT_BASELINE_TOKENS;
676
+
677
+ if (usedPct !== undefined && usedPct !== null) {
678
+ contextPct = Math.floor(usedPct);
679
+ } else {
680
+ contextPct = contextMax > 0 ? Math.floor((contextUsed * 100) / contextMax) : 0;
681
+ }
682
+
683
+ const contextK = Math.floor(contextUsed / 1000);
684
+ const maxK = Math.floor(contextMax / 1000);
685
+
686
+ // Format duration
687
+ const durationSec = Math.floor(durationMs / 1000);
688
+ let timeDisplay: string;
689
+ if (durationSec >= 3600) {
690
+ timeDisplay = `${Math.floor(durationSec / 3600)}h${Math.floor((durationSec % 3600) / 60)}m`;
691
+ } else if (durationSec >= 60) {
692
+ timeDisplay = `${Math.floor(durationSec / 60)}m${durationSec % 60}s`;
693
+ } else {
694
+ timeDisplay = `${durationSec}s`;
695
+ }
696
+
697
+ // Resolve context ID for display and persistence
698
+ const contextId = resolveContextId(sessionId);
699
+
700
+ // Render context section
701
+ renderContext(mode, contextPct, contextK, maxK, timeDisplay, modelName);
702
+
703
+ // Render git section
704
+ const git = getGitStatus(currentDir);
705
+ if (git) {
706
+ renderGit(mode, git, dirName);
707
+ }
708
+
709
+ // Render context manager line (line 3) with separator
710
+ console.log(SEPARATOR);
711
+ if (contextId) {
712
+ const contextState = loadContextState(contextId);
713
+ renderContextManager(mode, contextId, contextState);
714
+ } else {
715
+ renderNoContext(mode);
716
+ }
717
+
718
+ // Persist context_window to state.json
719
+ if (contextId) {
720
+ writeContextWindow(contextId, {
721
+ used_percentage: contextPct,
722
+ remaining_percentage: 100 - contextPct,
723
+ context_window_size: contextMax,
724
+ tokens_used: contextUsed,
725
+ total_input_tokens: totalInput,
726
+ total_output_tokens: outputTokens,
727
+ model: modelName,
728
+ last_updated: new Date().toISOString().split(".")[0],
729
+ });
730
+ }
731
+ }
732
+
733
+ main();