v0.19.0: Practical CLI and Streaming Filesystem Workflows
Status: Phase 0 through Phase 4 complete; published and externally audited (2026-08-28). The publication record freezes the release evidence. v0.19.0 returns development to application-driven standard-library work. Compiler changes are permitted only when one accepted public API cannot be expressed correctly by the existing language.
Release Goal
Nocter v0.19.0 is complete when a recursive text-search command can be written using only public standard-library APIs, processes large files without retaining their complete contents, reports recoverable filesystem failures, produces deterministic requested output, and receives ordinary hover, completion, and navigation for every public API it uses.
The reference application is an acceptance consumer, not a second semantic authority. Public behavior is specified in spec/; target facts remain under std/internal/os; portable policy and resource ownership remain in their standard-library modules.
Phase 0: Filesystem Traversal
Phase 0 adds one fallible, owning directory stream and owned UTF-8 directory entries:
pub struct ReadDir
pub struct DirEntry
instance ReadDir {
pub method &+self.next(): DirEntry?!
pub method &+self.close(): void
}
instance DirEntry {
pub method &self.file_name(): &str
pub method &self.path(): &Utf8Path
pub method &self.file_type(): FileType
}
pub func read_dir(path: &str): ReadDir!
next is deliberately not an Iterator method: directory progress may fail after construction, and the current iterator contract cannot hide a recoverable per-step failure. The outer ! reports that failure; the inner ? reports clean end of stream.
Completion Gate
Phase 0 is complete only when:
- the public specification fixes ordering, dot-entry filtering, symlink classification, UTF-8 policy, terminal failure, and explicit/automatic close behavior;
- Darwin syscall numbers, flags, record offsets, and record kinds have one owner under
std/internal/os/darwinand are sourced from the installed Darwin SDK contract; std/fsalone owns descriptor lifetime, record validation, UTF-8 conversion, path construction, and public error mapping;- every opened descriptor closes exactly once on explicit close, end of stream, failure, or drop;
- malformed native records cannot create a borrow outside the owned buffer or cause a repeated failure loop;
- native tests cover entries across multiple record batches, dot filtering, regular, directory, and symbolic-link classification, nonexistent and non-directory paths, invalid UTF-8 names, explicit close, and drop cleanup;
- standard contract and implementation sources provide source-backed hover, completion, and navigation without editor-only declaration reconstruction;
- complete locked tests, warnings-denied Clippy, formatting, generated documentation, and repository integrity checks pass;
- the final review finds no new compiler primitive, duplicate path decoder, duplicated errno policy, descriptor leak, compatibility wrapper, or target record layout outside its target owner.
Phase 1: Streaming Text Input
Phase 1 adds line-oriented methods to BufReader rather than to Reader: only the buffered owner can retain bytes after the first line delimiter without losing them from a general byte stream.
instance BufReader {
pub method &+self.read_line(): String?!
pub method &+self.read_line_into(destination: &+String): bool!
pub method &+self.close(): void
impl Reader
}
The owned method returns none only when EOF was reached before another byte. The reusable method clears its destination first and returns false for the same condition. Both remove LF and one immediately preceding CR, preserve every other byte, accept a final unterminated line, and reject invalid UTF-8 through the existing std.string.invalid_utf8 contract.
Completion Gate
Phase 1 is complete only when:
- the public specification fixes empty lines, LF, CRLF, lone CR, final unterminated lines, EOF, invalid UTF-8, destination state, partial reads, interruption, and terminal behavior;
- one package-internal UTF-8 contract owns validation and error construction, while
Stringalone owns copying validated bytes into its representation; BufReaderretains unread source bytes and at most one current line, never the complete file;- the caller-provided destination reuses its allocation and is empty on EOF or failure;
- EOF, explicit close, underlying read failure, invalid UTF-8, and recoverable growth failure
converge on one terminal state, and later byte reads report zero while line reads report
none; - zero requested buffer capacity is normalized to a progressing nonzero buffer;
- native tests cover delimiter boundaries, UTF-8 scalars across refills, short reads, lines larger than the buffer, an empty line, CRLF, lone CR, final unterminated input, invalid UTF-8, repeated EOF, explicit close, and destination reuse;
- standard contract and implementation sources provide source-backed hover, completion, and navigation without editor-only reconstruction;
- complete locked tests, warnings-denied Clippy, formatting, generated documentation, repository integrity checks, and the final responsibility-boundary review pass.
Phase 2: Collection Ordering
Phase 2 adds one allocation-free in-place ordering operation to readwrite slices under the existing < operator contract:
instance [T] {
pub method &+self.sort(): void where (&T < &T): bool
}
The result is ascending under the declared strict total order. The operation preserves every element exactly once, accepts move-only elements, performs no allocation, uses constant auxiliary storage, and has O(n log n) worst-case comparisons and moves. It deliberately does not preserve the relative order of elements for which neither operand is less. A future stable ordering API must therefore use a distinct contract rather than silently changing this one.
Vec<T> reaches this exact method through its existing readwrite slice coercion. It does not own a second declaration, forwarding wrapper, or ordering algorithm.
Completion Gate
Phase 2 is complete only when:
- the public specification fixes ascending order, strict-order preconditions, instability, complexity, allocation, trap/recoverable-failure behavior, and ownership behavior;
- one slice implementation owns comparison and movement, and
Vec<T>reaches it through ordinary operation selection on its declared readwrite coercion; - comparison only borrows elements and every rearrangement transfers two initialized owners into two holes without copying, early destruction, or an observable partially initialized slice;
- empty, one-element, already ordered, reverse, and duplicate sequences are valid;
- native tests cover an explicit readwrite slice view, Vec coercion, integers, move-only nominal values, duplicate keys, and exact destruction counts;
- standard contract and implementation sources provide source-backed hover, completion, and navigation without editor-only reconstruction;
- complete locked tests, warnings-denied Clippy, formatting, generated documentation, repository integrity checks, and the final responsibility-boundary review pass.
Phase 3: Reference Application
Phase 3 adds the complete examples/text-search package. Its command is:
text-search NEEDLE ROOT
ROOT is a directory. The command recursively visits regular files, skips symbolic links and other entry kinds, validates input as UTF-8, and writes matching lines as relative/path:line:text. Paths are sorted by UTF-8 byte order before files are read, so successful output is independent of directory enumeration order. Line numbers are one-based. Exit status 0 means at least one match, 1 means no match, and 2 means invalid arguments or a recoverable traversal, input, or output failure. Usage and failures are written to standard error.
The package uses only public APIs for traversal, streaming input, ordering, text search, numeric formatting, stdout, stderr, arguments, and process return status. It retains discovered paths but not complete file contents. One reusable String holds the current line, and a directory stream is closed before recursion enters any child.
Completion Gate
Phase 3 is complete because:
- the package README fixes arguments, traversal, symlink, UTF-8, output, ordering, error, partial output, exit-status, and storage behavior;
- the package imports no
std/internalmodule, compiler primitive, package-private declaration, or application-specific standard-library adapter; - native process acceptance covers usage, deterministic nested matches, no match, a symlink loop, a missing root, and invalid UTF-8;
- filesystem fixture contracts represent files, directories, and symbolic links explicitly rather than hiding this application's tree in one special test;
- package-mode compilation and execution use the same public-example registry as every other complete example;
- opening the real package source in the language server provides source-backed hover and navigation for traversal, streaming line input, and Vec-to-slice ordering through ordinary workspace analysis;
- the final review finds no retained whole-file input, open parent stream during recursion, filesystem-order output, symlink traversal, duplicated standard policy, compatibility path, or private escape hatch.
Phase 4: Stabilization and Release
Phase 4 reviewed contract/body separation, resource lifetime, allocation bounds, error paths, LSP behavior, and application performance across the complete reference path. The resulting stabilization review records the final findings and remediation.
The release candidate passed duplicate source qualification, deterministic package generation, and the fresh-install smoke process recorded in v0.19.0-release-preparation.md. Qualification and publication remain separate operations: completing this phase does not tag, upload, or publish an artifact. The separately authorized publication is recorded in ../releases/v0.19.0.md.
Completion Gate
Phase 4 implementation is complete because:
File,BufReader,BufWriter, andReadDireach have one terminal state transition and one resource-release authority;- collection and string growth use one checked geometric-capacity policy, zero-sized Vec elements follow the language ownership model, and explicit zero-capacity construction retains allocator selection;
- borrowed and owned text splitting use one component-boundary state machine, and iterator adapters
claim
ExactSizeIteratoronly when every valid remaining count is representable; - the reference application buffers output, closes directory streams before recursion, retains no complete file, and preserves deterministic successful output;
- standard module dependencies, restricted visibility, contract-only roots, private helper use, and package-absolute self imports are enforced from resolved or checked semantic facts;
- the real application package passes native process contracts and source-backed editor queries;
- the final review found no compatibility wrapper, duplicate allocator release path, duplicated split algorithm, hidden compiler primitive, or application-specific semantic branch.