Every acceptance clause is met: vendored source green under the pinned
zig 0.16 toolchain via the make.tcl tool step (zig optional), upstream
extract-to-directory + pinned porcelain listing, punk::zip accelerator
with the parity suite (identical names/bytes/mtimes both engines over
the G-124 shapes and the file-relative piperepl kit; pure path proven
when absent), recorded benchmark (crossover ~15-20 members, material
from ~50, x2.3-2.6 native / x3.8-4.9 kit-hosted), and PROVENANCE.md
origin/re-vendor/licence records.
Index entry moved to GOALS-archive.md; detail Status flipped (the file
moves to goals/archive/ in the adjacent pure-rename commit). Reference
sweep: G-101's pending-tense accelerator mention rewritten as achieved
with the archive path; G-128's Depends-on gains the archive pointer
plus the now-existing pattern surface and the zig-0.16 traps its port
should read first.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
The final G-126 acceptance clauses:
- punk::zip::accelerator (auto|none|<path>): auto probes
env(PUNKZIP_EXE) then a punkzip beside [info nameofexecutable];
resolution cached until reconfigured
- unzip hands whole-archive member WRITING to the accelerator only when
the call is one it serves identically (globs {*}, no excludes,
default -overwrite/-mtime/-verify, ascii member names) and re-stamps
mtimes with punk::zip's local-time convention afterwards, so the two
engines produce identical trees; preflight refusals, member selection
and the returned names always come from punk::zip's own reader (the
G-124 floor, unchanged); accelerator failure falls back to pure Tcl
silently; last_unzip_engine/last_accelerator_note expose the per-call
decision
- parity suite zipaccel.test (9 tests): identical names, bytes and
mtimes through both engines over the G-124 shapes and whole-kit
extraction of the file-relative tclsh90b4_piperepl.exe (841 members);
listings engine-independent; punkzip porcelain agrees with punk::zip
member dicts on every shared field (mtime excluded by design - UTC vs
local convention); pure path proven when the binary is absent;
selective calls proven to run pure. Implicit directories (kits store
no dir entries) are excluded from mtime comparison - neither engine
stamps them
- recorded benchmark (in the goal detail): accelerator fixed cost
~9-13ms, crossover ~15-20 members, material (>= x1.7) from ~50;
x2.3-2.6 at kit scale under a native tclsh and x3.8-4.9 kit-hosted
(punk91.exe 21.9s -> 5.7s) - the bake-from-kit case the goal
motivated
- full runtests at recorded baseline under tclsh9.0.5-punk: 1186/1211,
1 failed = the pre-existing exec-14.3 baseline
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
New surface for the vendored first-party build tools under src/tools:
make.tcl tool list|info|build|test ?<toolname> ...? ?-test 0|1?
- discovery is convention-based (a src/tools directory carrying
build.zig is a tool), so the G-128 tool will appear with zero
make.tcl edits; per-tool records are read tolerantly from
build.zig.zon (version, minimum_zig_version floor) and PROVENANCE.md
(upstream, vendored commit)
- list/info report installed-binary state (current|stale|absent vs
bin/<name><exe-suffix>) and the resolved toolchain
- toolchain resolution: PUNK_ZIG=<path> overrides; otherwise
bin/tools/zig* is scanned and the LOWEST release satisfying the floor
wins - deterministic as newer toolchains get unpacked beside the
pinned one; dev builds count only when their base version exceeds
the floor
- build runs the tree's own 'zig build test' as an EXIT-CODE gate
(expected stderr warnings do not fail it), then builds ReleaseSafe
and installs to bin/; nothing installs when the gate or build fails;
-test 0 skips the gate; build caches are deliberately left in place
between runs for rebuild speed (git- and fossil-ignored)
- zig stays OPTIONAL: packages/bake never require the step; without a
suitable toolchain, list reports the state and build/test exit
nonzero with bin/punk-getzig.cmd guidance
- full subcommand accompaniments per src/AGENTS.md: punk::args argdoc
(SUMMARIES/HELPTEXTS/bespoke passthrough definition), SUBGROUPS
("vendored tools"), known_commands, degraded-mode dispatch, plain
help text, bootsupport-staleness exemption, workflow text (new
OUT-OF-BAND SUBSYSTEMS section also covering buildsuite), project
version 0.28.3 + CHANGELOG entry
- piped characterization (zig-independent):
src/tests/shell/testsuites/punkexe/maketcltool.test - 4/4 green
- verified live: 'tool build punkzip' gated on the vendored tree's
130/130 suite and installed bin/punkzip.exe (ReleaseSafe, v2.3.1,
1.1MB) over the stale Nov-2024 v2.1.0 binary; list state flipped
stale -> current; PUNKBOOT_PLAIN degraded dispatch and tabled help
verified
G-126 Progress updated: the acceptance's build-step clause is
satisfied. Remaining: punk::zip fast path, parity suite, benchmark.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Vendored per the goal's developer decision: build.zig, build.zig.zon,
LICENSE + LICENSE-time-dot-zig, README.md and src/ (sources and test
fixtures) from the clean upstream checkout at commit
0882f0373eb2c983142e713c814b91aa82cfcea7 (punkzip v2.3.1). Upstream
doc/ (article mirrors), reference/ (hwzip 2.4 C source) and repo-local
agent/porting notes are deliberately not vendored.
- src/tools/punkzip/PROVENANCE.md: origin, vendored commit, re-vendor
procedure (clean-upstream-only), G-063-style licence records
(public-domain hwzip lineage, MIT musl time.zig, zero dependencies)
- src/tools/AGENTS.md: new DOX child - vendored-tools boundary,
upstream-first editing policy, pinned-toolchain verification; indexed
from src/AGENTS.md
- .fossil-settings/crlf-glob: cover the vendored fixtures (hamlet.txt
is CRLF byte-exact test data the compression tests pin sizes
against; git stores it verbatim - core.autocrlf false)
- verified from the vendored location with the pinned
bin/tools/zig-x86_64-windows-0.16.0 toolchain: zig build test 25/25
steps, 130/130 tests green; build outputs (.zig-cache/, zig-out/)
were already gitignored at any depth
Goal Progress updated: the acceptance's vendoring clause (origin,
state, re-vendor procedure, licensing) is satisfied. Next: the
make.tcl tool build step.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Upstream punkzip 0882f03 fixes the long-standing dir-mtime limitation
via a deferred post-extraction stamping pass (windows direct
CreateFileW BACKUP_SEMANTICS + SetFileTime; Io best-effort elsewhere),
pinned by tests at the stored epoch including the -d route and the
build roundtrip. Notes bullet updated from proven-fixable to fixed.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
File mtimes preserved exactly (no regression old->new exe); atimes and
directory mtimes not preserved - the dir case is the long-standing zig
limitation, still present in 0.16 Io, but proven fixable via direct
CreateFileW(BACKUP_SEMANTICS, WRITE_ATTRIBUTES) + SetFileTime with a
deferred post-extraction pass. Also records the DOS-time-as-UTC vs
UT-extra-field semantics difference vs bsdtar for the parity suite.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Upstream punkzip 572d893 (v2.3.0, 130/130) turns the January build
scaffolding into a working streaming recursive zip writer per user
decision (finish rather than remove - the shipped tool should be useful
from the start). Progress records the design (per-member streaming,
sorted deterministic entries, dir members, zip64 refusal) plus the
second zig-0.16 trap found en route (OpenFileOptions.allow_directory
defaults true, breaking file-vs-dir probes). Remaining-for-acceptance
list now starts at vendoring.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Upstream punkzip 7f88096 (v2.2.0, suite 128/128) delivers both
upstream-side prerequisites from the goal's Approach: the
extract-to-directory argument and the machine-readable listing mode
with its format pinned by CLI tests. Progress records the v1 porcelain
format contract for the parity-suite work; remaining-for-acceptance
list trimmed accordingly. Also noted: user-committed reference/
hwzip 2.4 C source upstream (cb2a731).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Upstream punkzip commits 3b87087 (0.16.0 port, floor moved, Init/Io
threading, build-graph install-order fix, msys overlapped-stdout fix,
v2.1.3, verified 841-member CRC extraction from the file-relative
piperepl kit) and 2e49c75 (three prefixed-zip container tests pinning
base_offset for both offset conventions; suite 125/125). Notes gain the
overlapped-stdout trap record (applies to the G-128 tool as well);
Progress section added with the remaining-for-acceptance list.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Status proposed -> active in the index with the detail-file mirror
updated. Pre-activation baseline is recorded in the detail file Notes;
first work item is the zig 0.16 port of the upstream checkout.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Upstream c:/repo/jn/zig/punkzip commits e9b5b553 (0.15.2 migration
completed, zig build test 122/122 green - bitstream wiring fixed, CLI
suite compiling and pinning current output) and 78453fc0 (localtime
banner restored dependency-free on stderr; zeit stays out per user
decision). Context refreshed; Notes record the decisions, the
stdout/stderr contract, and the known upstream gaps (base_offset
untested zig-side, build subcommand scaffolding, 0.16 argsAlloc).
Non-contract detail-file updates only; status stays proposed -
activation flip awaits user confirmation. goals_lint clean.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
punk::zip could write a zip and split an executable-prefixed one, but nothing in the tree
could READ a zip without zipfs, vfs::zip or tcllib's zipfile::decode - a system package
punkshell does not vendor, with exactly one consumer: make.tcl's zipfs-less kit
extraction. Measured at drafting: msys2's /usr/bin/tclsh8.6 has no tcllib, so a bake
driven from that host could not lift the runtime's own zip (its tcl_library) into the kit
and the artifact did not boot.
punk::zip 0.1.1 -> 0.2.0 adds three commands, stock Tcl only. archive_info reports where
the archive begins inside its host file and which offset convention it uses; members
walks the central directory and returns one dict per entry without decompressing
anything; unzip extracts all or part of it with every member's crc32 verified,
directories materialized, and the writer's 2MB whole-member-vs-streaming threshold
mirrored. All three accept a plain .zip or a zip attached to an executable or script
prefix, so "what is inside this kit / runtime / modpod" is now answerable on a runtime
without zipfs.
The offset derivation is stated once and shared: offsetbase = cdiroffset - diroffset.
Positive means an external preamble with archive-relative offsets, zero means the offsets
are file-relative (or there is no preamble); dataoffset is the split point either way.
Because every surface reads the ORIGINAL file at that base, the file-relative shape stops
being a special case - the broken split-off intermediate is never produced.
tclsh90b4_piperepl.exe is the store's one file-relative artifact: the old
split-then-tcllib path still fails on it with exactly "Bad zip file. Bad closure.", and
it now extracts all 841 members crc-verified in 735ms.
extract_preamble consumes that machinery instead of carrying its own scan, and two
defects went with the refactor: it read 28 bytes of a 30-byte local file header, and it
derived the file-relative base from the FIRST central directory record only (its own
'#todo! loop through all cdr file headers'). The shared walk takes the minimum over ALL
records and then validates that a PK\3\4 really sits there. Its five puts stdout debug
lines are gone.
Refusals are decided for every selected member before any file is opened, so an
unsupported archive cannot leave a half-populated directory: encrypted entries, zip64
size/offset fields, unknown compression methods, crc mismatch (partial file removed) and
member paths that would escape the target are each named. Store and deflate only,
mirroring the writer's own primitives.
make.tcl's zipfs-less extraction now calls punk::zip::unzip on the original runtime and no
longer creates the extracted_<runtime>.zip intermediate. No punkshell code requires
zipfile::decode any more. Two categories of hit remain and are deliberate: tcllib's own
zip/decode.tcl shipped as a LIBRARY inside some .vfs payloads, and one guarded optional
probe inside the vendored third-party ooxml1.10/ooxml.tcl.
First tests punk::zip has ever had (src/tests/modules/punk/zip/), covering the three
archive shapes from one source tree, the introspection output, the named refusals, and
the mkzip -> read round trip - the first coverage that what punkshell WRITES is readable.
Verification, all 2026-07-26. Reference host for the dependency-free claim: msys2
tclsh8.6 8.6.12, probed as zipfs / vfs::zip / zipfile::decode all ABSENT - suite driven
there harness-free, 28 passed, 2 skipped (the zipfs-gated cross-checks only), 0 failed.
30/30 under Tcl 9.0.3, 28 + 2 skipped under native 8.6. Full source-tree suite after the
change: 1142 tests, 1 failure - core/tcl exec-14.3, the documented baseline. Independent
cross-check: 868 members of tclsh905.exe extracted byte-identical against the same
archive mounted with tcl zipfs. Bake demonstration: 'bake -confirm 0 punk905' driven by
the msys2 tclsh8.6 (host=msys-x86_64, target=win32-x86_64) produced
extracted_tclsh905.exe/{tcl_library,lib} with no intermediate .zip, and the deployed kit
boots with tcl_library at //zipfs:/app/tcl_library, 95 encodings, msgcat 1.7.1, http
2.10.2 - and reads itself with the new reader.
Trap worth keeping: zlib stream inflate's 'get' returns only what the stream has already
produced, so one get per put silently truncates a large member to the first few chunks
(measured 327680 of 5048890 bytes on both 8.6.12 and 9.0.3). The inner drain loop is
load-bearing, and the >2MB streaming test exists to catch its removal.
Standing question answered and pinned: zipfs does NOT misidentify punk::zip::mkzip
directories. It keys on the trailing slash (which punk::zip::walk always emits), not the
stored permission bits, exactly as the current cli-999999.0a1.0.tm wording says - the
older, less precise wording survives only in frozen module snapshots under
src/vfs/mkzipfix.vfs and src/vfs/project.vfs. The real difference from zipfs mkzip is
that zipfs writes no directory entries at all.
The achieved flip archives the entry and the detail file, and sweeps the live tier: G-034
(the reader is its extract-rather-than-mount alternative, plus the measured finding that
make.tcl on 8.6 cannot mount its own templates modpod), G-101 (the read half of a
zipfs-less 8.6 container is solved), G-125 (the gate now has a named failure reason to
report), G-126 (the member-dict keys and fixture set its parity suite must reproduce, and
a back-pointer to G-128 whose only recorded bridge was this goal), G-128 (archive_info
answers "is this artifact safe to move the overlay of?" in one call).
One acceptance clause is reported for review rather than claimed silently: "NO
zipfile::decode requirement remains anywhere in the shipped tree" is satisfied for
punkshell's own code, with the vendored-third-party residue named above.
The archived detail file's diff is a rename plus the flip's evidence write-up.
Assisted-by: harness=claude; primary-model=claude-opus-5[1m]; api-location=anthropic.com
Three goals arising from the G-122 work, each drafted with its overlap survey and
approved before writing.
G-124 - dependency-free zip reading in punk::zip. Nothing in the tree can READ a zip
without zipfs, vfs::zip or tcllib's zipfile::decode, which is a system package punkshell
does not vendor and which has exactly one consumer: make.tcl's zipfs-less kit extraction.
The goal removes that requirement outright (not as an optional fallback), adds an
introspection surface so punk::zip is useful to script programmers, and gives the writer
its first round-trip coverage. Measured motivation recorded in the detail file: msys2's
tclsh8.6 has no tcllib and so bakes an unbootable kit, and extract_preamble's live
'#todo - need to adjust offsets' means a FILE-relative prefixed executable
(tclsh90b4_piperepl.exe) splits into a .zip tcllib rejects with "Bad closure" - reading
the original file at its derived base offset removes that failure mode by construction.
G-125 - a kit that cannot boot is not deployed. Today a failed extraction emits a
recapped BUILD-WARNING and then deletes and replaces the deployed kit anyway; field-
observed 2026-07-26 replacing a working punk91.exe with one that dies on init.tcl. The
gate tests the boot precondition on the assembled vfs, so a kit whose payload legitimately
supplies its own tcl library still builds.
G-126 - punkzip as a VENDORED zip accelerator behind G-124's pure-Tcl floor, with zig
kept optional (a tclsh-only clean checkout must still build and bake). Feasibility
measured and recorded: the source builds clean under its declared zig 0.15.2, and under
0.16 fails on one surfaced error (std.process.argsAlloc), with the OS-facing surface
confined to 3 of ~20 files while the ~10k-line compression core is untouched; zig 0.16.0
is already unpacked in bin/tools. Benchmarks captured for the speedup claim.
Non-contract Notes back-pointers added to G-034 (the reader is a candidate for its own
Acceptance's "alternative modpod mount path" branch) and G-101 (an 8.6 container has no
zipfs, so it needs a reader that works without one).
Assisted-by: harness=claude; primary-model=claude-opus-5[1m]; api-location=anthropic.com