The published 0.4 baseline has two export-map keys and eleven named types under @askrjs/cli/ssg. The candidate retains both keys and two configuration contracts. The askr binary remains the command interface. The SSG module has no runtime value exports.
| Baseline export | Decision | Need or migration |
|---|---|---|
SitemapConfig |
KEEP | Authored ssg.config.ts modules configure sitemap defaults, route overrides and asynchronous metadata resolvers; the SSG guide uses this type. |
SsgOutputReportConfig |
KEEP | Authored SSG config controls deployment prefixes, report summary limits and enforced byte/hydration budgets. |
SitemapChangeFrequency |
REMOVE | Derive NonNullable<NonNullable<SitemapConfig["defaults"]>["changeFrequency"]>. |
SitemapRouteConfig |
REMOVE | Derive Exclude<NonNullable<SitemapConfig["routes"]>[string], false>. |
SitemapRouteContext |
REMOVE | Derive Parameters<NonNullable<SitemapConfig["resolve"]>>[0]. |
SsgByteBudget |
REMOVE | Derive NonNullable<NonNullable<SsgOutputReportConfig["budgets"]>["routes"]>. |
SsgOutputBudgets |
REMOVE | Derive NonNullable<SsgOutputReportConfig["budgets"]>. |
SsgOutputReport |
REMOVE | The generated report is a versioned CLI JSON artifact; no programmatic report reader is exported. Consumers validate the documented artifact fields they use. |
SsgOutputAsset |
REMOVE | Internal report writer shape; consume documented asset JSON fields, validating incoming data. |
SsgOutputRoute |
REMOVE | Internal report writer shape; consume documented route JSON fields, validating incoming data. |
SsgOutputSize |
REMOVE | Internal report writer shape; consume documented raw/gzip JSON fields, validating incoming data. |
./ssg is kept for the two documented configuration owners. ./package.json is kept for tools reading the CLI version and metadata. The package root and implementation modules remain private. No shim preserves a removed type.
Sibling source, template, test and Markdown inventories found no external named import of the nine removed types. The existing sitemap guide already imports the retained owner. Internal writer tests continue to import their source-owned types; these are not public package paths.
The same analysis path now lazily compiles each reached exclusion glob once per workspace and collects state declarations from the existing call index instead of repeatedly walking entire syntax trees. All configured/discovered files pass the same compiled matchers. Compilation still short-circuits unused patterns; compiled matchers live only for this workspace analysis. There is no alternate execution path or cache persisting across analyses; the existing per-source syntax-fact cache retains its ownership. Legibility cost: one named per-workspace exclusion predicate, with the state collector using its existing indexed calls.
The exact trigger and error behavior remain those of the existing matcher and analyzer. Regression assertions cover configured/discovered files, dot directories, braces, single-character globs, comment patterns, skipped invalid patterns after an earlier match, direct aliased/namespace state initializers and nested non-initializer calls. The remaining rule, ignore, symlink and update tests qualify the wider behavior. Differential comparisons also check complete reports against the immutable base: all eight retained samples per fixture matched exactly, including all 33 pre-existing website-source diagnostics. The standalone paired website analysis mean was 184.4 → 167.3 ms; this is an analyzer measurement, not page rendering performance.
An unmodified Node 24.21.0/Vitest 4.1.11 baseline at 74ea2de measured 35.6 / 159.4 / 165.5 ms across the 50-file, 250-file and five-workspace fixtures (eight samples each). The changed path measured 30.0 / 136.8 / 139.0 ms with the unchanged workload and budgets. A fresh standalone profile also measured the website workload and found repeated state-binding work among its leading rule costs. Hosted acceptance is recorded separately; local measurements do not clear the previously failing merge gate.
The follow-up at 1e9f221 addresses the remaining repeated traversal: collect every syntax node in the existing source-fact index, then visit that same preorder array for each rule. The CPU profile attributed about 71% of the benchmark's sampled time to repeated rule walks. There is one path: the first use builds the index and later rules read it; no fallback or new cache owner is introduced. Legibility and memory cost: one node array in the existing source-owned facts, retaining one reference per syntax node for the same source-file lifetime. Rule callbacks, node order and error handling remain unchanged.
Matched Node 24.21.0/Vitest 4.1.11 runs measured 30.4 / 140.2 / 142.4 → 20.3 / 87.9 / 91.0 ms with the same eight samples, fixture assertions and budgets. Alternating standalone base/candidate runs also retained identical complete reports for all three fixtures and website source; the website analysis mean was 169.3 → 128.3 ms, preserving its 33 diagnostics. A mixed case covering eight rules preserved complete check-mode reports, applied-fix reports and the resulting source bytes. A source-rewrite regression checks nested reads, setter calls and fresh aliased/namespace bindings on a second analysis. Hosted merge acceptance is still required, and these measurements do not qualify page rendering or a capacity limit.
Manifest edits now use the same guarded file writer as other CLI file changes: read and validate the plan, lock guarded targets, stage original copies and replacements, replace files, then restore completed replacements on failure. Copies that cannot be restored are retained and named in the error. Exclusive file creation records ownership before writing, so partial writes can be cleaned without deleting a pre-existing file at a colliding path. Filesystem errors other than a missing target abort staging. Existing POSIX permission bits survive replacement and rollback, while new files retain the current umask.
The executed failure matrix covers replacement followed by restoration failure, partial original-copy and replacement writes, exclusive-creation collisions, permission errors, unrelated manifest changes after planning, success/rollback under a restrictive umask, new-file permissions and failed removal of a created file. Minimal regressions were run against the immutable pre-fix source. A separate child-process case is terminated after its first replacement: all original bytes remain available, manual recovery restores the targets, and a retry recovers the dead process's locks and succeeds. Recovery of these file transactions after abrupt termination is manual; these results do not qualify power-loss durability.
Create, skills, SSG and OpenAPI generator publication use one sibling record before moving an existing directory. Before the next locked operation, a missing live target is restored from its recorded original backup. A completed publication is identified by the staged directory's device and inode before its original backup is removed. Failed backup/record cleanup is retried by the next operation. Invalid records, non-directory or symbolic-link targets, and a different directory appearing at the target stop recovery and preserve the trees for inspection. Only a missing target is treated as absent; permission errors propagate. Stage directories must be separate siblings of the target.
Executed regressions kill a child process before the original rename, after the original rename and after the stage rename, then assert recovery before an incremental copy. Additional assertions cover malformed and misplaced records, junctions, conflicting live directories, missing recovery trees, failed rollback, failed backup/record cleanup, partial record writes, invalid stage paths and destination permission errors. The pre-fix shared publication source failed six minimal cases. These checkpoints qualify process termination with the tested filesystems, not power-loss durability or uncooperative concurrent changes.
The OpenAPI generator now uses that same publication routine and preserves its directory ownership check. Separate child-process probes reproduce and check partial stage writes, partial backup deletion, failed rollback and stage cleanup, destination read/stat permission errors, symbolic-link output and the same three termination checkpoints. Check mode remains observational. Project creation rechecks the missing/empty destination while holding the publication lock, after recovery; a deterministic concurrent-create regression asserts exactly one success and preservation of the first project's unrelated file. Another case restores interrupted generated output before refusing project creation over it. Remaining CLI #159 malformed-input and subprocess probes are tracked until executed.
Create reports success after publication completes. Child-process tests inject package-manager nonzero exit, missing-command and timeout results, then assert an error exit with no published destination or abandoned project stage. A successful install followed by failed publication also reports no success. These are subprocess result injections, not qualification of a real installer timeout or cancellation bound.
File transactions and directory publication now share one lock protocol: prepare a unique owner record in a sibling directory, publish the nonempty lock with a rename, then unlink only the observed dead owner or this operation's own owner and remove the directory only if empty. A delayed stale reaper cannot unlink a successor's different owner record. This replaces the two duplicated recursive-lock-removal implementations; the ten-second wait bound is unchanged. Existing dead owner.json records and aged empty/malformed legacy locks can be recovered. Ambiguous contents, symbolic links and owner-record junctions stop recovery and preserve unrelated paths.
The executed matrix covers two simultaneous stale-owner observations for file and directory operations, exact stale-write rejection, unrelated lock contents, junctions, fresh malformed-record timeout, aged legacy recovery, partial owner writes, failed preparation cleanup and an exclusive preparation collision. It also verifies successor ownership during release, denied process probes and filesystem errors during acquisition. The original six minimal cases failed before the fix. Separate child-process cases terminate after preparing an owner but before publishing the lock: data is unchanged, no incomplete live lock is exposed, retry succeeds and the abandoned private preparation remains available for manual cleanup. Existing file/directory termination and recovery cases also run with the shared protocol. These results qualify the tested process checkpoints and cooperating current CLI commands, not power-loss durability or concurrent use of older lock protocols.
On Windows, inspecting a lock directory or opening its owner record while its previous owner removes it can return EPERM. The acquisition loop retries read-only lock observations (lstat, scandir, open, read) within its existing ten-second bound and retains the native cause if denial persists; write/deletion failures and other filesystem errors still propagate. A repeated four-writer generator probe reproduced native scan and owner-open failures on separate source heads. Deterministic file/directory cases verify transient recovery, permanent-denial timeout, exact bytes and owner preservation; separate deletion-denial cases verify those errors propagate without entering a transaction.
Lock preflight and rename now have separate error classification. A one-shot EACCES during preflight propagates without entering a transaction, preserving its exact owner record and file bytes. OpenAPI loading destroys unused response bodies before rejecting headers/status or following a redirect. The executed matrix and tested process boundaries are recorded in 0.5.0 hardening.
The normal packed consumer checks both retained declaration names, nine rejected old imports and private paths under strict TypeScript 6 and 7, then runs the installed CLI help/version commands. No production dependency, package version or peer range changes in this cut. Full candidate graph, generated website API and maintainer review still gate release. CLI hardening issue #159 stays open until its remaining qualification is complete.