cc-context-stats 1.5.0 → 1.6.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/.github/ISSUE_TEMPLATE/bug_report.md +49 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +33 -0
- package/.github/workflows/ci.yml +39 -2
- package/.github/workflows/release.yml +3 -1
- package/CHANGELOG.md +16 -8
- package/CLAUDE.md +54 -0
- package/CODE_OF_CONDUCT.md +59 -0
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/RELEASE_NOTES.md +16 -6
- package/SECURITY.md +44 -0
- package/TODOS.md +72 -0
- package/assets/logo/favicon.svg +17 -14
- package/assets/logo/logo-black.svg +19 -18
- package/assets/logo/logo-full.svg +39 -29
- package/assets/logo/logo-icon.svg +20 -19
- package/assets/logo/logo-mark.svg +21 -19
- package/assets/logo/logo-white.svg +19 -18
- package/assets/logo/logo-wordmark.svg +5 -6
- package/docs/ARCHITECTURE.md +101 -0
- package/docs/CSV_FORMAT.md +40 -0
- package/docs/DEPLOYMENT.md +60 -0
- package/docs/DEVELOPMENT.md +125 -0
- package/package.json +2 -2
- package/pyproject.toml +1 -1
- package/scripts/statusline-full.sh +11 -3
- package/scripts/statusline-git.sh +8 -1
- package/scripts/statusline-minimal.sh +8 -1
- package/scripts/statusline.js +62 -8
- package/scripts/statusline.py +24 -10
- package/src/claude_statusline/__init__.py +1 -1
- package/src/claude_statusline/cli/context_stats.py +20 -1
- package/src/claude_statusline/core/config.py +5 -4
- package/src/claude_statusline/core/state.py +64 -7
- package/src/claude_statusline/formatters/layout.py +17 -2
- package/tests/bash/test_parity.bats +315 -0
- package/tests/fixtures/json/comma_in_path.json +31 -0
- package/tests/node/rotation.test.js +89 -0
- package/tests/python/test_data_pipeline.py +446 -0
- package/tests/python/test_layout.py +19 -2
- package/tests/python/test_state_rotation_validation.py +232 -0
- package/tests/python/test_statusline.py +2 -0
- package/.claude/commands/context-stats.md +0 -17
- package/.claude/settings.local.json +0 -117
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Tests for state file rotation and session ID validation."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from claude_statusline.core.state import StateFile, _validate_session_id
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# Helpers
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _make_csv_line(index: int) -> str:
|
|
18
|
+
"""Generate a deterministic CSV state line for a given index."""
|
|
19
|
+
return (
|
|
20
|
+
f"{1710288000 + index},100,200,300,400,500,600,0.01,"
|
|
21
|
+
f"10,5,sess-{index},model,/tmp/proj,200000"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# State File Rotation
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestStateFileRotation:
|
|
31
|
+
"""Tests for _maybe_rotate() in StateFile."""
|
|
32
|
+
|
|
33
|
+
def test_below_threshold_no_rotation(self, tmp_path, monkeypatch):
|
|
34
|
+
"""File with fewer than 10,000 lines is not rotated."""
|
|
35
|
+
monkeypatch.setattr(StateFile, "STATE_DIR", tmp_path)
|
|
36
|
+
monkeypatch.setattr(StateFile, "OLD_STATE_DIR", tmp_path / "old")
|
|
37
|
+
(tmp_path / "old").mkdir()
|
|
38
|
+
|
|
39
|
+
sf = StateFile("test-session")
|
|
40
|
+
# Write 9,999 lines
|
|
41
|
+
lines = [_make_csv_line(i) + "\n" for i in range(9_999)]
|
|
42
|
+
sf.file_path.write_text("".join(lines))
|
|
43
|
+
|
|
44
|
+
sf._maybe_rotate()
|
|
45
|
+
|
|
46
|
+
result_lines = sf.file_path.read_text().splitlines()
|
|
47
|
+
assert len(result_lines) == 9_999
|
|
48
|
+
|
|
49
|
+
def test_at_threshold_no_rotation(self, tmp_path, monkeypatch):
|
|
50
|
+
"""File with exactly 10,000 lines is not rotated."""
|
|
51
|
+
monkeypatch.setattr(StateFile, "STATE_DIR", tmp_path)
|
|
52
|
+
monkeypatch.setattr(StateFile, "OLD_STATE_DIR", tmp_path / "old")
|
|
53
|
+
(tmp_path / "old").mkdir()
|
|
54
|
+
|
|
55
|
+
sf = StateFile("test-session")
|
|
56
|
+
lines = [_make_csv_line(i) + "\n" for i in range(10_000)]
|
|
57
|
+
sf.file_path.write_text("".join(lines))
|
|
58
|
+
|
|
59
|
+
sf._maybe_rotate()
|
|
60
|
+
|
|
61
|
+
result_lines = sf.file_path.read_text().splitlines()
|
|
62
|
+
assert len(result_lines) == 10_000
|
|
63
|
+
|
|
64
|
+
def test_exceeds_threshold_truncates_to_5000(self, tmp_path, monkeypatch):
|
|
65
|
+
"""File with 10,001 lines is truncated to 5,000."""
|
|
66
|
+
monkeypatch.setattr(StateFile, "STATE_DIR", tmp_path)
|
|
67
|
+
monkeypatch.setattr(StateFile, "OLD_STATE_DIR", tmp_path / "old")
|
|
68
|
+
(tmp_path / "old").mkdir()
|
|
69
|
+
|
|
70
|
+
sf = StateFile("test-session")
|
|
71
|
+
lines = [_make_csv_line(i) + "\n" for i in range(10_001)]
|
|
72
|
+
sf.file_path.write_text("".join(lines))
|
|
73
|
+
|
|
74
|
+
sf._maybe_rotate()
|
|
75
|
+
|
|
76
|
+
result_lines = sf.file_path.read_text().splitlines()
|
|
77
|
+
assert len(result_lines) == 5_000
|
|
78
|
+
|
|
79
|
+
def test_retained_lines_are_most_recent(self, tmp_path, monkeypatch):
|
|
80
|
+
"""After rotation, the retained lines are the last 5,000 of the original."""
|
|
81
|
+
monkeypatch.setattr(StateFile, "STATE_DIR", tmp_path)
|
|
82
|
+
monkeypatch.setattr(StateFile, "OLD_STATE_DIR", tmp_path / "old")
|
|
83
|
+
(tmp_path / "old").mkdir()
|
|
84
|
+
|
|
85
|
+
sf = StateFile("test-session")
|
|
86
|
+
total = 10_001
|
|
87
|
+
lines = [_make_csv_line(i) + "\n" for i in range(total)]
|
|
88
|
+
sf.file_path.write_text("".join(lines))
|
|
89
|
+
|
|
90
|
+
sf._maybe_rotate()
|
|
91
|
+
|
|
92
|
+
result_lines = sf.file_path.read_text().splitlines()
|
|
93
|
+
# First retained line should be the one at index (total - 5000) = 5001
|
|
94
|
+
assert f"sess-{total - 5000}" in result_lines[0]
|
|
95
|
+
# Last retained line should be the last original line
|
|
96
|
+
assert f"sess-{total - 1}" in result_lines[-1]
|
|
97
|
+
|
|
98
|
+
def test_rotation_via_append_entry(self, tmp_path, monkeypatch):
|
|
99
|
+
"""append_entry triggers rotation when threshold is exceeded."""
|
|
100
|
+
monkeypatch.setattr(StateFile, "STATE_DIR", tmp_path)
|
|
101
|
+
monkeypatch.setattr(StateFile, "OLD_STATE_DIR", tmp_path / "old")
|
|
102
|
+
(tmp_path / "old").mkdir()
|
|
103
|
+
|
|
104
|
+
sf = StateFile("test-session")
|
|
105
|
+
# Write exactly 10,000 lines (at threshold, no rotation yet)
|
|
106
|
+
lines = [_make_csv_line(i) + "\n" for i in range(10_000)]
|
|
107
|
+
sf.file_path.write_text("".join(lines))
|
|
108
|
+
|
|
109
|
+
# Import StateEntry to create a valid entry
|
|
110
|
+
from claude_statusline.core.state import StateEntry
|
|
111
|
+
|
|
112
|
+
entry = StateEntry(
|
|
113
|
+
timestamp=1710298000,
|
|
114
|
+
total_input_tokens=100,
|
|
115
|
+
total_output_tokens=200,
|
|
116
|
+
current_input_tokens=300,
|
|
117
|
+
current_output_tokens=400,
|
|
118
|
+
cache_creation=500,
|
|
119
|
+
cache_read=600,
|
|
120
|
+
cost_usd=0.01,
|
|
121
|
+
lines_added=10,
|
|
122
|
+
lines_removed=5,
|
|
123
|
+
session_id="test-session",
|
|
124
|
+
model_id="model",
|
|
125
|
+
workspace_project_dir="/tmp/proj",
|
|
126
|
+
context_window_size=200000,
|
|
127
|
+
)
|
|
128
|
+
sf.append_entry(entry)
|
|
129
|
+
|
|
130
|
+
# Now file had 10,001 lines -> should have been rotated to 5,000
|
|
131
|
+
result_lines = sf.file_path.read_text().splitlines()
|
|
132
|
+
assert len(result_lines) == 5_000
|
|
133
|
+
|
|
134
|
+
def test_no_temp_files_left_after_rotation(self, tmp_path, monkeypatch):
|
|
135
|
+
"""No .tmp files remain after successful rotation."""
|
|
136
|
+
monkeypatch.setattr(StateFile, "STATE_DIR", tmp_path)
|
|
137
|
+
monkeypatch.setattr(StateFile, "OLD_STATE_DIR", tmp_path / "old")
|
|
138
|
+
(tmp_path / "old").mkdir()
|
|
139
|
+
|
|
140
|
+
sf = StateFile("test-session")
|
|
141
|
+
lines = [_make_csv_line(i) + "\n" for i in range(10_001)]
|
|
142
|
+
sf.file_path.write_text("".join(lines))
|
|
143
|
+
|
|
144
|
+
sf._maybe_rotate()
|
|
145
|
+
|
|
146
|
+
tmp_files = list(tmp_path.glob("*.tmp"))
|
|
147
|
+
assert tmp_files == []
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
# Session ID Validation
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class TestSessionIdValidation:
|
|
156
|
+
"""Tests for _validate_session_id and StateFile constructor validation."""
|
|
157
|
+
|
|
158
|
+
def test_reject_forward_slash(self):
|
|
159
|
+
with pytest.raises(ValueError, match="/"):
|
|
160
|
+
_validate_session_id("../../etc/passwd")
|
|
161
|
+
|
|
162
|
+
def test_reject_backslash(self):
|
|
163
|
+
with pytest.raises(ValueError, match=re.escape("\\")):
|
|
164
|
+
_validate_session_id("..\\..\\etc\\passwd")
|
|
165
|
+
|
|
166
|
+
def test_reject_dot_dot(self):
|
|
167
|
+
with pytest.raises(ValueError, match=r"\.\."):
|
|
168
|
+
_validate_session_id("..hidden")
|
|
169
|
+
|
|
170
|
+
def test_reject_null_byte(self):
|
|
171
|
+
with pytest.raises(ValueError):
|
|
172
|
+
_validate_session_id("session\0id")
|
|
173
|
+
|
|
174
|
+
def test_accept_valid_uuid(self):
|
|
175
|
+
_validate_session_id("abc-123-def-456") # Should not raise
|
|
176
|
+
|
|
177
|
+
def test_accept_hyphens_underscores(self):
|
|
178
|
+
_validate_session_id("my_session-id_123") # Should not raise
|
|
179
|
+
|
|
180
|
+
def test_accept_alphanumeric(self):
|
|
181
|
+
_validate_session_id("abcdef1234567890") # Should not raise
|
|
182
|
+
|
|
183
|
+
def test_statefile_rejects_invalid_session(self, tmp_path, monkeypatch):
|
|
184
|
+
monkeypatch.setattr(StateFile, "STATE_DIR", tmp_path)
|
|
185
|
+
monkeypatch.setattr(StateFile, "OLD_STATE_DIR", tmp_path / "old")
|
|
186
|
+
(tmp_path / "old").mkdir()
|
|
187
|
+
|
|
188
|
+
with pytest.raises(ValueError):
|
|
189
|
+
StateFile("../../etc/passwd")
|
|
190
|
+
|
|
191
|
+
def test_statefile_accepts_none_session(self, tmp_path, monkeypatch):
|
|
192
|
+
monkeypatch.setattr(StateFile, "STATE_DIR", tmp_path)
|
|
193
|
+
monkeypatch.setattr(StateFile, "OLD_STATE_DIR", tmp_path / "old")
|
|
194
|
+
(tmp_path / "old").mkdir()
|
|
195
|
+
|
|
196
|
+
sf = StateFile(None) # Should not raise
|
|
197
|
+
assert sf.session_id is None
|
|
198
|
+
|
|
199
|
+
def test_statefile_accepts_valid_session(self, tmp_path, monkeypatch):
|
|
200
|
+
monkeypatch.setattr(StateFile, "STATE_DIR", tmp_path)
|
|
201
|
+
monkeypatch.setattr(StateFile, "OLD_STATE_DIR", tmp_path / "old")
|
|
202
|
+
(tmp_path / "old").mkdir()
|
|
203
|
+
|
|
204
|
+
sf = StateFile("valid-session-123") # Should not raise
|
|
205
|
+
assert sf.session_id == "valid-session-123"
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# ---------------------------------------------------------------------------
|
|
209
|
+
# CLI Session ID Rejection (subprocess test)
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class TestCliSessionIdRejection:
|
|
214
|
+
"""Test that context-stats CLI rejects invalid session IDs."""
|
|
215
|
+
|
|
216
|
+
def test_cli_rejects_path_traversal(self):
|
|
217
|
+
result = subprocess.run(
|
|
218
|
+
[sys.executable, "-m", "claude_statusline.cli.context_stats", "../../etc/passwd"],
|
|
219
|
+
capture_output=True,
|
|
220
|
+
text=True,
|
|
221
|
+
)
|
|
222
|
+
assert result.returncode != 0
|
|
223
|
+
assert "Invalid session_id" in result.stderr
|
|
224
|
+
|
|
225
|
+
def test_cli_rejects_backslash(self):
|
|
226
|
+
result = subprocess.run(
|
|
227
|
+
[sys.executable, "-m", "claude_statusline.cli.context_stats", "test\\bad"],
|
|
228
|
+
capture_output=True,
|
|
229
|
+
text=True,
|
|
230
|
+
)
|
|
231
|
+
assert result.returncode != 0
|
|
232
|
+
assert "Invalid session_id" in result.stderr
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Display ASCII graph of token usage for current session
|
|
3
|
-
argument-hint: [session_id] [--type cumulative|delta|both] [--no-color]
|
|
4
|
-
allowed-tools: Bash(*)
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Context Stats
|
|
8
|
-
|
|
9
|
-
Display the token consumption history as ASCII graphs.
|
|
10
|
-
|
|
11
|
-
**IMPORTANT**: This command executes a bash script and displays its output directly. Do NOT analyze or interpret the results - simply show them to the user.
|
|
12
|
-
|
|
13
|
-
Execute the token graph script:
|
|
14
|
-
|
|
15
|
-
!`bash /Users/montimage/buildspace/luongnv89/claude-statusline/scripts/context-stats.sh $ARGUMENTS`
|
|
16
|
-
|
|
17
|
-
The output above shows the token usage visualization. No further analysis is needed.
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(/Users/montimage/buildspace/luongnv89/claude-statusline/scripts/statusline-full.sh:*)",
|
|
5
|
-
"Bash(find:*)",
|
|
6
|
-
"Bash(GIT_DIR=/Users/montimage/buildspace/luongnv89/claude-statusline/.git git add scripts/)",
|
|
7
|
-
"Bash(GIT_DIR=/Users/montimage/buildspace/luongnv89/claude-statusline/.git git -c core.hooksPath=/dev/null commit:*)",
|
|
8
|
-
"Bash(GIT_SSH_COMMAND=\"ssh -F /dev/null -o IdentityFile=~/.ssh/id_ed25519\" git push origin main)",
|
|
9
|
-
"Bash(/Users/montimage/buildspace/luongnv89/claude-statusline/scripts/statusline-original.sh)",
|
|
10
|
-
"Bash(/Users/montimage/buildspace/luongnv89/claude-statusline/scripts/statusline-git.sh)",
|
|
11
|
-
"Bash(/Users/montimage/buildspace/luongnv89/claude-statusline/scripts/statusline-minimal.sh:*)",
|
|
12
|
-
"Bash(ls:*)",
|
|
13
|
-
"Bash(wc:*)",
|
|
14
|
-
"Bash(pytest:*)",
|
|
15
|
-
"Bash(bats:*)",
|
|
16
|
-
"Bash(ruff check:*)",
|
|
17
|
-
"Bash(npx eslint:*)",
|
|
18
|
-
"Bash(shellcheck:*)",
|
|
19
|
-
"Bash(gh run list:*)",
|
|
20
|
-
"Bash(gh run view:*)",
|
|
21
|
-
"Bash(ruff format:*)",
|
|
22
|
-
"Bash(git rm:*)",
|
|
23
|
-
"Bash(scripts/statusline-full.sh:*)",
|
|
24
|
-
"Bash(./scripts/statusline-full.sh:*)",
|
|
25
|
-
"Bash(~/.claude/statusline.conf)",
|
|
26
|
-
"Bash(/Users/montimage/buildspace/luongnv89/claude-statusline/scripts/context-stats.sh:*)",
|
|
27
|
-
"Bash(bash -x:*)",
|
|
28
|
-
"Bash(timeout 5 bash:*)",
|
|
29
|
-
"Bash(perl -e 'alarm 10; exec @ARGV' -- /Users/montimage/buildspace/luongnv89/claude-statusline/scripts/context-stats.sh test-session --no-color)",
|
|
30
|
-
"Bash(bash -c '/Users/montimage/buildspace/luongnv89/claude-statusline/scripts/context-stats.sh test-session --no-color; echo \"\"Exit code: $?\"\"')",
|
|
31
|
-
"Bash(/bin/bash /Users/montimage/buildspace/luongnv89/claude-statusline/scripts/context-stats.sh:*)",
|
|
32
|
-
"Bash(/bin/bash:*)",
|
|
33
|
-
"Bash(/opt/homebrew/bin/bash:*)",
|
|
34
|
-
"Bash(COLUMNS=80 LINES=40 /bin/bash:*)",
|
|
35
|
-
"Bash(pre-commit:*)",
|
|
36
|
-
"Bash(./install.sh)",
|
|
37
|
-
"Bash(context-stats:*)",
|
|
38
|
-
"Bash(gh pr list:*)",
|
|
39
|
-
"Bash(apt-get update:*)",
|
|
40
|
-
"Bash(apt-get install:*)",
|
|
41
|
-
"Bash(pip install:*)",
|
|
42
|
-
"Bash(mypy:*)",
|
|
43
|
-
"Bash(npm ci:*)",
|
|
44
|
-
"Bash(npx prettier --check scripts/statusline.js)",
|
|
45
|
-
"Bash(cat:*)",
|
|
46
|
-
"Bash(node -c:*)",
|
|
47
|
-
"Bash(xxd:*)",
|
|
48
|
-
"Bash(node --version:*)",
|
|
49
|
-
"Bash(awk:*)",
|
|
50
|
-
"Bash(grep:*)",
|
|
51
|
-
"Bash(npm test:*)",
|
|
52
|
-
"Bash(SAMPLE_INPUT='{\"\"\"\"model\"\"\"\":{\"\"\"\"display_name\"\"\"\":\"\"\"\"Claude 3.5 Sonnet\"\"\"\"},\"\"\"\"workspace\"\"\"\":{\"\"\"\"current_dir\"\"\"\":\"\"\"\"/tmp/test\"\"\"\",\"\"\"\"project_dir\"\"\"\":\"\"\"\"/tmp/test\"\"\"\"},\"\"\"\"context_window\"\"\"\":{\"\"\"\"context_window_size\"\"\"\":200000,\"\"\"\"current_usage\"\"\"\":{\"\"\"\"input_tokens\"\"\"\":1000,\"\"\"\"cache_creation_input_tokens\"\"\"\":500,\"\"\"\"cache_read_input_tokens\"\"\"\":200}}}')",
|
|
53
|
-
"Bash(echo \"Testing bash scripts...\" echo \"$SAMPLE_INPUT\")",
|
|
54
|
-
"Bash(./scripts/statusline-git.sh echo \"$SAMPLE_INPUT\")",
|
|
55
|
-
"Bash(./scripts/statusline-minimal.sh:*)",
|
|
56
|
-
"Bash(echo \"Testing Python script...\" echo \"$SAMPLE_INPUT\")",
|
|
57
|
-
"Bash(python3:*)",
|
|
58
|
-
"Bash(echo \"Testing Node.js script...\" echo \"$SAMPLE_INPUT\")",
|
|
59
|
-
"Bash(node ./scripts/statusline.js:*)",
|
|
60
|
-
"Bash(./scripts/statusline-git.sh echo \"\" echo \"$SAMPLE_INPUT\")",
|
|
61
|
-
"Bash(export PATH=\"$HOME/.local/bin:$PATH\")",
|
|
62
|
-
"Bash(~/.local/bin/context-stats test-session:*)",
|
|
63
|
-
"Bash(~/.local/bin/context-stats)",
|
|
64
|
-
"Bash(~/.local/bin/context-stats:*)",
|
|
65
|
-
"Bash(gh run watch:*)",
|
|
66
|
-
"Bash(claude-statusline)",
|
|
67
|
-
"Bash(python -m build:*)",
|
|
68
|
-
"Bash(unzip:*)",
|
|
69
|
-
"Bash(twine:*)",
|
|
70
|
-
"WebFetch(domain:pypi.org)",
|
|
71
|
-
"Bash(python -m pytest:*)",
|
|
72
|
-
"Bash(python:*)",
|
|
73
|
-
"Bash(python -c:*)",
|
|
74
|
-
"Bash(timeout 5 context-stats:*)",
|
|
75
|
-
"Bash(gtimeout 5 context-stats:*)",
|
|
76
|
-
"Bash(./scripts/context-stats.sh:*)",
|
|
77
|
-
"Bash(echo:*)",
|
|
78
|
-
"Bash(CLAUDE_SESSION_ID=\"bf8a8370-18d6-473d-8f12-51e7f3c2a9d0\" node scripts/statusline.js:*)",
|
|
79
|
-
"Skill(pre-commit)",
|
|
80
|
-
"Bash(npm run format:check:*)",
|
|
81
|
-
"Bash(bash:*)",
|
|
82
|
-
"Bash(git tag:*)",
|
|
83
|
-
"Bash(git pull:*)",
|
|
84
|
-
"Bash(git rebase:*)",
|
|
85
|
-
"Bash(git stash:*)",
|
|
86
|
-
"Bash(npm whoami:*)",
|
|
87
|
-
"Bash(git filter-branch:*)",
|
|
88
|
-
"Bash(gh issue view:*)",
|
|
89
|
-
"Bash(head:*)",
|
|
90
|
-
"Bash(/Users/montimage/buildspace/luongnv89/cc-context-stats/.venv/bin/python -m pytest:*)",
|
|
91
|
-
"Bash(/Users/montimage/buildspace/luongnv89/cc-context-stats/.venv/bin/python3 -m pytest:*)",
|
|
92
|
-
"Bash(/Users/montimage/buildspace/luongnv89/cc-context-stats/.venv/bin/python3.14 -m pytest:*)",
|
|
93
|
-
"Bash(/Users/montimage/buildspace/luongnv89/cc-context-stats/venv/bin/python3 -m pytest:*)",
|
|
94
|
-
"Bash(/Users/montimage/buildspace/luongnv89/cc-context-stats/venv/bin/pip install:*)",
|
|
95
|
-
"Bash(.test_venv/bin/pip install:*)",
|
|
96
|
-
"Bash(.test_venv/bin/python3.14 -m pytest:*)",
|
|
97
|
-
"Bash(.venv/bin/pip install:*)",
|
|
98
|
-
"Bash(.venv/bin/python3.14 -m pytest:*)",
|
|
99
|
-
"Bash(.venv/bin/context-stats:*)",
|
|
100
|
-
"Bash(npx jest:*)",
|
|
101
|
-
"Bash(gh workflow:*)",
|
|
102
|
-
"Bash(git -C /Users/montimage/buildspace/luongnv89/cc-context-stats status)",
|
|
103
|
-
"Bash(git -C /Users/montimage/buildspace/luongnv89/cc-context-stats diff)",
|
|
104
|
-
"Bash(git -C /Users/montimage/buildspace/luongnv89/cc-context-stats log --oneline -5)",
|
|
105
|
-
"Bash(git -C /Users/montimage/buildspace/luongnv89/cc-context-stats add tests/python/test_statusline.py)",
|
|
106
|
-
"Bash(git -C /Users/montimage/buildspace/luongnv89/cc-context-stats commit -m \"$\\(cat <<''EOF''\nfix: resolve Windows CI failures caused by Unicode encoding in Python tests\n\nSet PYTHONUTF8=1 for subprocess calls and use explicit utf-8 encoding\nfor read_text\\(\\) to handle Unicode characters \\(emojis/icons\\) on Windows\nwhere the default cp1252 encoding cannot decode them.\nEOF\n\\)\")",
|
|
107
|
-
"Bash(git -C /Users/montimage/buildspace/luongnv89/cc-context-stats push)",
|
|
108
|
-
"Bash(COLUMNS=200 cat tests/fixtures/json/valid_full.json | scripts/statusline-full.sh 2>&1)",
|
|
109
|
-
"Bash(export COLUMNS=200 && cat tests/fixtures/json/valid_full.json | scripts/statusline-full.sh 2>&1)",
|
|
110
|
-
"Bash(gh release:*)",
|
|
111
|
-
"Read(//Users/montimage/.claude/**)",
|
|
112
|
-
"Bash(git checkout:*)",
|
|
113
|
-
"Bash(npm publish:*)",
|
|
114
|
-
"Bash(npm view:*)"
|
|
115
|
-
]
|
|
116
|
-
}
|
|
117
|
-
}
|