Skip to content

Commit 9e5bd0a

Browse files
authored
chore(release): v1.1.1 (#12)
Patch release bundling every main-branch delta since the v1.1.0 marketplace artifact, headlined by the claude --print CWD pollution fix from PR #11. - Bump .claude-plugin/plugin.json to 1.1.1 so the plugin marketplace cache re-fetches the patched code on next refresh. - Add scripts/check_summariser_pollution.py + tests as the regression detector for the cwd-pollution bug. Stdlib only, exits non-zero with a file list if any .jsonl outside ~/.claude/projects/-root--lossless-code--cli-cwd contains the summariser prompt. - Expand CHANGELOG.md to cover the full v1.1.0 -> v1.1.1 surface (cwd fix, fingerprint feature, db package split, BM25 eviction, session filtering, circuit breaker, claude-cli provider, etc). - Document the second deployment gap in the cwd-pollution learning doc: marketplace cache is a separate artifact from the manual install, so install.sh alone cannot deliver the fix to plugin users.
1 parent 8c7d0f8 commit 9e5bd0a

4 files changed

Lines changed: 216 additions & 3 deletions

File tree

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "lossless-code",
33
"description": "DAG-based lossless context management for Claude Code. Every message from every session is persisted in a SQLite vault with FTS5 search. Summaries form a directed acyclic graph - nothing is ever lost.",
4-
"version": "1.1.0",
4+
"version": "1.1.1",
55
"author": {
66
"name": "GodsBoy",
77
"email": "dhuysamen@gmail.com"

CHANGELOG.md

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
11
# Changelog
22

3-
## Unreleased
3+
## v1.1.1 — 2026-04-14
44

5-
### Added — fingerprint file context (default off)
5+
Patch release that ships every main-branch delta accumulated since the
6+
v1.1.0 marketplace artifact, headlined by the `claude --print` CWD fix
7+
that closes the `claude --resume` pollution bug. Marketplace users
8+
should upgrade to pick up the fix; manual installs already covered by
9+
re-running `install.sh`.
10+
11+
### Fixed
12+
13+
- **`claude --print` subprocess CWD pollution** (PR #11). The
14+
summariser's `claude --print` calls now pin `cwd=` to
15+
`~/.lossless-code/.cli-cwd`, so Claude Code files internal
16+
summarisation sessions in their own bucket instead of polluting the
17+
user's `claude --resume` list with hundreds of "Summarise the
18+
following conversation turns concisely…" entries. Includes
19+
`scripts/check_summariser_pollution.py` regression detector.
20+
- **Installer + README sync for fingerprint feature** (PR #10).
21+
`install.sh` now copies the new PreToolUse / PostToolUse hooks and
22+
the `scripts/file_context.py`, `scripts/hook_store_tool_call.py`
23+
files added by the fingerprint feature; README documents the new
24+
`fileContextEnabled` flag and the hook surface.
25+
26+
### Added — fingerprint file context (default off, PR #9)
627

728
- **`fileContextEnabled` config flag** (default `false`) gates all new
829
behavior. Flip to `true` in `~/.lossless-code/config.json` to opt in.
@@ -30,3 +51,42 @@
3051
from a file path to the recent summaries that reference it.
3152
- **`lcc status`** surfaces tagged message count, distinct tagged files,
3253
and cached fingerprint count when the flag is on.
54+
55+
### Changed
56+
57+
- **`scripts/db.py` split into `scripts/db/` package** (PR #8). Single
58+
745-line module replaced with focused submodules: `config`,
59+
`schema`, `messages`, `summaries`, `sessions`, `embeddings`,
60+
`search`, `dream_log`. Public import surface is preserved via
61+
`scripts/db/__init__.py`; existing call sites unchanged.
62+
- **BM25 prompt-aware context eviction** (PR #7). Context injection now
63+
scores candidate summaries against the live prompt with BM25 instead
64+
of a flat recency cut, so high-signal older summaries survive
65+
eviction when the prompt actually references them.
66+
67+
### Added — earlier in the v1.1.0 → v1.1.1 window
68+
69+
- **Session filtering for lossless-claw v0.7.0 parity**: stateless
70+
session gate, stop-hook filtering, plus DB-layer tests covering
71+
pattern matching and filter behaviour.
72+
- **Circuit breaker + dynamic chunk sizing for summarisation**:
73+
protects the summariser from runaway provider failures and adapts
74+
chunk size to the active provider's context window.
75+
- **Vault bloat protection**: summary size caps and dream pagination
76+
prevent unbounded growth on long-running vaults.
77+
- **`claude-cli` provider** for Claude Max / Pro subscription users
78+
(no API key path). `ANTHROPIC_API_KEY` is stripped from the
79+
subprocess env so the CLI falls back to OAuth.
80+
- **Custom Anthropic-compatible providers** (MiniMax and others) via
81+
`openaiBaseUrl`, plus provider auto-detection and structured error
82+
handling.
83+
- **`dream --status` flag** for inspecting dream-cycle state.
84+
- **`scripts/check_summariser_pollution.py`** stdlib regression detector
85+
for the cwd pollution bug. Walks `~/.claude/projects/`, skips the
86+
legitimate `-root--lossless-code--cli-cwd` bucket, exits non-zero
87+
with a file list if any other bucket contains a polluting `.jsonl`.
88+
89+
## v1.1.0 — semantic search hybrid
90+
91+
- Hybrid semantic search combining FTS5 BM25 with vector similarity via
92+
reciprocal rank fusion. Cached `fastembed.TextEmbedding` instance.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env python3
2+
"""Detect summariser CWD pollution in Claude Code project session buckets.
3+
4+
Walks ``~/.claude/projects/`` (override with ``LOSSLESS_CHECK_PROJECTS_DIR``),
5+
skipping the legitimate ``-root--lossless-code--cli-cwd`` bucket. Any ``.jsonl``
6+
file containing the lossless-code summariser prompt outside that bucket is a
7+
regression of the cwd-pinning fix and causes a non-zero exit.
8+
9+
Exit codes:
10+
0 no pollution found (or projects dir missing)
11+
1 one or more polluting files found (printed to stdout)
12+
"""
13+
14+
from __future__ import annotations
15+
16+
import os
17+
import sys
18+
from pathlib import Path
19+
20+
LEGIT_BUCKET = "-root--lossless-code--cli-cwd"
21+
NEEDLE = (
22+
b"Summarise the following conversation turns concisely, "
23+
b"preserving all key decisions, facts, file paths, commands"
24+
)
25+
SCAN_BYTES = 8192
26+
27+
28+
def projects_dir() -> Path:
29+
override = os.environ.get("LOSSLESS_CHECK_PROJECTS_DIR")
30+
if override:
31+
return Path(override)
32+
return Path.home() / ".claude" / "projects"
33+
34+
35+
def file_is_polluting(path: Path) -> bool:
36+
try:
37+
with path.open("rb") as fh:
38+
head = fh.read(SCAN_BYTES)
39+
except OSError:
40+
return False
41+
return NEEDLE in head
42+
43+
44+
def find_polluting(root: Path) -> list[Path]:
45+
if not root.is_dir():
46+
return []
47+
hits: list[Path] = []
48+
for bucket in sorted(root.iterdir()):
49+
if not bucket.is_dir() or bucket.name == LEGIT_BUCKET:
50+
continue
51+
for entry in bucket.rglob("*.jsonl"):
52+
if entry.is_file() and file_is_polluting(entry):
53+
hits.append(entry)
54+
return hits
55+
56+
57+
def main() -> int:
58+
hits = find_polluting(projects_dir())
59+
if not hits:
60+
return 0
61+
print(f"Found {len(hits)} polluting jsonl file(s) outside {LEGIT_BUCKET}:")
62+
for path in hits:
63+
print(f" {path}")
64+
return 1
65+
66+
67+
if __name__ == "__main__":
68+
sys.exit(main())
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env python3
2+
"""Tests for scripts/check_summariser_pollution.py regression detector."""
3+
4+
import os
5+
import subprocess
6+
import sys
7+
import tempfile
8+
import unittest
9+
from pathlib import Path
10+
11+
SCRIPT = Path(__file__).resolve().parent.parent / "scripts" / "check_summariser_pollution.py"
12+
13+
POLLUTING_LINE = (
14+
'{"type":"user","message":{"role":"user","content":"Summarise the following '
15+
'conversation turns concisely, preserving all key decisions, facts, file paths, '
16+
'commands and outputs"}}\n'
17+
)
18+
CLEAN_LINE = '{"type":"user","message":{"role":"user","content":"hello world"}}\n'
19+
20+
21+
def _run(projects_dir: Path) -> subprocess.CompletedProcess:
22+
env = dict(os.environ)
23+
env["LOSSLESS_CHECK_PROJECTS_DIR"] = str(projects_dir)
24+
return subprocess.run(
25+
[sys.executable, str(SCRIPT)],
26+
env=env,
27+
capture_output=True,
28+
text=True,
29+
)
30+
31+
32+
def _write_jsonl(path: Path, body: str) -> None:
33+
path.parent.mkdir(parents=True, exist_ok=True)
34+
path.write_text(body)
35+
36+
37+
class TestCheckSummariserPollution(unittest.TestCase):
38+
def setUp(self):
39+
self.tmp = tempfile.TemporaryDirectory()
40+
self.addCleanup(self.tmp.cleanup)
41+
self.projects = Path(self.tmp.name)
42+
43+
def test_zero_polluting_three_clean_exits_0(self):
44+
bucket = self.projects / "-root-foo"
45+
for i in range(3):
46+
_write_jsonl(bucket / f"clean{i}.jsonl", CLEAN_LINE)
47+
result = _run(self.projects)
48+
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
49+
50+
def test_polluting_files_exit_1_and_listed(self):
51+
bucket = self.projects / "-root-foo"
52+
_write_jsonl(bucket / "bad1.jsonl", POLLUTING_LINE)
53+
_write_jsonl(bucket / "bad2.jsonl", POLLUTING_LINE)
54+
_write_jsonl(bucket / "ok.jsonl", CLEAN_LINE)
55+
result = _run(self.projects)
56+
self.assertEqual(result.returncode, 1)
57+
self.assertIn("bad1.jsonl", result.stdout)
58+
self.assertIn("bad2.jsonl", result.stdout)
59+
self.assertNotIn("ok.jsonl", result.stdout)
60+
61+
def test_polluting_in_cli_cwd_bucket_ignored(self):
62+
bucket = self.projects / "-root--lossless-code--cli-cwd"
63+
_write_jsonl(bucket / "legit.jsonl", POLLUTING_LINE)
64+
result = _run(self.projects)
65+
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
66+
67+
def test_empty_jsonl_is_clean(self):
68+
bucket = self.projects / "-root-foo"
69+
_write_jsonl(bucket / "empty.jsonl", "")
70+
result = _run(self.projects)
71+
self.assertEqual(result.returncode, 0)
72+
73+
def test_malformed_jsonl_does_not_crash(self):
74+
bucket = self.projects / "-root-foo"
75+
_write_jsonl(bucket / "junk.jsonl", "not json at all\n{broken")
76+
result = _run(self.projects)
77+
self.assertEqual(result.returncode, 0)
78+
79+
def test_missing_projects_dir_exits_0(self):
80+
result = _run(self.projects / "does-not-exist")
81+
self.assertEqual(result.returncode, 0)
82+
83+
84+
if __name__ == "__main__":
85+
unittest.main()

0 commit comments

Comments
 (0)