feat: zipnative-cli 1.0.0 — the agent-grade ZIP CLI on zipnative 1.0.0 - #6
Merged
Conversation
…entries, accept '-' as a flag value - io.ts: writeFileStream resolves/rejects only after the write stream's 'close' so a refusal on the first pull can no longer leave an empty placeholder behind unlinkQuiet (fd opened asynchronously). - stream.ts: --skip-unsupported now skip()s the entry it could not decode before advancing (the core refused with ZIP_API_MISUSE otherwise). - args.ts: a lone '-' after a flag is a value (stdin/stdout), not a flag. - govern.ts: validate --format json|text (exit 2 otherwise). - index.ts: --stream help no longer claims byte identity with the buffered layout (file inputs use addStream → data-descriptor layout); --strict wording for verify; positional archive shown for inspect/verify.
…t-binary spawn smoke Utilities (frozen 39-code ZIP_* → E_* table, 13 E_* codes), every command, integration round-trips (create → verify → extract, modify remanence vs compact, forward stream read, parallel byte-identity, refusal posture on seven raw-crafted hostile shapes), foreign-provenance interop fixtures with a 20 KB budget, docs consistency and veraZIP vendoring drift checks.
…LI-driven corpus, negative canaries scripts/validate-zip.mjs is a 1:1 port of zipnative's scripts/validate-zip.ts (commit 4f1bc36, independent raw parser, never imports the engine) with the pdfnative outcome vocabulary PASS/FAIL/XFAIL/XPASS/INFRA/SKIP, exit 0/1/2/3, VERAZIP_REQUIRED fail-closed and per-file JSON reports. scripts/generate-zip-corpus.mjs drives dist/cli.cjs to write 30 conformant archives (3 of them hostile-but-conformant, asserted refused by the CLI) and crafts 4 negative canaries with its own raw writer. 30 PASS + 4 XFAIL.
…ows, publish with Trusted Publishing + SBOM, CodeQL, Scorecard), governance and templates
…E.md, llms.txt, changelog, roadmap, security, contributing, release notes v1.0.0 + draft PR
…path filter (corpus is self-contained)
…ds projects --summary output - list/stream: the -l short alias swallowed the following positional as its value; --long is the only spelling now (USAGE updated). - crc32: a failed --expect no longer emits a success status envelope before the error envelope; the success envelope now carries expect/matched. - projection: --fields applies to the --summary document instead of being ignored when both are given.
… dependency-free run-all.js (73 jobs)
…m verdict; create reports layout - inspect: determinism.deterministic = epoch timestamps + canonical order + UTF-8 flags (reproducibility); new canonicalLayout mirrors noDataDescriptors. A streamed archive (data descriptors) is reproducible run-to-run, so it no longer fails --check deterministic; --check canonical-layout is the alias of no-data-descriptor. Text verdict reads 'reproducible, layout canonical'. - create: status envelope carries layout: buffered | data-descriptor. - schema inspect / inspect-summary / status updated; help and docs aligned.
…es (audit A-01, A-03, A-08, A-29, A-37) - utils/flags.ts lists every boolean flag (global + per command); parseArgs never consumes the next token for them, so `zipnative --json list …` dispatches and `list --long a.zip` keeps its positional. A dash-digit token is a value; combined short flags (-lq) are refused with E_USAGE; --flag=false negates a boolean for hasFlag/getBoolFlag. - io: a terminal with nothing piped is refused (E_USAGE) instead of blocking; an explicit '-' still reads stdin. Process-wide EPIPE guard on stdout/stderr: a closed downstream pipe ends the run quietly with exit 0. - index: unknown command and flags-without-command are usage errors (exit 2, E_USAGE envelope); --format, -f documented on every command.
…mp warnings (audit A-02, B-31) The engine encodes a Date through its local getters, so a zoned --date produced different DOS fields (and a different --deterministic hash) per host time zone. Every explicit date (--date, manifest date on create and modify) is now normalised to its UTC components before it reaches the engine; a string without a zone designator is read as UTC. Years outside 1980-2107, odd seconds and a --chunk-size outside 1 KiB..16 MiB emit a warning instead of being clamped silently.
…niform --overwrite policy
Audit findings A-06, A-07, A-09, A-14, A-16, A-22, A-26 (arbitrated).
- utils/sink.ts: the one extraction sink shared by `extract` and `stream` —
lexical containment (safeJoin), duplicate policy, physical containment
(the nearest existing ancestor and the created directory are realpath'd
under the root's realpath, so a symlink/junction planted inside the
destination cannot redirect mkdir or the write), exclusive `wx` opens
without --overwrite (no check-then-write window), partial files removed.
- utils/io.ts: `WriteOptions { exclusive }` on writeOutput /
writeStreamingOutput / writeFileStream with the uniform E_IO refusal
"Refusing to overwrite existing file <path> (pass --overwrite)"; the
source is no longer pulled once the open has failed.
- --overwrite on create, modify, cat, inflate and batch (directory mode):
an existing output is refused (E_IO) and left intact; extract/stream
unchanged. `modify --in-place` uses an unpredictable, exclusively created
temp name.
- --max-input-size <size> (global, default 4 GiB, "none" disables with one
warning): bounds every buffered read — stdin (byte-counted, read aborted)
and files (size checked before reading) for list/inspect/verify/extract/
cat/modify, create --stdin-name and inflate --sync — with E_LIMIT and
detail { limit: "maxInputSize", configured, observed }.
- argv-typed paths are no longer refused for containing "..": they are the
user's own filesystem authority. validatePath() stays for manifest values
(batch tasks, create/modify entry paths, the extraction sink).
- corpus generator removes each target before regenerating it.
- tests: utils/sink, utils/io (exclusive, bounds), commands/overwrite-policy,
junction-inside-destination for extract and stream, modify temp name,
--max-input-size parsing and readArchiveBytes bound.
…; --parallel refuses writer-shaping modules Audit findings B-03, B-07 (+B-41), B-02, B-33 (arbitrated). - modify opens eagerly (overlap / CD-LFH structure checked before any edit) and cross-checks every untouched entry with reader.verifyEntry() before the save — CRC-32, sizes and local header vs central directory, one decompress pass, never a recompress. A lying record is refused with E_DATA (ZIP_CRC_MISMATCH / ZIP_SIZE_MISMATCH) or E_SECURITY (ZIP_CD_LFH_MISMATCH) naming the entry instead of being laundered into a canonical-looking archive; runs under --dry-run too; no opt-out. Encrypted entries and stream-only-codec entries are copied as-is and counted. The envelope gains verified, verifySkipped and tier (schema status updated). - The "read-side only" codec claim was false: the engine resolves methods 0/8 through the registry, so a --codec module registering method 8 replaces the writer's compressor (even under --deterministic), and a deflateImpl replaces the sync tier unless --deterministic. Every doc site (README, SECURITY, KB, AGENTS, CHANGELOG, release notes, copilot instructions, --help) now says so; LoadedCodecModule.overridesBuiltin lists the writer-resolved methods a module registers. - create --parallel refuses (exit 2) a module registering method 0/8, and a deflateImpl without --deterministic: the worker pool never sees the module and the envelope would lie. Sequential writes announce the override with a warning line (silent under --dry-run). - create: static `progress` import (two dynamic imports removed). - tests: hostile archives from the raw builder (CRC lie, LFH/CD mismatch, overlap, encrypted survivor), create-codec-write suite (isolated file: codec registration is process-global), overridesBuiltin.
…, raw-name hex, verify --entry, extract --skip-unsupported, cat sync-codec fallback, inflate bytesConsumed
Audit findings B-04, B-01, B-05, B-06, B-14, B-15, B-16, B-17, B-18, B-43 (arbitrated).
- create --order insertion now means the argv order (each directory still
walks name-sorted): walkPaths(preserveInputOrder) skips the global sort,
so an EPUB `mimetype` listed first is written first. Canonical (default)
is untouched.
- Manifests: entries[].extraFields [{ id, hex | base64 }] (create; modify
add/replace/add-dir), modify edits[].mode, and commentBase64 (both, exclusive
with comment) reach AddEntryOptions.extraFields / externalAttributes /
setComment(Uint8Array). parseMode / externalAttributesFor / parseExtraFields
live in utils/zipops.ts, shared by both commands.
- --comment-file <path> on create and modify (raw bytes, "-" = stdin,
exclusive with --comment, 65535-byte cap): the binary-comment path.
- Forensic hex: entries[].rawNameHex and commentHex under --long
(list / inspect --entries / stream --long), archive.commentHex on list
and inspect whenever the archive has a comment.
- verify --entry <name> (repeatable): eager open, then verifyEntry() per
name; report carries `selected`, encrypted / stream-only-codec entries are
skipped like verifyZip, unknown names are E_NOT_FOUND (ZIP_ENTRY_NOT_FOUND)
before any output, a structural refusal lands in report.error.
- extract --skip-unsupported: encrypted entries and methods with no
registered codec are skipped (reason "unsupported") instead of aborting.
- cat falls back to readEntry() for a --codec method that decompresses
synchronously only (was ZIP_UNSUPPORTED_CODEC_MODE).
- inflate envelope gains bytesConsumed (the inflator's exact figure on the
streaming path).
- schema: entry rows, create/modify manifests, entries/inspect archive,
verify (+summary), status updated; completions and --help updated.
- corpus: comment-binary.zip, order-insertion-epub.zip,
extra-fields-manifest.zip (37 archives: 33 conformant + 4 canaries).
- tests: engine-coverage suite (order, extra fields, comments, hex, verify
--entry, skip-unsupported, sync-only codec, bytesConsumed, walk order).
… dry-run, colours on stderr, data error classes, envelope nits
Audit findings A-05, A-12, A-13, A-21, A-30, B-19 (+A-46), B-29, B-36, B-37, B-40 (arbitrated).
- batch --manifest under --json / --format json: stdout is ONE batch
document. Every task runs under captureStdout() (utils/io.ts, 64 MiB cap
→ E_LIMIT captureBytes) and its output lands in tasks[i].report (parsed
JSON, or an array for NDJSON), .stdout (text) and .stdoutBytes. Tasks that
would write their artefact to stdout — create/modify/cat/inflate without
"output", stream --cat — are refused at validation (E_USAGE, also under
--dry-run). Text mode keeps the interleaved contract.
- create/extract --dry-run print no text plan when agent mode comes from
the environment (ZIPNATIVE_JSON), not only from the flag.
- colours: style() decides on stderr (where the progress lines go);
FORCE_COLOR turns colour on, TERM=dumb off, NO_COLOR always wins.
- Unsafe ENTRY NAMES are data, not usage: modify --add/--rename/--add-dir
and create --stdin-name refuse them with E_INPUT (exit 1) carrying
entryName; modify --add "dir/=payload" (flags and manifest) is E_INPUT
pointing at --add-dir.
- doctor: the limits check carries data { ...ZipLimits, maxInputSize }
with "none" for a disabled bound, and counts --max-input-size overrides.
- stream --summary gains descriptorEntries and bytesKnown (data-descriptor
local headers carry zero sizes); a forward-read failure before the first
header no longer carries an empty entryName.
- unixMode is four octal digits ("0000", "0644", "4755").
- Every CLI-side E_NOT_FOUND (cat, inspect, stream --cat, verify --entry)
carries zipCode ZIP_ENTRY_NOT_FOUND and names the remedy.
- schema: batch tasks[].report/stdout/stdoutBytes, doctor checks[].data,
stream-summary, entries unixMode pattern; --help updated.
- tests: agent-contract suite, captureStdout, colours (stderr/FORCE_COLOR/
TERM=dumb); error-class expectations updated.
…exports, path completions, concurrency cap Audit findings A-10, A-23, A-31, A-38, A-40, A-43, B-32 (arbitrated). - governance: AI_GOVERNANCE_POLICY now deep-equals .github/ai-governance.json (spec_updated, compliance_report.description, capability_manifest, verification.advisory_in_ci, references) and tests/utils/governance-sync pins the policy and every AGENT_RULES rule line to the .github files. - SIGINT / SIGTERM: utils/inflight.ts tracks the files being written (writeOutput opens first and registers only a file it created; writeFileStream registers on 'open'); the handler removes exactly those paths and exits 130 / 143. Smoke test on POSIX (spawn + SIGINT). - remedies: every refusal in cat / extract / stream / batch / create / modify names the next action (--skip-unsafe, --add-dir, --dry-run, the directory-mode rules, `zipnative list` …). - dead exports removed with their tests: die, ensureDir, parentDir, entryBasename, guardAsync, LIMIT_DEFAULTS (deprecate and getBoolFlag stay). - completions: PATH_FLAGS complete files after path flags (bash _filedir / compgen -f, zsh _files, fish -r -F); every other value flag is fish -r. - batch --concurrency goes through parsePositiveInt with a 64 cap; create --chunk-size is accepted with --stdin-name (the chunked writer); the last dynamic import in create.ts is static.
…every PR, attested SBOM, CJS-only package, lint tests, coverage ratchet Audit findings A-04, A-27, A-33, A-41, A-42, A-44 (arbitrated). - ci.yml: paths-ignore reduced to LICENSE/.editorconfig/.gitignore/FUNDING/ issue templates (tests/docs/consistency.test.ts pins the docs, so docs changes must run the suite); Windows matrix 22 + 24; new macOS job (the second case-insensitive filesystem the sink handles); dist checks expect the CJS bin only. - verazip.yml: no `paths:` filter — a required check must run on every PR. - publish.yml: top-level permissions read-only (the job requests id-token / contents / attestations); the release tag reaches the shell through the environment; the SBOM is attested (actions/attest-build-provenance, SHA-pinned); the packed tarball is verified (bin, AGENTS.md, llms.txt, errors.json; no maps, no tests) before publish. - package: CJS bin only (tsup format cjs, no dts, no sourcemap; `module`, `types`, `sideEffects` removed), `files` adds AGENTS.md and docs/data/errors.json and excludes maps, repository URL git+https. Tarball: 7 files, 102 kB packed. - lint covers tests/ (relaxed test-ergonomics override; three findings fixed); coverage thresholds 93 / 88 / 94 / 93 from the measured 96.3 / 92.3 / 97.9 / 96.8; tsconfig "dom" lib annotated.
…edge base, AGENTS, llms.txt, changelog, roadmap, security, contributing, release notes, drafts) Audit findings A-15 (+B-22), A-17/A-18, A-19, A-20, A-24, A-28, A-32, A-34, A-35, A-39, A-45, B-20, B-23…B-28, B-30, B-34, B-35, B-38, B-45. - README / llms.txt / samples README / CLAUDE.md / copilot + instruction files: every command table regenerated from --help; global options gain --max-input-size and the colour rules; new process contract, exit codes, environment and memory tables; the false "every file-path argument is validated against ../" claim replaced by the manifest-values-only posture; sink, overwrite policy, modify verification, codec truth, insertion order, UTC dates, --stream layout vs canonicalLayout, batch --json single document, CJS-only package, CI matrix, measured numbers. - Knowledge base / AGENTS.md / docs/data: per-command reference and envelope catalogue rebuilt from the built binary; §8 mapping re-graded with a Reach column (capability vs type); diagnostics carry a raisedBy column (errors.json + KB + AGENTS); core-exports.json rows gain reach/via. - CHANGELOG [1.0.0]: one line per accepted audit id; stale lines corrected. ROADMAP: released items moved, the six deferred sentences and the A-39 / A-44 notes added verbatim, AES item links the engine non-goals. SECURITY: sink, overwrite policy, input validation scope, modify verification, codec statement, signals, supply chain. CONTRIBUTING: CI matrix, branch-protection checks, pinned-docs relations, lint scope, thresholds. CITATION date. SUPPORT aligned with SECURITY. - Release notes (security first, compatibility notes) and the draft PR body (batches B1–B8, gate commands, human follow-ups). - New: .github/ISSUE_TEMPLATE/config.yml; two upstream issue drafts under .github/drafts/ (engine DOS time from local getters; forward stream should refuse custom-method entries) — both pass govern verify-issue. - --help: every line within 80 columns; global usage documents exit codes (130/143) and every ZIPNATIVE_* variable; batch usage lists the forwarded create flags. Bridge re-exports the worker ByteSource / StreamOptions aliases (B-45). tests/docs/consistency.test.ts pins USAGE ↔ COMMANDS ↔ flag table, 80-column help, status enum ↔ emitStatus callers, CITATION version, raisedBy, the absence of "read-side only", llms.txt tarball paths, README/KB flag coverage.
…6 majors, 11 minors, 4 nits) with four contract fixes Doc audit D (23 findings) verified by E: 21 confirmed, 1 adjusted, 1 rejected, 1 new. Code (the documentation stated the intended contract): - govern verify-issue is bounded by --max-input-size like every other buffered read (D-03). - utils/ziperr.ts maps the unwrapped node:zlib errors of the sync tier (Z_DATA_ERROR / Z_NEED_DICT → ZIP_DEFLATE_CORRUPT, Z_BUF_ERROR → ZIP_DEFLATE_TRUNCATED, both E_PARSE): `inflate --sync` on node-zlib no longer reports a corrupt or truncated stream as E_RUNTIME without a zipCode — the class never depends on the codec tier (D-04). - stream: under --json, --summary / --fields select the json report instead of being silently ignored by the NDJSON default (D-05). - verify --entry: the E_NOT_FOUND message names the remedy (D-12). Documentation: the EPUB example now runs as written (inputs are looked up where they are, --base only rebases names); the Docker sample creates /out; stream --long carries rawNameHex only (no commentHex outside the central directory); inspect --summary example gains canonicalLayout; `schema` = 22 subjects (20 schemas + errors + manifest); unixMode null depends on the producer, not the host; --fields array-leaf null rule; crc32 envelope expect?/matched?; stream list-mode envelope fields; inspect text verdict "(streamed)" suffix; E_NOT_FOUND remedies per command; Discussions pointer consistent with SUPPORT.md; ROADMAP deferred items carry their audit ids; verazip.yml header 37 archives; bug template Node 22/24 only; create --help envelope lists dryRun and layout; refreshed counts (57 files / 1184 tests, coverage 96.27 / 92.36 / 97.91 / 96.81, tarball 116.7 kB). tests: doc-audit-fixes suite (govern bound, zlib mapping, inflate --sync classes, stream --json --summary, verify --entry remedy).
… one formatDiagnosticLine() for every stderr diagnostic Review finding Q2-F1: list --format ndjson and stream --list printed the engine diagnostics on stderr through process.stderr.write, so --quiet did not apply to them although --help documents it as suppressing every text diagnostic. They now go through progress(); the line format is shared by the sink and the listings (utils/diagnostics.ts formatDiagnosticLine).
…nvelope (and a remedy: line in text mode) Review finding Q2-F2: engine refusals carry the engine message, which names library options (rejectTraversal: false, onDuplicate: 'first') that do not exist on the command line, so an agent had to branch on zipCode through a table of its own. - error.remedy (optional, top level next to detail): the CLI flag(s) or command that lift the refusal. One table, ZIP_REMEDY in utils/agent.ts (type-only import of the code union: the start-up path never loads the engine), plus an explicit CliError option for CLI-side refusals without a zipCode (--overwrite, --max-input-size, batch task output); a ZipLimitError names its exact --max-* flag (LIMIT_FLAGS). Absent when nothing lifts it (structural refusals, corrupt data, usage errors). - schema error (additionalProperties: false) and schema errors / docs/data/errors.json (cli.remedy) carry it; batch task errors too. Text mode prints "remedy: …" after the message, never suppressed. - The bridge rule is pinned: no src/ module specifier references the zipnative package outside core-bridge, except version.ts's zipnative/package.json metadata probe (kept out of the bridge so --version never loads the engine). - tests: utils/remedy, envelope/schema/consistency pins updated. - docs: AGENTS §2/§3/§7, llms.txt, README, knowledge base, ROADMAP (--explain fed by the same table), CHANGELOG [Unreleased].
…itizeEntryPath() Review finding Q2-F3: `verify` proves integrity and structure (CRC, sizes, local headers, overlap) and says ok on a zip-slip archive, and the --check vocabulary had no path-safety assertion, so "verify + inspect --check" looked like a safety gate it was not. - inspect: stats.unsafeNames (names the engine's sanitizeEntryPath() refuses — traversal, absolute, drive/UNC, NUL, ADS, device names; directory names checked without their slash) and the `safe-names` check; the text report prints the count on the names line. - verify --help, README, knowledge base: verify is not path safety — gate with `inspect --check safe-names,no-symlinks` or `extract --dry-run`. - schema inspect, AGENTS §7 gate, README/KB check lists (20 assertions), samples inspect/02-check-gates, CHANGELOG. - tests: inspect-safe-names (hostile raw archive → 2 unsafe, check fails after the report; clean archive passes; verify still ok on the same file).
…eprecation policy, locale stance, secret scanning Review findings Q2-F4 and Q4 P1-5 / P0-3 / P1-7: AGENTS.md, llms.txt, README and the knowledge base carry the two rules an unattended caller must encode (--no-config / --config; the envelope is the last stderr line starting with a brace); CONTRIBUTING gains the versioning, stability and deprecation policy (public surface, semver rules, the deprecate() ladder) with a README summary, names secret scanning and push protection next to the branch-protection ruleset, and states the npm requirements and why there is no packageManager field; README states the locale stance.
…le build, moderate audit, commit-message gate, start-up guard Review findings Q4 P0-2, P1-1, P1-2, P1-8, P2-5, P2-6. - @cyclonedx/cyclonedx-npm 6.0.1 is an exact-pinned devDependency installed from the lockfile; publish.yml runs `npx cyclonedx-npm --omit dev` instead of an unpinned `npx --yes …@^1` fetched inside the job that holds the OIDC token (that range, 1.8.0, also declared engines.npm 6 - 9 and could not have run on the npm >= 11 the job uses). Dependabot ignores its majors. - publish.yml packs the tarball, prints its SHA-256 to the job summary, verifies its contents, attests it with actions/attest-build-provenance (next to the SBOM), attaches tarball + checksum to the release and publishes THAT packed file so the attested bytes are the published bytes. - ci.yml: every job rebuilds and compares the SHA-256 of dist/cli.cjs (verified byte-stable locally), audits at --audit-level=moderate (clean), runs the new start-up guard, and a dependency-free commitlint job checks Conventional Commits on pull requests (commit subjects + PR title). - tests/integration/startup-budget.test.ts: the bundle reaches the worker only through the lazy import() and its hoisted externals are Node built-ins plus the engine (the ≈10 ms top-level engine require stays tracked as ROADMAP A-11); start-up overhead over bare Node <= 250 ms (min of 3, relative budget). - docs: SECURITY supply-chain paragraph, README, CONTRIBUTING CI table, commit-convention pattern, CHANGELOG.
… noUncheckedIndexedAccess Review finding Q4-P1-3 and audit A-44 (ROADMAP → Released). - eslint.config.js: tseslint strictTypeChecked over src/ (type-aware: no-floating-promises, no-misused-promises, no-unnecessary-condition, …) with three justified relaxations (restrict-template-expressions allows numbers/booleans, no-confusing-void-expression ignores arrow shorthands, require-await off for the `async (args) => Promise<void>` command contract); tests extend disableTypeChecked and keep the strict set. - tsconfig.json: noUncheckedIndexedAccess (one test fixture adjusted). - 17 findings fixed without behaviour change: unbound-method thunks, unnecessary assertions/conditions, JSON.stringify for manifest versions, Error causes on stream failures, a shared streamIsTty() (`isTTY` is typed boolean but undefined on a pipe), two documented eslint-disable lines (the inflator's mutated `finished`, the future-engine-code fallback in ZIP_TO_CLI).
…s, verifyEntry skipped reason Review finding Q3 (layering exploration): the two compensations the CLI carries for engine inconsistencies are now filed as human-submittable drafts under .github/drafts/, in the format of the existing two, each with an engine-only reproduction, the 1.0.0 source pointers, the compensation to delete once fixed, the related API asks (analyzeDeterminism, write-side unixMode), a non-goals check and the compliance report. All four drafts pass `govern verify-issue`. ROADMAP and CHANGELOG reference them.
… 1201 tests, coverage 96.32 / 92.52 / 97.93 / 96.91, tarball 120.0 kB)
…ATE, untrack the four engine drafts The engine repository ignores agent issue drafts (only README/TEMPLATE are tracked) and pdfnative-cli versions none: a draft is an intermediate state addressed to a human, not a deliverable, and it goes stale the moment the engine fixes the issue. This repository had committed four of them. - .gitignore: `.github/drafts/*` with README.md and TEMPLATE.md exceptions - .github/drafts/README.md: states the rule, the upstream target and the template workflow; TEMPLATE.md carries the sections the compliance report and `govern verify-issue` expect (the template itself passes the verifier) - the four upstream-*.md drafts are removed from the index (they stay on the release machine until a human files them in Nizoka/zipnative) - ROADMAP, CHANGELOG, PR draft and AGENT_RULES no longer point at ignored files; they describe the four engine asks instead - governance-sync test pins the ignore rule, the README and the template headings to the policy's draft_location
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
…e length check advisory The job scanned BASE..HEAD on refs/pull/N/merge, so GitHub's synthetic "Merge X into Y" commit failed the pattern on every PR. The range now ends at pull_request.head.sha with --no-merges. A subject over 100 characters (commitlint's default, not a Conventional Commits rule) is a warning; the format stays blocking, for the PR title too since a squash merge makes it the commit subject. The PR draft carries a conforming title.
The default pull_request types (opened, synchronize, reopened) do not include `edited`, so a title fix never re-ran commitlint, and a manual "Re-run" replays the original payload with the old title. All jobs run on the event: a skipped job would count as a passing required check.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat: zipnative-cli 1.0.0 — the agent-grade ZIP CLI on zipnative 1.0.0
Summary
(
create,modify), Read & extract (list,inspect,cat,extract,stream),Integrity & codecs (
verify,crc32,inflate), Automation & meta (batch,doctor,schema,completion,govern).src/core-bridge/index.tsand is wrapped bymapZipError/guard, the only place thatreads the engine's
err.code.--jsonenvelopes carryingcode(13 stableE_*classes) andzipCode(the 39 frozenZIP_*causes, verbatim) plusentryName/detail;--dry-runon seven commands;--strict;--summary/--fields/ compact JSON; theeight
--max-*bounds plus--max-input-size; 22 schema subjects and a capabilitymanifest;
doctor;llms.txt,AGENTS.mdanddocs/data/errors.jsonin the tarball.(one sink:
safeJoincontainment, realpath re-check against planted links, exclusiveopens, uniform overwrite refusal, case-fold check, partial-file removal, signal cleanup,
symlinks never materialised, skip-not-write opt-outs) over the engine's refusals.
streamover unseekable input with the trust caveat explicit;modifyappend-only vs--compactwith the data-remanence and 7-Zip caveats surfaced and every re-emitted entryverified;
batch --manifestpipelines with a codec-load policy and one stdout documentunder
--json.(commit
4f1bc36), independent by construction, over a 37-archive corpus written by thebuilt CLI (33 PASS + 4 XFAIL) — blocking in CI on Linux and Windows on every PR and
pre-publish.
Publishing with provenance, an attested SBOM and a verified bin-only tarball, the
AI-governance / HITL files mirrored by
governand pinned by a test, agent guidance filesfor the coding assistants the ecosystem supports.
accepted findings, implemented in eight batches on this branch (below); six deferred to the
roadmap, one rejected.
Changes
package.json
namezipnative-cli,version1.0.0,dependencies.zipnative^1.0.0,engines.node>=22,bin.zipnative→dist/cli.cjs,files=dist(no maps),LICENSE,README.md,AGENTS.md,llms.txt,docs/data/errors.json;publishConfig.provenance; nomodule/types/sideEffects(bin-only);repository.urlgit+https://….build(tsup →dist/cli.cjsonly),test,test:coverage,lint(src/andtests/),typecheck:all,corpus:zip,validate:zip(= build + corpus + validator).src/core-bridge/index.ts
src/index.ts(the 77-export ledgermapped in
docs/KNOWLEDGE_BASE.md§8);ensureCodecsReady()(memoisedinitNodeZipCodecs) andloadParallelZip()(lazyzipnative/workerimport with theworker script resolved through the exports map).
Commands (
src/commands/)create.ts— plan (walk / manifest / stdin) →createZip|createParallelZip→toBytes()|stream(); every name pre-checked withsanitizeEntryPath;--order insertion= argv order;--comment-file; manifestextraFields/commentBase64;--parallelrefuses writer-shaping codec modules; envelopelayout/tier.modify.ts— eager open, fixed-order edits throughcreateZipModifier,verifyEntry()onevery re-emitted entry,
save()|saveCompact();--in-placevia an exclusive temp file--comment-file; manifestmode/extraFields/commentBase64.list.ts,inspect.ts(eager open, stats, determinism verdict split intodeterministic/
canonicalLayout, 19--checkassertions,commentHex),cat.ts(readEntryStream/readEntryRaw, sync-only codec fallback),extract.ts(two-phase sink,--skip-unsupported),stream.ts(iterateZipEntries; attribute flags refused;trust: "local-headers-only";summary
descriptorEntries/bytesKnown).verify.ts(verifyZip+ counters,--entryviaverifyEntry,E_VERIFY_FAILEDwithzipCode),crc32.ts,inflate.ts(createInflatorwith a mandatory bound;--sync/--method;bytesConsumed).batch.ts(directory create / verify with a 1–64 pool;--manifestpipelines; one stdoutdocument under
--jsonviacaptureStdout),doctor.ts(limits as numbers),schema.ts(22 subjects, including
errorsandmanifest),completion.ts(theCOMMANDStable,PATH_FLAGS, four shells),govern.ts.Utilities (
src/utils/)ziperr.ts—ZIP_TO_CLI(39 codes → class + exit,satisfies Record<ZipErrorCode, …>),ZIP_DIAGNOSTIC_CODES(11),mapZipError/guard,isFsError.error.ts(13E_*codes,CliErrorwithzipCode/entryName/detail),agent.ts(envelopes,emitStatus,progress),diagnostics.ts(the sink),limits.ts(eight CWE-tagged flags +--max-input-size),engine.ts(prepareEngine),codecs.ts(--codecloader,overridesBuiltin),io.ts(validatePathfor manifestvalues,
safeJoin, bounded reads, exclusive writes,captureStdout, 50 MB cap, streams),sink.ts(the one extraction sink),inflight.ts(signal cleanup),flags.ts(theboolean-flag table),
zipops.ts(UTC dates, modes, extra fields),entryfmt.ts(
rawNameHex,commentHex, 4-digitunixMode),walk.ts(preserveInputOrder),glob.ts,sizes.ts,manifest.ts,projection.ts,config.ts(codecrefused),version.ts,governance.ts,colors.ts(stderr,FORCE_COLOR,TERM=dumb),args.ts.Wiring (single source of truth respected)
src/index.ts— USAGE for 15 commands + the global block (exit codes, environment),COMMAND_USAGE,loadCommand(), global flags →ZIPNATIVE_*env, config merge, the EPIPEguard, the signal handler, the agent error envelope.
src/commands/completion.ts—COMMANDS(withgroup),GLOBAL_FLAGS,PATH_FLAGS,DRY_RUN_COMMANDS;src/utils/flags.tsBOOLEAN_FLAGS;src/utils/projection.tsPROJECTED_COMMANDS;src/utils/manifest.tsMANIFEST_COMMANDS;src/utils/config.tsKNOWN_COMMANDS.scripts/ & workflows (veraZIP, CI)
scripts/generate-zip-corpus.mjs— drives the built CLI (plus the raw builder forcanaries) to write 37 archives +
manifest.jsontotest-output/zip/: 33 conformant(every writer path incl. binary comments,
--order insertion, manifestextraFields;3 hostile-but-conformant with
refusedBy) + 4 raw-crafted negatives (WF/ENTRY-OVERLAP,WF/CD-COUNT,WF/LFH-SIZE-MISMATCH,WF/LFH-NAME-MISMATCH).scripts/validate-zip.mjs— vendored fromzipnative/scripts/validate-zip.ts(commit4f1bc36, hashes recorded in the header); raw parser, never imports the engine; level 0always, level 1 foreign tools SKIP when absent,
VERAZIP_REQUIRED=1fail-closed; exit0/1/2/3; coverage canary over
REQUIRED_NEGATIVE_CHECKS.scripts/helpers/interop-tools.mjs— bsdtar / unzip / 7z / python-zipfile / jar integritycheckers with per-tool exit contracts.
.github/workflows/:ci.yml(Ubuntu 22/24, Windows 22/24, macOS 22; docs changes runthe suite),
verazip.yml(blocking, Linux + Windows, no path filter),publish.yml(thefull gate, veraZIP, attested SBOM, tarball verification, Trusted Publishing),
codeql.yml,scorecard.yml;dependabot.yml;ai-governance.json,AGENT_RULES.md,drafts/README.md+ two upstream drafts,ISSUE_TEMPLATE/config.yml,copilot-instructions.md.Samples
samples/<command>/.sh+.ps1pairs (41 demos),samples/agent/(the recommendedloop),
samples/run-all.js(73 jobs),samples/README.md. All offline.Docs
table, Conformance status, Quick Start, Command Reference from the USAGE strings, Global
options with the limit defaults, environment, memory and exit codes, Driving from AI
agents, Security),
docs/KNOWLEDGE_BASE.md(12 sections incl. the 39-code mapping and the77-export map),
AGENTS.md,llms.txt,CHANGELOG.md(every audit finding tagged),ROADMAP.md,SECURITY.md,CONTRIBUTING.md(veraZIP, CI matrix, branch protection,pinned docs),
SUPPORT.md,CODE_OF_CONDUCT.md,CITATION.cff,release-notes/TEMPLATE.md,release-notes/v1.0.0.md,docs/data/core-exports.json,docs/data/errors.json.Tests
tests/helpers/capture.ts), theengine-independent
tests/helpers/raw-zip-builder.tsfor adversarial shapes, twoforeign-provenance interop fixtures (
tests/fixtures/README.md+ policy test), one spawnsmoke test against
dist/cli.cjs(incl. EPIPE and SIGINT), the veraZIP vendor test,tests/utils/governance-sync.test.ts, andtests/docs/consistency.test.ts(commandcounts,
E_*codes, theZIP_*mapping, the 77-export map, the limits table, the schemasubjects, USAGE ↔
COMMANDS↔ README / KB tables, status enum ↔emitStatuscallers,CITATION version, environment variables, tarball paths).
Coverage statements 96.32 / branches 92.52 / functions 97.93 / lines 96.91 against the
enforced thresholds 93 / 88 / 94 / 93.
The audit pass, batch by batch
Two independent audits (A: CLI, UX, agent contract, supply chain; B: engine coverage) were
arbitrated into 81 canonical findings (four merged pairs): 74 accepted, 6 deferred
(A-11 lazy engine require, A-25 fuzz tests, A-36 one governance schema, B-21 manifest
externalAttributes, B-39list --extra, B-42 inflate-tier visibility — all on the roadmap),1 rejected (B-44). Each batch is one commit on this branch.
456a854; A-01, A-03, A-08, A-29, A-37): theboolean-flag table (
utils/flags.ts) so a boolean never consumes the next token and globalflags work before the command name; EPIPE → exit 0; TTY guard on implicit stdin; unknown
command → exit 2 /
E_USAGE;--format, -feverywhere; ≤ 80-column USAGE;-lqrefused.ecda18a; A-02, B-31): ISO dates are UTC wall-clock and time-zoneindependent; clamp warnings (1980–2107, odd seconds,
--chunk-sizerange).efb4c1e; A-06, A-07, A-09, A-14, A-16, A-22,A-26): one sink module with realpath containment and exclusive opens;
--max-input-size;uniform
--overwritepolicy on every writer; argv..accepted (manifest values stillchecked); threat-model rows.
modifyverification and codec truth (9640bdc; B-02, B-03, B-07 + B-41,B-33): eager open +
verifyEntry()on every re-emitted entry, no opt-out; honest codecclaims (a method 0/8 module shapes the writer);
create --parallelrefuses writer-shapingmodules;
modifyenvelopetier.31dc1c3; B-01, B-04, B-05, B-06, B-14, B-15, B-16, B-17, B-18,B-43):
--order insertion= argv order; manifestextraFields,mode,commentBase64;--comment-file;rawNameHex/commentHex;verify --entry;extract --skip-unsupported;catsync-codec fallback;inflatebytesConsumed; three corpusentries (37 archives).
5c2e9fc; A-05, A-12, A-13, A-21, A-30, B-19 + A-46, B-29, B-36,B-37, B-40): one stdout document for
batch --json; env-driven dry-run; colours onstderr; unsafe names are
E_INPUT;doctorlimits as numbers;stream --summarymarkers; no empty
entryName; 4-digitunixMode;zipCodeon everyE_NOT_FOUND.6f1f05b; A-10, A-23, A-31, A-38, A-40, A-43, B-32): governance synctest; remedies in every refusal; dead exports removed; SIGINT / SIGTERM cleanup; path
completions;
--concurrencycap;--chunk-sizewith--stdin-name.857261c; A-04, A-27, A-33, A-41, A-42, A-44): docs changes runCI; macOS job and Windows 22/24; coverage ratchet; attested SBOM and tarball verification;
CJS-only bin package; lint covers tests.
ce41623):inspectseparates reproducibility from layout(
canonicalLayout,--check canonical-layout);createreportslayout.B-20, B-23–B-28, B-30, B-34, B-35, B-38, B-45): README / KB / AGENTS / llms.txt aligned,
consistency-test relations extended, CONTRIBUTING branch protection, release notes with
Security first, roadmap sentences for every deferred item,
ISSUE_TEMPLATE/config.yml, thetwo upstream drafts.
Test plan
Every command below was run on this branch at HEAD and must be green again on the PR:
npm run typecheck:all— cleannpm run lint— 0 errors (src/andtests/)npm run test:coverage— 1193 passed + 9 skipped across 61 files; statements 96.32 /branches 92.52 / functions 97.93 / lines 96.91 ≥ thresholds 93 / 88 / 94 / 93
npm run build—dist/cli.cjsonly (nodist/cli.js, no.d.ts, no maps)npx vitest run tests/integration/built-binary-smoke.test.ts— post-build spawn suite(
--help,--version --json,schema manifest= 15 commands, EPIPE exit 0, SIGINT exit 130on POSIX)
npm run validate:zip— 33 PASS + 4 XFAIL, 0 FAIL, exit 0 (level 1 with the toolspresent;
VERAZIP_REQUIRED=1in CI)node samples/run-all.js— 73/73npm pack --dry-run— 7 files, 120.0 kB packednpm audit --audit-level=high— 0 vulnerabilitiescreate --deterministic→inspect --check deterministic→verify --strict→extract→crc32cross-check;create --parallelresolves the workerscript from the bundle;
doctorreportsdeflate-tier: node-zlib;create --streamisdeterministic: true,canonicalLayout: falsenet/http/fetch/dnsimport anywhere insrc/Human follow-ups (not performed by any agent)
v1.0.0, create the GitHub Release (the publishworkflow runs on the published release).
zipnative-cli(repositoryNizoka/zipnative-cli,workflow
publish.yml) — the 0.0.1 placeholder was token-published, so the OIDC trustrelationship does not exist yet.
ci (22),ci (24),windows (22),windows (24),macos,verazip-linux,verazip-windows).ISSUE_TEMPLATE/config.ymlpoint at the engine's board until then); decide on a
security@zipnative.devinbox(SECURITY.md names the shared
security@pdfnative.devfallback today)..github/drafts/(git-ignored, onthe release machine) under your own identity after review: DOS time encoded from local
getters (the CLI compensates with UTC wall-clock components);
iterateZipEntries().data()pumping a registered custom-method entry through the inflater instead of refusing before
the first byte; the node-zlib inflate tier leaking raw
Z_DATA_ERROR/Z_BUF_ERRORinstead of
ZIP_DEFLATE_*(the CLI maps them inziperr.ts);verifyEntry()notreporting the
skippedreasonverifyZip()knows (the CLI re-derives it). Each passesgovern verify-issue.zipnative(ecosystem.json, README, ROADMAP) announcing the CLI.Backward compatibility
E_*classes, the 39-entryZIP_*mapping, exit codes (0/1/2, plus 130/143 on signals) andthe 22 schema subjects are frozen for 1.x; additions are minor, changes are major.
deterministic: truebytes are never post-processed by the CLI, socreate --deterministicoutput inherits zipnative's frozen byte contract.Out of scope (recorded in ROADMAP)
zipnative-mcp(separate repository).zip64streaming ADR.stream— waits on the engine (upstream draft).--explain <ZIP_CODE>, adeflatecommand,inspect --diff,create --from-list,--store-symlinksand manifestexternalAttributes,list --extra, inflate-tier visibilityin
doctor, user-level config, positional arguments in manifest tasks, lazy engine require,fuzz tests,
noUncheckedIndexedAccess, one ecosystem governance schema, man pages,scripts/verify-docs.mjs, veraZIP sync automation.Self-review checklist
npm run typecheck:allcleannpm run lint0 errors (src and tests)npm run test:coveragegreen, thresholds (93 / 88 / 94 / 93) metnpm run build+ built-binary smoke test (node dist/cli.cjs --help, every command,schema manifest,create --parallel,doctoron thenode-zlibtier)npm run validate:zippasses locally (33 PASS + 4 XFAIL, exit 0) and inverazip.ymlsrc/core-bridge/index.tsandwrapped by
mapZipError/guardZipLimitsbound,--max-input-size,safeJoin+ realpath containment, exclusive opens, overwriterefusal,
modifyverification,--codecargv-only, manifest codec gate)docs/KNOWLEDGE_BASE.md§8 (docs/data/core-exports.json)ZIP_TO_CLIand documented (docs/data/errors.json,AGENTS.md); 11/11 diagnostics listed
tests/docs/consistency.test.tsandtests/utils/governance-sync.test.tsgreenzipnativeremains the only one); no socket anywhere