@brandry/claude-jsonl-compressor 1.0.0-rc.1 → 1.0.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/CHANGELOG.md +29 -0
- package/README.md +34 -16
- package/SKILL.md +6 -1
- package/package.json +2 -2
- package/references/claude-jsonl-compression-format.md +81 -3
- package/scripts/claude_session_tools.py +34 -4
- package/scripts/compress_claude_jsonl.py +778 -155
- package/scripts/repair_claude_jsonl.py +32 -10
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
Notable public changes are recorded here. This project follows Semantic Versioning.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [1.0.0] - 2026-08-11
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Simplified Chinese and Japanese documentation for the public project.
|
|
12
|
+
- Stable machine-readable `reasonCode` values for resume-path rejection.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Enumerate Claude project sessions correctly beneath Windows 8.3 short-name roots.
|
|
17
|
+
- Bound public diagnostics for malformed JSONL metadata without changing the
|
|
18
|
+
underlying validation or compression decisions.
|
|
19
|
+
- Preflight file hard-link capability in the target directory and any explicit
|
|
20
|
+
backup directory before a live session is staged, backed up, or moved.
|
|
21
|
+
- Retain and report live-transaction temporary paths whose observed identity or
|
|
22
|
+
bytes change before cleanup, including cleanup residue after an earlier
|
|
23
|
+
transaction failure.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Run the hard-link and transaction suite on Windows, Linux, and macOS CI
|
|
28
|
+
runner volumes and print their filesystem type as diagnostic evidence.
|
|
29
|
+
- Promote the public npm package from release candidate to stable `1.0.0` on
|
|
30
|
+
the `latest` dist-tag.
|
|
31
|
+
|
|
5
32
|
## [1.0.0-rc.1] - 2026-07-28
|
|
6
33
|
|
|
7
34
|
Initial public release candidate.
|
|
@@ -24,4 +51,6 @@ Initial public release candidate.
|
|
|
24
51
|
- Live replacement handles one closed session at a time and requires Python 3.10 or newer.
|
|
25
52
|
- Parent-directory durability is best effort where the platform does not support directory fsync.
|
|
26
53
|
|
|
54
|
+
[Unreleased]: https://github.com/brandrylabs/claude-jsonl-compressor/compare/v1.0.0...HEAD
|
|
55
|
+
[1.0.0]: https://github.com/brandrylabs/claude-jsonl-compressor/releases/tag/v1.0.0
|
|
27
56
|
[1.0.0-rc.1]: https://github.com/brandrylabs/claude-jsonl-compressor/releases/tag/v1.0.0-rc.1
|
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Claude JSONL Compressor
|
|
2
2
|
|
|
3
|
+
[English](https://github.com/brandrylabs/claude-jsonl-compressor/blob/v1.0.0/README.md) | [简体中文](https://github.com/brandrylabs/claude-jsonl-compressor/blob/v1.0.0/docs/README.zh-CN.md) | [日本語](https://github.com/brandrylabs/claude-jsonl-compressor/blob/v1.0.0/docs/README.ja.md)
|
|
4
|
+
|
|
3
5
|
Strict, model-assisted compression for one Claude Code session transcript, plus an independent byte-preserving compatibility repair for historical `Read.pages` records.
|
|
4
6
|
|
|
5
|
-
**Release:** [`1.0.0
|
|
7
|
+
**Release:** [`1.0.0`](CHANGELOG.md)<br>
|
|
6
8
|
**Engine:** `v10`<br>
|
|
7
9
|
**Model-pack schema:** `v11`<br>
|
|
8
10
|
**License:** GPL-3.0-only<br>
|
|
@@ -94,9 +96,22 @@ Excluded records appear in reports only as counts and digests. Their text is not
|
|
|
94
96
|
- Python 3.10 or newer
|
|
95
97
|
- Node.js 22 or newer only when using the npm command wrappers
|
|
96
98
|
- Claude Code is optional; it is needed only for an explicitly requested runtime `/resume` or `/context` smoke test
|
|
99
|
+
- Hard-link support on the volume holding the target file, and on an explicitly configured backup directory, for `--replace-original` only
|
|
97
100
|
|
|
98
101
|
No Python package installation is required.
|
|
99
102
|
|
|
103
|
+
### Hard-link requirement for live replacement
|
|
104
|
+
|
|
105
|
+
`--replace-original` publishes the candidate with `os.link` so that it never overwrites a concurrent claimant, and the rollback path restores the captured original the same way. The target directory must therefore support file hard links. An explicitly supplied `--backup-dir` also publishes numbered backups with a hard link and must support the same operation.
|
|
106
|
+
|
|
107
|
+
Before live replacement staging, backup payload publication, or target movement, the compressor runs a unique, same-directory hard-link probe on the target volume. The probe briefly creates two dot-prefixed temporary files and removes them; a cleanup failure is reported with the retained probe name and stops the live operation. If the capability probe fails, the target remains at its original path and byte content, and no live replacement stage, backup payload, or target move has started. This probe is a capability check, not a guarantee against a later permission, quota, network, or concurrency failure; late publication, rollback, and cleanup failures are reported with their transaction state and verified recovery assets.
|
|
108
|
+
|
|
109
|
+
Each unique live-transaction temporary path is checked against its recorded filesystem identity and frozen bytes before cleanup. A detected mismatch is retained and reported as `committed-cleanup-failed` after a committed candidate, or alongside the primary failure before commit. Portable Python cannot bind the final pathname `unlink` atomically to that earlier identity check across Windows, Linux, and macOS. Run live replacement only with the session closed and no other writer; hostile same-account directory manipulation is outside this guarantee.
|
|
110
|
+
|
|
111
|
+
NTFS, APFS, ext4, XFS, and btrfs commonly support file hard links, while FAT/exFAT, some network shares, overlay or bind-mounted filesystems, and Windows ReFS may reject them. The probe is authoritative for the actual directory and account; the filesystem name alone is not treated as a guarantee. The repository CI runs the probe and transaction tests on Windows, Linux, and macOS runner volumes.
|
|
112
|
+
|
|
113
|
+
Candidate output is unaffected: it publishes through `os.replace` and has no hard-link dependency.
|
|
114
|
+
|
|
100
115
|
## Installation
|
|
101
116
|
|
|
102
117
|
### Install As A Codex Skill
|
|
@@ -134,10 +149,10 @@ The installed directory must contain `SKILL.md`, `scripts/`, `config/`, `templat
|
|
|
134
149
|
|
|
135
150
|
### Install The npm CLI
|
|
136
151
|
|
|
137
|
-
After
|
|
152
|
+
After version 1.0.0 is published:
|
|
138
153
|
|
|
139
154
|
```bash
|
|
140
|
-
npm install --global @brandry/claude-jsonl-compressor
|
|
155
|
+
npm install --global @brandry/claude-jsonl-compressor
|
|
141
156
|
```
|
|
142
157
|
|
|
143
158
|
This installs two commands:
|
|
@@ -152,7 +167,7 @@ The npm package is a zero-dependency Node shim over the bundled Python implement
|
|
|
152
167
|
Upgrade or uninstall the global CLI:
|
|
153
168
|
|
|
154
169
|
```bash
|
|
155
|
-
npm install --global @brandry/claude-jsonl-compressor
|
|
170
|
+
npm install --global @brandry/claude-jsonl-compressor
|
|
156
171
|
npm update --global @brandry/claude-jsonl-compressor
|
|
157
172
|
npm uninstall --global @brandry/claude-jsonl-compressor
|
|
158
173
|
```
|
|
@@ -160,7 +175,7 @@ npm uninstall --global @brandry/claude-jsonl-compressor
|
|
|
160
175
|
Local development install and invocation:
|
|
161
176
|
|
|
162
177
|
```bash
|
|
163
|
-
npm install --save-dev @brandry/claude-jsonl-compressor
|
|
178
|
+
npm install --save-dev @brandry/claude-jsonl-compressor
|
|
164
179
|
npm update @brandry/claude-jsonl-compressor
|
|
165
180
|
npm exec -- claude-jsonl-compressor --version
|
|
166
181
|
npm exec -- claude-jsonl-repair-read-pages --version
|
|
@@ -170,15 +185,15 @@ npm uninstall @brandry/claude-jsonl-compressor
|
|
|
170
185
|
Run without retaining an installation:
|
|
171
186
|
|
|
172
187
|
```bash
|
|
173
|
-
npx --yes --package @brandry/claude-jsonl-compressor
|
|
174
|
-
npx --yes --package @brandry/claude-jsonl-compressor
|
|
188
|
+
npx --yes --package @brandry/claude-jsonl-compressor claude-jsonl-compressor --version
|
|
189
|
+
npx --yes --package @brandry/claude-jsonl-compressor claude-jsonl-repair-read-pages --version
|
|
175
190
|
```
|
|
176
191
|
|
|
177
192
|
Actual npm/npx operations use the same Python CLI options:
|
|
178
193
|
|
|
179
194
|
```bash
|
|
180
|
-
npx --yes --package @brandry/claude-jsonl-compressor
|
|
181
|
-
npx --yes --package @brandry/claude-jsonl-compressor
|
|
195
|
+
npx --yes --package @brandry/claude-jsonl-compressor claude-jsonl-compressor --input session.jsonl --write-model-pack run/session.model-pack.md
|
|
196
|
+
npx --yes --package @brandry/claude-jsonl-compressor claude-jsonl-repair-read-pages --input session.jsonl --scan-only
|
|
182
197
|
```
|
|
183
198
|
|
|
184
199
|
### Use From Source Without Installing
|
|
@@ -209,6 +224,9 @@ python "$skill\scripts\compress_claude_jsonl.py" `
|
|
|
209
224
|
```
|
|
210
225
|
|
|
211
226
|
This is read-only. A nonzero result must be resolved before model-pack generation.
|
|
227
|
+
Use `reasonCode` for the exact machine-readable cause; `status` is only the
|
|
228
|
+
coarse category. The stable value table is in
|
|
229
|
+
[`references/claude-jsonl-compression-format.md`](references/claude-jsonl-compression-format.md#authoritative-last-prompt).
|
|
212
230
|
|
|
213
231
|
### 2. Generate A Model Evidence Pack
|
|
214
232
|
|
|
@@ -541,24 +559,24 @@ python -B -I -S scripts/compress_claude_jsonl.py --version
|
|
|
541
559
|
python -B -I -S scripts/repair_claude_jsonl.py --version
|
|
542
560
|
npm test
|
|
543
561
|
npm pack --dry-run --json
|
|
544
|
-
npm publish --dry-run --access public --tag
|
|
562
|
+
npm publish --dry-run --access public --tag latest
|
|
545
563
|
```
|
|
546
564
|
|
|
547
565
|
The release suite covers active/dead branch partitioning, fixed-seed topology transformations, strict pointer failures, dual model-pack budgets, complete structured token accounting, multilingual semantic ledgers and thinking, handoffs, request/claim digests, mandatory support excerpts, tool pairs, repeated compression, checkpoint policies, transaction races and committed-report states, exact byte repair, BOM/CRLF, npm tarball allowlisting and offline tarball installation.
|
|
548
566
|
|
|
549
|
-
### Maintainer
|
|
567
|
+
### Maintainer Stable Release Checklist
|
|
550
568
|
|
|
551
|
-
1. Confirm a clean public tree and matching `1.0.0
|
|
569
|
+
1. Confirm a clean public tree and matching `1.0.0` values in `package.json`, Python version output, docs, and tests.
|
|
552
570
|
2. Run the Python, npm, isolated-Python, tarball, privacy, and offline-install gates above.
|
|
553
571
|
3. Inspect `npm pack --dry-run --json`; publish only the allowlisted files.
|
|
554
|
-
4. Require a clean worktree, create annotated tag `v1.0.0
|
|
555
|
-
5.
|
|
572
|
+
4. Require a clean worktree, create annotated tag `v1.0.0`, and push the commit and tag.
|
|
573
|
+
5. Publish from an authenticated maintainer environment with permission to publish this package:
|
|
556
574
|
|
|
557
575
|
```bash
|
|
558
|
-
npm publish --access public --tag
|
|
576
|
+
npm publish --access public --tag latest
|
|
559
577
|
```
|
|
560
578
|
|
|
561
|
-
6. Verify
|
|
579
|
+
6. Verify npm version `1.0.0` and the `latest` dist-tag, then create a GitHub release (not a prerelease) from the already-pushed tag.
|
|
562
580
|
|
|
563
581
|
Do not append `--provenance` to a local publish. npm provenance requires a supported cloud CI runner. For later releases, prefer npm trusted publishing from a public GitHub repository on a GitHub-hosted runner with `id-token: write`, a protected release tag, and a matching protected environment; trusted publishing generates provenance automatically.
|
|
564
582
|
|
package/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ description: Compress one Claude Code JSONL session with strict active-branch is
|
|
|
7
7
|
|
|
8
8
|
Operate on exactly one authoritative JSONL. Never merge another branch or session automatically.
|
|
9
9
|
|
|
10
|
-
Public package: `1.0.0
|
|
10
|
+
Public package: `1.0.0`. Internal engine: `v10`. Model-pack schema: `v11`.
|
|
11
11
|
|
|
12
12
|
## Safety Invariants
|
|
13
13
|
|
|
@@ -73,6 +73,10 @@ python "$skill\scripts\compress_claude_jsonl.py" `
|
|
|
73
73
|
--analyze-resume-path
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
Use `reasonCode` as the exact machine-readable outcome and `status` only as its
|
|
77
|
+
coarse category. The stable pairs are documented in
|
|
78
|
+
`references/claude-jsonl-compression-format.md`.
|
|
79
|
+
|
|
76
80
|
Strict active mode stops before writing any pack, candidate, sidecar or backup when the authority is absent, malformed, dangling, cyclic, has unsafe/recurring session lineage, has ordinary-message physical parent inversion, contains a malformed `parentUuid`, or is UUID-ambiguous.
|
|
77
81
|
|
|
78
82
|
After partitioning, run the shared validator on the authoritative logical active chain plus its projected pointer. Old malformed tool exchanges, duplicate tool IDs, or compact-pair metadata on that chain must stop before semantic evidence generation; damage confined to excluded inactive branches remains excluded and does not become summary text.
|
|
@@ -336,5 +340,6 @@ Use `--scan-titles` only when the user supplies a title and permits title scanni
|
|
|
336
340
|
- A source hash change aborts live replacement.
|
|
337
341
|
- Live replacement requires the Claude process for that session to be closed. The transaction validates immutable candidate bytes, exclusively creates and verifies a numbered backup, captures the actual old target, verifies its full SHA-256, installs the candidate, and verifies the published bytes and structure. If another process recreates the target during capture, preserve the external target and recovery backups and fail without publishing. Parent-directory fsync is best effort and reported because platform support differs.
|
|
338
342
|
- A write/fsync/validation/replace failure returns nonzero. The transaction restores the captured original bytes when replacement began; if restoration itself fails, it raises a high-priority error and retains the numbered backup for recovery.
|
|
343
|
+
- Each unique live-transaction temporary path is checked against its recorded filesystem identity and frozen bytes before cleanup. A detected mismatch is retained and reported; a pre-commit failure also includes any cleanup residue in its error. Portable pathname cleanup cannot atomically bind that final delete to the earlier identity check across Windows, Linux, and macOS, so require a closed session and no other writer. Do not claim protection from hostile same-account directory manipulation.
|
|
339
344
|
- If a valid live replacement commits but final sidecar/report publication fails, return exit code 3 with `committed-report-failed` and the committed hashes/backup labels. Do not rerun blindly or describe that state as an uncommitted failure.
|
|
340
345
|
- Keep reports and temporary work outside `.claude`; do not leave ad hoc files in live session directories.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandry/claude-jsonl-compressor",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Strict, model-assisted compression and byte-preserving compatibility repair for one Claude Code JSONL session.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -55,6 +55,6 @@
|
|
|
55
55
|
},
|
|
56
56
|
"publishConfig": {
|
|
57
57
|
"access": "public",
|
|
58
|
-
"tag": "
|
|
58
|
+
"tag": "latest"
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Claude Code JSONL Compression Format Notes
|
|
2
2
|
|
|
3
3
|
This document describes the empirical format handled by
|
|
4
|
-
`@brandry/claude-jsonl-compressor` package `1.0.0
|
|
4
|
+
`@brandry/claude-jsonl-compressor` package `1.0.0` and engine `v10`.
|
|
5
5
|
|
|
6
6
|
Claude Code transcript JSONL is an observed internal format, not a published
|
|
7
7
|
stable storage API. The rules below are deliberately strict where ambiguity
|
|
@@ -14,7 +14,7 @@ The project keeps four independent version domains:
|
|
|
14
14
|
|
|
15
15
|
| Domain | Current value | Meaning |
|
|
16
16
|
| --- | --- | --- |
|
|
17
|
-
| Package | `1.0.0
|
|
17
|
+
| Package | `1.0.0` | GitHub/npm release version |
|
|
18
18
|
| Compression engine | `v10` | Topology, partition, and rewrite behavior |
|
|
19
19
|
| Model-pack schema | `v11` | Evidence-pack and model-summary binding protocol |
|
|
20
20
|
| Report schema | `1` | Compression and repair report fields |
|
|
@@ -39,6 +39,38 @@ A transcript can contain records that play very different roles:
|
|
|
39
39
|
|
|
40
40
|
Physical line order alone is therefore not a definition of current context.
|
|
41
41
|
|
|
42
|
+
### Physical line accounting
|
|
43
|
+
|
|
44
|
+
The JSONL framer recognizes records only at physical LF boundaries, removes a
|
|
45
|
+
single CR before each LF, and skips blank physical lines. The parser keeps a
|
|
46
|
+
trusted side map from each parsed record to its original 1-based physical line
|
|
47
|
+
number. `validate_jsonl` and the `Read.pages` repair report use that map, so a
|
|
48
|
+
blank line or an excluded branch cannot make an error point at the wrong file
|
|
49
|
+
line. Input `_line`, `_mergedLines`, `_mergedUuids`, and
|
|
50
|
+
`_validationContentLines` fields are untrusted and ignored; validation rebuilds
|
|
51
|
+
record- and content-block provenance in an isolated projection.
|
|
52
|
+
|
|
53
|
+
The model evidence pack uses its own `L<number>` logical nonblank-record
|
|
54
|
+
coordinates because it is a reserialized evidence view; those anchors are not
|
|
55
|
+
claims about physical source-file line numbers. Repair `recordLine`, validator
|
|
56
|
+
diagnostic `line` fields, and the source active-chain preflight used by
|
|
57
|
+
compression/model-pack generation, in contrast, refer to the original file's
|
|
58
|
+
physical lines. The active-chain preflight is a logical projection, so it
|
|
59
|
+
carries source-line provenance separately rather than renumbering the chain.
|
|
60
|
+
|
|
61
|
+
Validation/report counters and `type`/`sessionId`/`subtype` metadata carried
|
|
62
|
+
by validator or system-error diagnostics preserve ordinary short strings.
|
|
63
|
+
Synthetic keys use the collision-safe angle-bracket namespace: `<missing>`,
|
|
64
|
+
`<null>`, and `<invalid:json-type>`. A real string beginning with `<` is
|
|
65
|
+
escaped by adding a second leading `<`; an unusually long real string is
|
|
66
|
+
represented by a bounded `<string:length=...;sha256=...>` label that contains
|
|
67
|
+
no original characters. Complex
|
|
68
|
+
malformed values are labeled by JSON type rather than copied into diagnostics
|
|
69
|
+
or model evidence. The same bound is applied recursively to public validator
|
|
70
|
+
samples, nested diagnostic metadata, identifier-valued dictionary keys, and
|
|
71
|
+
oversized source values embedded in error text. Ordinary error wording and
|
|
72
|
+
physical-line locations remain readable when they contain only normal values.
|
|
73
|
+
|
|
42
74
|
## Common Observed Record Types
|
|
43
75
|
|
|
44
76
|
| `type` | Observed purpose |
|
|
@@ -107,6 +139,39 @@ Strict topology statuses include:
|
|
|
107
139
|
- `extension-unsafe`
|
|
108
140
|
- `valid`
|
|
109
141
|
|
|
142
|
+
`status` is the coarse category. `reasonCode` is the stable, machine-readable
|
|
143
|
+
cause. Current producers emit exactly one of these pairs:
|
|
144
|
+
|
|
145
|
+
| `status` | `reasonCode` | Meaning |
|
|
146
|
+
| --- | --- | --- |
|
|
147
|
+
| `absent` | `last-prompt-absent` | No authoritative `last-prompt` exists |
|
|
148
|
+
| `duplicate-uuid` | `duplicate-uuid` | At least one UUID is ambiguous |
|
|
149
|
+
| `malformed` | `leaf-uuid-malformed` | The authoritative `leafUuid` is absent, empty or not a string |
|
|
150
|
+
| `dangling` | `chain-missing-uuid` | The selected chain references an unknown UUID |
|
|
151
|
+
| `loop` | `chain-loop` | The selected chain contains a parent loop |
|
|
152
|
+
| `malformed-parent` | `chain-malformed-parent` | A selected record has an invalid non-null `parentUuid` |
|
|
153
|
+
| `dangling` | `chain-empty` | Defensive outcome when tracing returns no chain and no more specific error |
|
|
154
|
+
| `non-monotonic` | `chain-non-monotonic` | Physical parent order violates the accepted compatibility rule |
|
|
155
|
+
| `session-mismatch` | `lineage-unsafe` | Session ancestry is not an accepted one-way lineage |
|
|
156
|
+
| `extension-limit` | `extension-limit-exceeded` | Post-pointer records exceed the explicit closure limit |
|
|
157
|
+
| `session-mismatch` | `extension-authority-session-missing` | A requested closure has no usable authority session |
|
|
158
|
+
| `extension-unsafe` | `extension-record-missing-uuid` | A closure record has no usable UUID |
|
|
159
|
+
| `extension-branch` | `extension-record-not-linear-descendant` | A closure record is not the next direct child |
|
|
160
|
+
| `session-mismatch` | `extension-record-session-mismatch` | A closure record does not match the authority session |
|
|
161
|
+
| `extension-unsafe` | `extension-record-not-safe-closure` | A closure record is not a tool-result-only safe closure |
|
|
162
|
+
| `extension-unsafe` | `extension-pending-tool-ids` | The requested closure leaves tool calls unresolved |
|
|
163
|
+
| `valid` | `ok` | Strict topology validation succeeded |
|
|
164
|
+
|
|
165
|
+
For `lineage-unsafe`, `lineageReason` carries the lower-level lineage diagnosis.
|
|
166
|
+
Human-readable `errors` text is diagnostic and is not an enum. Normal
|
|
167
|
+
compression/model-pack/repair failures also include `reasonCode=<value>` in the
|
|
168
|
+
raised CLI error.
|
|
169
|
+
|
|
170
|
+
Report-schema v1 objects and `compactMetadata` are open, additive objects:
|
|
171
|
+
consumers must ignore unknown fields. Stable producers include `reasonCode` on
|
|
172
|
+
every strict topology result. A pre-stable v1 artifact may lack it; readers
|
|
173
|
+
should treat that as legacy/unknown rather than infer `ok`.
|
|
174
|
+
|
|
110
175
|
`--resume-leaf UUID` is an explicit recovery override. It is reported as
|
|
111
176
|
`active-chain-manual-override`, distinct from default strict `active-chain`.
|
|
112
177
|
`--preserve-physical-tail` is a separate legacy compatibility
|
|
@@ -175,7 +240,7 @@ The engine emits exactly one current Codex-created compact pair:
|
|
|
175
240
|
Simplified synthetic shape:
|
|
176
241
|
|
|
177
242
|
```json
|
|
178
|
-
{"type":"system","subtype":"compact_boundary","uuid":"BOUNDARY_UUID","parentUuid":null,"compactMetadata":{"codexOfflineCompression":true,"codexOfflineCompressionVersion":"v10","modelPackSchemaVersion":11,"reportSchemaVersion":1,"summaryUuid":"SUMMARY_UUID","preserveMode":"active-chain","resumeLeafInfo":{"status":"valid","selectedLeafUuid":"ACTIVE_LEAF"}}}
|
|
243
|
+
{"type":"system","subtype":"compact_boundary","uuid":"BOUNDARY_UUID","parentUuid":null,"compactMetadata":{"codexOfflineCompression":true,"codexOfflineCompressionVersion":"v10","modelPackSchemaVersion":11,"reportSchemaVersion":1,"summaryUuid":"SUMMARY_UUID","preserveMode":"active-chain","resumeLeafInfo":{"status":"valid","reasonCode":"ok","selectedLeafUuid":"ACTIVE_LEAF"}}}
|
|
179
244
|
{"type":"user","uuid":"SUMMARY_UUID","parentUuid":"BOUNDARY_UUID","isCompactSummary":true,"message":{"role":"user","content":"SUMMARY_TEXT"}}
|
|
180
245
|
```
|
|
181
246
|
|
|
@@ -460,6 +525,19 @@ does not overwrite the external target. It preserves the verified numbered
|
|
|
460
525
|
backup and, when necessary, places the captured original in another numbered
|
|
461
526
|
backup, then fails without publishing the candidate.
|
|
462
527
|
|
|
528
|
+
Before any live staging, backup payload publication, or target move, the
|
|
529
|
+
implementation probes file hard-link support in the target directory and, when
|
|
530
|
+
configured, the external backup directory. The probe itself briefly creates
|
|
531
|
+
and removes unique dot-prefixed temporary files. Every unique live-transaction
|
|
532
|
+
temporary path records its observed filesystem identity and frozen bytes.
|
|
533
|
+
Cleanup rechecks both and retains a detected mismatch. A mismatch after commit is reported as
|
|
534
|
+
`committed-cleanup-failed`; a mismatch while an earlier transaction failure is
|
|
535
|
+
already active is appended to that failure. This is a best-effort race check,
|
|
536
|
+
not a portable atomic identity-bound delete: standard Python pathname cleanup
|
|
537
|
+
cannot eliminate a replacement between its final check and `unlink()` across
|
|
538
|
+
Windows, Linux, and macOS. Live operation assumes Claude Code is closed and no
|
|
539
|
+
other writer is manipulating the session directory.
|
|
540
|
+
|
|
463
541
|
Candidate-mode reports are written with the candidate. Live compression delays
|
|
464
542
|
its final sidecar/report until replacement metadata is available, so it does not
|
|
465
543
|
leave a stale pre-commit report. If the JSONL commits and validates but final
|
|
@@ -36,6 +36,25 @@ def eprint(*parts: object) -> None:
|
|
|
36
36
|
|
|
37
37
|
configure_stdio()
|
|
38
38
|
|
|
39
|
+
MIN_SUPPORTED_PYTHON = (3, 10)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def warn_if_python_too_old() -> Optional[str]:
|
|
43
|
+
"""Warn on an unsupported interpreter without blocking the run."""
|
|
44
|
+
if sys.version_info >= MIN_SUPPORTED_PYTHON:
|
|
45
|
+
return None
|
|
46
|
+
running = ".".join(str(part) for part in sys.version_info[:3])
|
|
47
|
+
required = ".".join(str(part) for part in MIN_SUPPORTED_PYTHON)
|
|
48
|
+
message = (
|
|
49
|
+
f"WARNING: running on Python {running}; this project documents Python {required} or newer. "
|
|
50
|
+
"Continuing anyway. Unexpected errors may be caused by the interpreter version."
|
|
51
|
+
)
|
|
52
|
+
eprint(message)
|
|
53
|
+
return message
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
warn_if_python_too_old()
|
|
57
|
+
|
|
39
58
|
|
|
40
59
|
def read_jsonl_records(path: pathlib.Path) -> List[Dict[str, Any]]:
|
|
41
60
|
records: List[Dict[str, Any]] = []
|
|
@@ -57,13 +76,25 @@ def list_session_files(root: pathlib.Path) -> List[pathlib.Path]:
|
|
|
57
76
|
found: List[pathlib.Path] = []
|
|
58
77
|
for dir_path, dir_names, file_names in os.walk(root, topdown=True, followlinks=False):
|
|
59
78
|
directory = pathlib.Path(dir_path)
|
|
79
|
+
# Resolve the containing directory once and compare each child against
|
|
80
|
+
# `resolved_directory / name`. Comparing child.resolve() against
|
|
81
|
+
# os.path.abspath(child) instead would reject every entry whenever any
|
|
82
|
+
# component of the root is a Windows 8.3 short name, because resolve()
|
|
83
|
+
# expands short names to their long form and abspath() leaves them as
|
|
84
|
+
# written. A symlink or junction that leaves the directory still
|
|
85
|
+
# resolves somewhere other than resolved_directory / name, so the
|
|
86
|
+
# escape guard is unchanged.
|
|
87
|
+
try:
|
|
88
|
+
resolved_directory = directory.resolve()
|
|
89
|
+
except OSError:
|
|
90
|
+
dir_names[:] = []
|
|
91
|
+
continue
|
|
60
92
|
safe_dirs: List[str] = []
|
|
61
93
|
for name in dir_names:
|
|
62
94
|
child = directory / name
|
|
63
95
|
try:
|
|
64
96
|
resolved = child.resolve()
|
|
65
|
-
|
|
66
|
-
if resolved != lexical or not is_same_or_inside(resolved, root_resolved):
|
|
97
|
+
if resolved != resolved_directory / name or not is_same_or_inside(resolved, root_resolved):
|
|
67
98
|
continue
|
|
68
99
|
except OSError:
|
|
69
100
|
continue
|
|
@@ -75,8 +106,7 @@ def list_session_files(root: pathlib.Path) -> List[pathlib.Path]:
|
|
|
75
106
|
candidate = directory / name
|
|
76
107
|
try:
|
|
77
108
|
resolved = candidate.resolve()
|
|
78
|
-
|
|
79
|
-
if resolved != lexical or not is_same_or_inside(resolved, root_resolved):
|
|
109
|
+
if resolved != resolved_directory / name or not is_same_or_inside(resolved, root_resolved):
|
|
80
110
|
continue
|
|
81
111
|
if not candidate.is_file():
|
|
82
112
|
continue
|