compact-code 0.1.0__tar.gz → 0.2.0__tar.gz
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.
- {compact_code-0.1.0 → compact_code-0.2.0}/PKG-INFO +17 -2
- {compact_code-0.1.0 → compact_code-0.2.0}/README.md +13 -1
- {compact_code-0.1.0 → compact_code-0.2.0}/pyproject.toml +6 -1
- {compact_code-0.1.0 → compact_code-0.2.0}/src/compact_code/__init__.py +1 -1
- {compact_code-0.1.0 → compact_code-0.2.0}/src/compact_code/cli.py +42 -1
- compact_code-0.2.0/src/compact_code/hooks.py +39 -0
- compact_code-0.2.0/src/compact_code/ledger.py +52 -0
- compact_code-0.2.0/tests/test_ledger.py +101 -0
- {compact_code-0.1.0 → compact_code-0.2.0}/.gitignore +0 -0
- {compact_code-0.1.0 → compact_code-0.2.0}/LICENSE +0 -0
- {compact_code-0.1.0 → compact_code-0.2.0}/src/compact_code/compactor.py +0 -0
- {compact_code-0.1.0 → compact_code-0.2.0}/src/compact_code/expand.py +0 -0
- {compact_code-0.1.0 → compact_code-0.2.0}/src/compact_code/tokens.py +0 -0
- {compact_code-0.1.0 → compact_code-0.2.0}/tests/test_cli.py +0 -0
- {compact_code-0.1.0 → compact_code-0.2.0}/tests/test_compactor.py +0 -0
- {compact_code-0.1.0 → compact_code-0.2.0}/uv.lock +0 -0
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: compact-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Dense Python for AI agents, readable view for humans. Same logic, ~30-55% fewer tokens.
|
|
5
|
+
Project-URL: Homepage, https://github.com/EmmanuelFreund/compact-code
|
|
6
|
+
Project-URL: Repository, https://github.com/EmmanuelFreund/compact-code
|
|
7
|
+
Project-URL: Issues, https://github.com/EmmanuelFreund/compact-code/issues
|
|
5
8
|
Author-email: Emmanuel Freund <freund.emmanuel@gmail.com>
|
|
6
9
|
License: MIT
|
|
7
10
|
License-File: LICENSE
|
|
@@ -61,10 +64,22 @@ Codebase density gain: -19% to -41% tokens depending on the project (less if doc
|
|
|
61
64
|
| `compact-code compact --check` | dry run: report savings without writing |
|
|
62
65
|
| `compact-code compact --keep-docstrings` | keep docstrings (use when they are functional: CLI help text, doctests, flit) |
|
|
63
66
|
| `compact-code expand <file>` | print a readable 4-space-indented view (`--write` to rewrite in place) |
|
|
64
|
-
| `compact-code stats` | tokens and $ saved per full
|
|
67
|
+
| `compact-code stats` | tokens and $ saved: per full read + cumulative real agent reads |
|
|
68
|
+
| `compact-code install-hook` | install a Claude Code hook that counts **real** savings on every agent read (`--project` for project-level) |
|
|
65
69
|
|
|
66
70
|
Install `compact-code[exact]` for exact token counts (tiktoken).
|
|
67
71
|
|
|
72
|
+
## The live savings counter
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
compact-code install-hook # one-time, hooks into Claude Code
|
|
76
|
+
# ... let your agent work ...
|
|
77
|
+
compact-code stats
|
|
78
|
+
# Agent savings so far: 142 reads of compacted files, 87,310 tokens saved (~$0.26).
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The hook fires on every `Read` Claude Code performs; if the file is in this project's compaction manifest, the per-file saving (prorated for partial reads) is credited to a local ledger. No network, no telemetry — everything stays in `.compact-code.json`.
|
|
82
|
+
|
|
68
83
|
## The honest fine print
|
|
69
84
|
|
|
70
85
|
- The gain is proportional to how much code your agent actually **reads**. Exploration-heavy and read-heavy sessions save the most; one-file quick fixes save nothing.
|
|
@@ -43,10 +43,22 @@ Codebase density gain: -19% to -41% tokens depending on the project (less if doc
|
|
|
43
43
|
| `compact-code compact --check` | dry run: report savings without writing |
|
|
44
44
|
| `compact-code compact --keep-docstrings` | keep docstrings (use when they are functional: CLI help text, doctests, flit) |
|
|
45
45
|
| `compact-code expand <file>` | print a readable 4-space-indented view (`--write` to rewrite in place) |
|
|
46
|
-
| `compact-code stats` | tokens and $ saved per full
|
|
46
|
+
| `compact-code stats` | tokens and $ saved: per full read + cumulative real agent reads |
|
|
47
|
+
| `compact-code install-hook` | install a Claude Code hook that counts **real** savings on every agent read (`--project` for project-level) |
|
|
47
48
|
|
|
48
49
|
Install `compact-code[exact]` for exact token counts (tiktoken).
|
|
49
50
|
|
|
51
|
+
## The live savings counter
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
compact-code install-hook # one-time, hooks into Claude Code
|
|
55
|
+
# ... let your agent work ...
|
|
56
|
+
compact-code stats
|
|
57
|
+
# Agent savings so far: 142 reads of compacted files, 87,310 tokens saved (~$0.26).
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The hook fires on every `Read` Claude Code performs; if the file is in this project's compaction manifest, the per-file saving (prorated for partial reads) is credited to a local ledger. No network, no telemetry — everything stays in `.compact-code.json`.
|
|
61
|
+
|
|
50
62
|
## The honest fine print
|
|
51
63
|
|
|
52
64
|
- The gain is proportional to how much code your agent actually **reads**. Exploration-heavy and read-heavy sessions save the most; one-file quick fixes save nothing.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "compact-code"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Dense Python for AI agents, readable view for humans. Same logic, ~30-55% fewer tokens."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -19,6 +19,11 @@ classifiers = [
|
|
|
19
19
|
"Topic :: Software Development :: Code Generators",
|
|
20
20
|
]
|
|
21
21
|
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://github.com/EmmanuelFreund/compact-code"
|
|
24
|
+
Repository = "https://github.com/EmmanuelFreund/compact-code"
|
|
25
|
+
Issues = "https://github.com/EmmanuelFreund/compact-code/issues"
|
|
26
|
+
|
|
22
27
|
[project.optional-dependencies]
|
|
23
28
|
exact = ["tiktoken>=0.5"]
|
|
24
29
|
|
|
@@ -6,7 +6,7 @@ import json
|
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
8
|
|
|
9
|
-
from . import compactor, expand, tokens
|
|
9
|
+
from . import compactor, expand, hooks, ledger, tokens
|
|
10
10
|
|
|
11
11
|
MANIFEST = ".compact-code.json"
|
|
12
12
|
EXCLUDED_DIRS = {
|
|
@@ -115,11 +115,43 @@ def cmd_stats(args):
|
|
|
115
115
|
print(f"Codebase: {before:,} -> {after:,} tokens ({pct:.1f}% smaller, {len(files)} files)")
|
|
116
116
|
print(f"Every full read of this codebase by your agent now saves "
|
|
117
117
|
f"{saved:,} tokens (~${dollars:.2f} at ${args.price}/Mtok input).")
|
|
118
|
+
led = manifest.get("ledger")
|
|
119
|
+
if led and led.get("reads"):
|
|
120
|
+
led_saved = int(led["tokens_saved"])
|
|
121
|
+
led_dollars = led_saved / 1_000_000 * args.price
|
|
122
|
+
print(f"Agent savings so far: {led['reads']:,} reads of compacted files, "
|
|
123
|
+
f"{led_saved:,} tokens saved (~${led_dollars:.2f}).")
|
|
124
|
+
else:
|
|
125
|
+
print("No agent reads recorded yet. Run `compact-code install-hook` to start "
|
|
126
|
+
"counting real savings (Claude Code).")
|
|
118
127
|
print("Sessions that read a lot of code (exploration, onboarding, refactors) "
|
|
119
128
|
"benefit the most; surgical one-file fixes benefit little.")
|
|
120
129
|
return 0
|
|
121
130
|
|
|
122
131
|
|
|
132
|
+
def cmd_hook(args):
|
|
133
|
+
try:
|
|
134
|
+
payload = json.load(sys.stdin)
|
|
135
|
+
tool_input = payload.get("tool_input", {})
|
|
136
|
+
path = tool_input.get("file_path")
|
|
137
|
+
if path and path.endswith(".py"):
|
|
138
|
+
ledger.record_read(path, limit=tool_input.get("limit"))
|
|
139
|
+
except Exception:
|
|
140
|
+
pass
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def cmd_install_hook(args):
|
|
145
|
+
path, installed = hooks.install(project=args.project)
|
|
146
|
+
if installed:
|
|
147
|
+
print(f"Hook installed in {path}.")
|
|
148
|
+
print("Claude Code will now credit real savings on every read of a "
|
|
149
|
+
"compacted file. Restart Claude Code (or open /hooks) to activate.")
|
|
150
|
+
else:
|
|
151
|
+
print(f"Hook already installed in {path}.")
|
|
152
|
+
return 0
|
|
153
|
+
|
|
154
|
+
|
|
123
155
|
def main(argv=None):
|
|
124
156
|
parser = argparse.ArgumentParser(
|
|
125
157
|
prog="compact-code",
|
|
@@ -145,6 +177,15 @@ def main(argv=None):
|
|
|
145
177
|
help="USD per million input tokens (default: 3.0)")
|
|
146
178
|
p.set_defaults(func=cmd_stats)
|
|
147
179
|
|
|
180
|
+
p = sub.add_parser("hook", help="(internal) Claude Code PostToolUse hook, reads JSON on stdin")
|
|
181
|
+
p.set_defaults(func=cmd_hook)
|
|
182
|
+
|
|
183
|
+
p = sub.add_parser("install-hook",
|
|
184
|
+
help="install the Claude Code hook that counts real savings")
|
|
185
|
+
p.add_argument("--project", action="store_true",
|
|
186
|
+
help="install in ./.claude/settings.json instead of ~/.claude/settings.json")
|
|
187
|
+
p.set_defaults(func=cmd_install_hook)
|
|
188
|
+
|
|
148
189
|
args = parser.parse_args(argv)
|
|
149
190
|
return args.func(args)
|
|
150
191
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Install the Claude Code hook that feeds the savings ledger."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
HOOK_COMMAND = "compact-code hook"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def settings_path(project=False):
|
|
10
|
+
if project:
|
|
11
|
+
return os.path.join(".claude", "settings.json")
|
|
12
|
+
return os.path.join(os.path.expanduser("~"), ".claude", "settings.json")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def install(project=False):
|
|
16
|
+
"""Merge the PostToolUse/Read hook into Claude Code settings.
|
|
17
|
+
|
|
18
|
+
Returns (path, installed) — installed is False if it was already there.
|
|
19
|
+
"""
|
|
20
|
+
path = settings_path(project)
|
|
21
|
+
settings = {}
|
|
22
|
+
if os.path.exists(path):
|
|
23
|
+
with open(path, encoding="utf-8") as f:
|
|
24
|
+
content = f.read().strip()
|
|
25
|
+
if content:
|
|
26
|
+
settings = json.loads(content)
|
|
27
|
+
post = settings.setdefault("hooks", {}).setdefault("PostToolUse", [])
|
|
28
|
+
for matcher in post:
|
|
29
|
+
for hook in matcher.get("hooks", []):
|
|
30
|
+
if HOOK_COMMAND in hook.get("command", ""):
|
|
31
|
+
return path, False
|
|
32
|
+
post.append({
|
|
33
|
+
"matcher": "Read",
|
|
34
|
+
"hooks": [{"type": "command", "command": HOOK_COMMAND}],
|
|
35
|
+
})
|
|
36
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
37
|
+
with open(path, "w", encoding="utf-8", newline="\n") as f:
|
|
38
|
+
json.dump(settings, f, indent=2)
|
|
39
|
+
return path, True
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Cumulative savings ledger: records real agent reads of compacted files."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
MANIFEST = ".compact-code.json"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def find_manifest(start_dir):
|
|
10
|
+
"""Walk up from start_dir looking for a manifest. Returns its path or None."""
|
|
11
|
+
current = os.path.abspath(start_dir)
|
|
12
|
+
while True:
|
|
13
|
+
candidate = os.path.join(current, MANIFEST)
|
|
14
|
+
if os.path.isfile(candidate):
|
|
15
|
+
return candidate
|
|
16
|
+
parent = os.path.dirname(current)
|
|
17
|
+
if parent == current:
|
|
18
|
+
return None
|
|
19
|
+
current = parent
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def record_read(file_path, limit=None):
|
|
23
|
+
"""Credit the savings of one agent read of file_path, if it was compacted.
|
|
24
|
+
|
|
25
|
+
Returns the tokens credited (0 if the file is not in any manifest).
|
|
26
|
+
"""
|
|
27
|
+
file_path = os.path.abspath(file_path)
|
|
28
|
+
manifest_path = find_manifest(os.path.dirname(file_path))
|
|
29
|
+
if manifest_path is None:
|
|
30
|
+
return 0
|
|
31
|
+
with open(manifest_path, encoding="utf-8") as f:
|
|
32
|
+
manifest = json.load(f)
|
|
33
|
+
root = os.path.dirname(manifest_path)
|
|
34
|
+
key = os.path.relpath(file_path, root).replace("\\", "/")
|
|
35
|
+
entry = manifest.get("files", {}).get(key)
|
|
36
|
+
if entry is None:
|
|
37
|
+
return 0
|
|
38
|
+
saved = entry["before"] - entry["after"]
|
|
39
|
+
if limit is not None:
|
|
40
|
+
try:
|
|
41
|
+
with open(file_path, encoding="utf-8", errors="replace") as f:
|
|
42
|
+
total_lines = sum(1 for _ in f)
|
|
43
|
+
if total_lines > 0:
|
|
44
|
+
saved = saved * min(1.0, limit / total_lines)
|
|
45
|
+
except OSError:
|
|
46
|
+
pass
|
|
47
|
+
ledger = manifest.setdefault("ledger", {"reads": 0, "tokens_saved": 0})
|
|
48
|
+
ledger["reads"] += 1
|
|
49
|
+
ledger["tokens_saved"] = round(ledger["tokens_saved"] + saved, 1)
|
|
50
|
+
with open(manifest_path, "w", encoding="utf-8", newline="\n") as f:
|
|
51
|
+
json.dump(manifest, f, indent=1)
|
|
52
|
+
return saved
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from compact_code import cli, hooks, ledger
|
|
7
|
+
|
|
8
|
+
SRC = "def f(x: int) -> int:\n \"\"\"Doubles.\"\"\"\n # comment\n return x * 2\n"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def compacted(tmp_path, monkeypatch):
|
|
13
|
+
(tmp_path / "pkg").mkdir()
|
|
14
|
+
(tmp_path / "pkg" / "mod.py").write_text(SRC, encoding="utf-8")
|
|
15
|
+
monkeypatch.chdir(tmp_path)
|
|
16
|
+
cli.main(["compact"])
|
|
17
|
+
return tmp_path
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _ledger(root):
|
|
21
|
+
manifest = json.loads((root / cli.MANIFEST).read_text(encoding="utf-8"))
|
|
22
|
+
return manifest.get("ledger")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_record_read_accumulates(compacted):
|
|
26
|
+
target = compacted / "pkg" / "mod.py"
|
|
27
|
+
saved1 = ledger.record_read(str(target))
|
|
28
|
+
saved2 = ledger.record_read(str(target))
|
|
29
|
+
assert saved1 == saved2 > 0
|
|
30
|
+
led = _ledger(compacted)
|
|
31
|
+
assert led["reads"] == 2
|
|
32
|
+
assert led["tokens_saved"] == pytest.approx(saved1 * 2)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_record_read_unknown_file(compacted):
|
|
36
|
+
other = compacted / "pkg" / "other.py"
|
|
37
|
+
other.write_text("x = 1\n", encoding="utf-8")
|
|
38
|
+
assert ledger.record_read(str(other)) == 0
|
|
39
|
+
assert _ledger(compacted) is None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_record_read_no_manifest(tmp_path):
|
|
43
|
+
f = tmp_path / "a.py"
|
|
44
|
+
f.write_text("x = 1\n", encoding="utf-8")
|
|
45
|
+
assert ledger.record_read(str(f)) == 0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_record_read_partial_prorated(compacted):
|
|
49
|
+
target = compacted / "pkg" / "mod.py"
|
|
50
|
+
full = ledger.record_read(str(target))
|
|
51
|
+
total_lines = len(target.read_text(encoding="utf-8").splitlines())
|
|
52
|
+
partial = ledger.record_read(str(target), limit=1)
|
|
53
|
+
assert 0 < partial < full
|
|
54
|
+
assert partial == pytest.approx(full / total_lines)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_hook_command_credits_read(compacted, monkeypatch):
|
|
58
|
+
target = compacted / "pkg" / "mod.py"
|
|
59
|
+
payload = {"tool_name": "Read", "tool_input": {"file_path": str(target)}}
|
|
60
|
+
monkeypatch.setattr("sys.stdin", io.StringIO(json.dumps(payload)))
|
|
61
|
+
assert cli.main(["hook"]) == 0
|
|
62
|
+
assert _ledger(compacted)["reads"] == 1
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_hook_command_survives_garbage(compacted, monkeypatch):
|
|
66
|
+
monkeypatch.setattr("sys.stdin", io.StringIO("not json"))
|
|
67
|
+
assert cli.main(["hook"]) == 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_stats_shows_cumulative(compacted, capsys):
|
|
71
|
+
ledger.record_read(str(compacted / "pkg" / "mod.py"))
|
|
72
|
+
capsys.readouterr()
|
|
73
|
+
cli.main(["stats"])
|
|
74
|
+
out = capsys.readouterr().out
|
|
75
|
+
assert "Agent savings so far: 1" in out
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_install_hook_project(compacted, capsys):
|
|
79
|
+
assert cli.main(["install-hook", "--project"]) == 0
|
|
80
|
+
settings = json.loads(
|
|
81
|
+
(compacted / ".claude" / "settings.json").read_text(encoding="utf-8"))
|
|
82
|
+
cmds = [h["command"]
|
|
83
|
+
for m in settings["hooks"]["PostToolUse"] for h in m["hooks"]]
|
|
84
|
+
assert hooks.HOOK_COMMAND in cmds
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_install_hook_idempotent_and_merging(compacted):
|
|
88
|
+
claude = compacted / ".claude"
|
|
89
|
+
claude.mkdir()
|
|
90
|
+
existing = {"permissions": {"allow": ["Bash(git *)"]},
|
|
91
|
+
"hooks": {"PostToolUse": [
|
|
92
|
+
{"matcher": "Write", "hooks": [{"type": "command", "command": "echo hi"}]}]}}
|
|
93
|
+
(claude / "settings.json").write_text(json.dumps(existing), encoding="utf-8")
|
|
94
|
+
cli.main(["install-hook", "--project"])
|
|
95
|
+
cli.main(["install-hook", "--project"])
|
|
96
|
+
settings = json.loads((claude / "settings.json").read_text(encoding="utf-8"))
|
|
97
|
+
assert settings["permissions"]["allow"] == ["Bash(git *)"]
|
|
98
|
+
post = settings["hooks"]["PostToolUse"]
|
|
99
|
+
assert len(post) == 2
|
|
100
|
+
cmds = [h["command"] for m in post for h in m["hooks"]]
|
|
101
|
+
assert cmds.count(hooks.HOOK_COMMAND) == 1 and "echo hi" in cmds
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|