Compare commits

...

3 Commits

Author SHA1 Message Date
slhx agent 7fe9429243 chore: untrack generated build cache 2026-07-20 16:45:18 +02:00
slhx agent a8dbe3221f chore: remove generated migration artifacts 2026-07-20 16:44:53 +02:00
slhx agent 054aa4d36e chore: point to toolset migration 2026-07-20 16:44:39 +02:00
7 changed files with 12 additions and 312 deletions
+2 -8
View File
@@ -1,8 +1,2 @@
# Zig build artifacts
.zig-cache/
zig-out/
# Local editor/OS noise
*.swp
*~
.DS_Store
/.zig-cache/
/zig-out/
-19
View File
@@ -1,19 +0,0 @@
# Agent Instructions
## Work tracking
No durable tracker or plan file is configured in this repository. Treat the user request as the active work authority unless a future shared tracker, issue, or plan is explicitly named. Do not create local issue mirrors, PLAN.md, workledgers, or tracker state without explicit instruction.
## Requirement governance
`REQUIREMENTS.md` is the requirement authority for this repository and is checked with `redgate`. Before implementation work, read the applicable requirement row; if none covers the durable behavior, update `REQUIREMENTS.md` first or stop and ask for authority. When adding or changing rows, choose the ring by product foundation, not implementation size: lower rings capture stable kernel obligations and core contracts; higher rings compose on lower-ring obligations and must not weaken, redefine, or bypass them. If one row mixes foundational and optional behavior, split it before implementation. If the change alters durable product obligations, acceptance behavior, safety/recovery behavior, public interfaces, or verification duties, update `REQUIREMENTS.md` in the same slice and run the relevant `redgate` checks.
For behavior intended to stick, write or update a failing BDD/TDD test, contract test, or executable proof before implementation code; run it and record the RED result. Implement only after the requirement row and RED proof exist. If RED proof would be fake, unsafe, or disproportionate, state that exception before implementation and use the strongest cheaper check. Before final handoff, compare the user request and actual diff against `REQUIREMENTS.md`. If there is no requirement impact, the handoff must include exactly `REQUIREMENT IMPACT: none - <specific reason>`. If code, tests, or docs changed but `REQUIREMENTS.md` did not, explicitly justify why no requirement row changed.
## Single-file agent-facing tool
`lines.zig` is a single-file, read-only inspection primitive for agents and humans. Keep stdout to selected file content only; send usage and diagnostics to stderr. Do not add regexes, embedded languages, shell evaluation, network access, deletion, hidden writes, prompts, colors, pagers, config discovery, or output formatting modes. Prefer POSIX `sh` smoke coverage in `smoke.sh` for the CLI contract; keep any broader workflow outside the tool.
## Verification
Run `zig build test` for the CLI smoke and `redgate lint --strict && redgate health --strict` for requirement-shape and citation checks before handoff.
+10
View File
@@ -0,0 +1,10 @@
# lines — migrated
The canonical `lines` agent tool moved to `/opt/repositories/toolset`.
This repository no longer builds or releases an executable. Its Git history is the migration record; source, requirements, tests, installation, and release authority now live in toolset.
```sh
cd /opt/repositories/toolset
zig build install-text -p "$HOME/.ink"
```
-37
View File
@@ -1,37 +0,0 @@
# Requirements
## cli
0 001 `lines FILE START optional-END` prints exactly the 1-based inclusive line range from FILE, preserving selected bytes and newline boundaries without headers or decoration. [core]
0 001.e1 Usage errors, invalid ranges, unreadable files, and input read errors exit nonzero with diagnostics on stderr; errors detected before output begins must not emit stdout bytes. [errors]
0 002 Successful range output writes selected file content to stdout only; usage, diagnostics, warnings, and errors write to stderr only. [stdio]
0 003 The public argv grammar is only `FILE START optional-END` plus `--help`/`-h`; no regexes, shell evaluation, embedded programs, glob policy, config discovery, prompts, or formatting modes are allowed. [agent]
0 004 `lines` is read-only: it must not create, modify, delete, cache, page, daemonize, call the network, or persist hidden state during normal use. [safety]
1 005 `lines --help` documents usage, positive line-number rules, stdout/stderr boundaries, exit statuses, examples, and the read-only/no-mini-language agent boundary. [docs]
## robustness
0 001 Missing arguments, invalid decimal integers, zero, overflow, `END` less than `START`, unreadable paths, and permission errors exit nonzero with a useful stderr diagnostic and no stdout bytes. [errors]
0 002 Empty files, files without a trailing newline, CRLF input, binary-ish bytes including NUL, and invalid UTF-8 are processed as bytes without text decoding or output corruption. [bytes]
0 003 Very long physical lines must not require allocating the whole line or whole file; selected bytes stream through bounded buffers. [memory]
0 004 Huge valid `u64` line numbers are accepted without truncation; ranges beyond EOF succeed quietly after emitting any available selected lines. [range]
0 005 Pipeline use such as `lines file 1 1000000 | head` must not print noisy broken-pipe diagnostics or corrupt stdout. [pipe]
## performance
0 001 Normal operation must stream or otherwise prove bounded memory: memory use is independent of file size and limited to fixed I/O buffers plus argv/path bookkeeping. [memory]
0 002 The large-file proof must generate a local file of at least 1 GiB or 10 million lines, whichever is cheaper on the machine, and record elapsed time plus peak resident memory. [bench]
0 003 For a late-file range on the large proof file, `lines` must target within 2x elapsed time and within 2x peak resident memory of the relevant `sed -n START,ENDp` or `awk` baseline; unavailable measurement must be reported as a blocker or reroute. [bench]
0 004 Performance work must use boring Zig/std/POSIX mechanisms first; assembly, mandatory SIMD, custom allocators, mmap-only design, background indexes, caches, or daemons are out of scope. [boring]
## portability
0 001 The implementation must stay a single Zig source file plus the existing build/smoke files and avoid third-party runtime dependencies. [single]
0 002 File I/O and process behavior must use Zig standard library or portable POSIX-compatible primitives; any target-specific branch must be documented and covered by a fallback. [portable]
1 003 `zig build test` remains the primary verification entry point and must run through a POSIX `sh` smoke without GNU-only shell features. [test]
## verification
1 001 `zig build test` proves the main output path, help text contract, invalid-input stdout cleanliness, and at least one edge case for EOF or missing trailing newline. [smoke]
1 002 A robustness smoke must cover empty files, no trailing newline, CRLF, long lines, invalid UTF-8 or binary-ish bytes, huge line numbers, `END` less than `START`, missing files, and pipeline/SIGPIPE behavior. [smoke]
1 003 A performance proof must be reproducible from repo commands, clean generated large files after completion, and report command lines, elapsed time, and peak memory for `lines` and the baseline. [bench]
-35
View File
@@ -1,35 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "lines",
.root_module = b.createModule(.{
.root_source_file = b.path("lines.zig"),
.target = target,
.optimize = optimize,
}),
});
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);
b.step("run", "Run lines").dependOn(&run_cmd.step);
// req: verification/001
const smoke = b.addSystemCommand(&.{ "sh", "smoke.sh", "zig-out/bin/lines" });
smoke.setCwd(b.path("."));
smoke.step.dependOn(b.getInstallStep());
b.step("test", "Run POSIX smoke test").dependOn(&smoke.step);
// req: performance/002
// req: performance/003
// req: verification/003
const perf = b.addSystemCommand(&.{ "sh", "perf.sh", "zig-out/bin/lines" });
perf.setCwd(b.path("."));
perf.step.dependOn(b.getInstallStep());
b.step("perf", "Run large-file elapsed-time and RSS proof").dependOn(&perf.step);
}
-121
View File
@@ -1,121 +0,0 @@
const std = @import("std");
// req: cli/002
fn usage(file: std.Io.File, io: std.Io) !void {
var buffer: [1024]u8 = undefined;
var writer = file.writer(io, &buffer);
try writer.interface.writeAll(
\\usage: lines FILE START [END]
\\
\\Print a 1-based inclusive line range from FILE to stdout.
\\START and END must be positive decimal integers; END defaults to START.
\\No writes, no regex, no program language, no stdin prompts.
\\
\\output:
\\ stdout: selected file lines only, with original line order preserved
\\ stderr: usage and diagnostics only
\\
\\exit status:
\\ 0 on success, 1 on runtime/input errors, 2 on usage errors
\\
\\examples:
\\ lines src/main.zig 40 80
\\ lines README.md 1 20 | grep usage
\\
);
try writer.interface.flush();
}
fn fail(io: std.Io, comptime fmt: []const u8, args: anytype) noreturn {
var buffer: [512]u8 = undefined;
var writer = std.Io.File.stderr().writer(io, &buffer);
writer.interface.print(fmt, args) catch {};
writer.interface.flush() catch {};
std.process.exit(1);
}
fn parseLineNo(text: []const u8, name: []const u8, io: std.Io) u64 {
const value = std.fmt.parseInt(u64, text, 10) catch fail(io, "lines: invalid {s}: {s}\n", .{ name, text });
if (value == 0) fail(io, "lines: {s} must be positive\n", .{name});
return value;
}
fn stdoutFailure(out: *std.Io.File.Writer, io: std.Io) noreturn {
if (out.err) |err| switch (err) {
error.BrokenPipe => std.process.exit(0),
else => fail(io, "lines: stdout: {s}\n", .{@errorName(err)}),
};
fail(io, "lines: stdout: WriteFailed\n", .{});
}
fn writeStdout(out: *std.Io.File.Writer, io: std.Io, bytes: []const u8) void {
out.interface.writeAll(bytes) catch stdoutFailure(out, io);
}
fn flushStdout(out: *std.Io.File.Writer, io: std.Io) void {
out.interface.flush() catch stdoutFailure(out, io);
}
// req: cli/001
// req: robustness/002
// req: robustness/003
// req: performance/001
fn streamRange(file: std.Io.File, io: std.Io, start: u64, end: u64) void {
var read_buffer: [64 * 1024]u8 = undefined;
var reader_file = file.readerStreaming(io, &read_buffer);
var chunk: [64 * 1024]u8 = undefined;
var out_buffer: [64 * 1024]u8 = undefined;
var stdout_file = std.Io.File.stdout().writerStreaming(io, &out_buffer);
var line_no: u64 = 1;
while (line_no <= end) {
const n = reader_file.interface.readSliceShort(&chunk) catch |err| fail(io, "lines: read failed: {s}\n", .{@errorName(err)});
if (n == 0) break;
var pos: usize = 0;
while (pos < n and line_no <= end) {
const rest = chunk[pos..n];
const line_end = if (std.mem.indexOfScalar(u8, rest, '\n')) |newline| pos + newline + 1 else n;
if (line_no >= start) writeStdout(&stdout_file, io, chunk[pos..line_end]);
if (line_end <= n and chunk[line_end - 1] == '\n') line_no += 1;
pos = line_end;
}
}
flushStdout(&stdout_file, io);
}
pub fn main(init: std.process.Init) !void {
const allocator = init.gpa;
var args = try std.process.Args.Iterator.initAllocator(init.minimal.args, allocator);
defer args.deinit();
_ = args.skip();
const path = args.next() orelse {
try usage(std.Io.File.stderr(), init.io);
std.process.exit(2);
};
if (std.mem.eql(u8, path, "--help") or std.mem.eql(u8, path, "-h")) {
try usage(std.Io.File.stdout(), init.io);
return;
}
const start_text = args.next() orelse {
try usage(std.Io.File.stderr(), init.io);
std.process.exit(2);
};
const maybe_end_text = args.next();
if (args.next() != null) {
try usage(std.Io.File.stderr(), init.io);
std.process.exit(2);
}
// req: cli/001
// req: cli/001.e1
// req: cli/003
const start = parseLineNo(start_text, "START", init.io);
const end = if (maybe_end_text) |end_text| parseLineNo(end_text, "END", init.io) else start;
if (end < start) fail(init.io, "lines: END must be >= START\n", .{});
const file = std.Io.Dir.cwd().openFile(init.io, path, .{ .mode = .read_only, .allow_directory = false }) catch |err| fail(init.io, "lines: {s}: {s}\n", .{ path, @errorName(err) });
defer file.close(init.io);
streamRange(file, init.io, start, end);
}
-92
View File
@@ -1,92 +0,0 @@
#!/bin/sh
# POSIX smoke for the agent-facing lines tool.
# req: cli/001
# req: cli/001.e1
# req: cli/002
# req: robustness/001
# req: robustness/002
# req: robustness/003
# req: robustness/004
# req: robustness/005
# req: verification/001
# req: verification/002
set -eu
bin=${1:-zig-out/bin/lines}
tmp=${TMPDIR:-/tmp}/lines-smoke.$$
trap 'rm -f "$tmp".*' EXIT HUP INT TERM
printf 'a\nb\nc\n' > "$tmp.in"
printf 'b\nc\n' > "$tmp.want"
"$bin" "$tmp.in" 2 3 > "$tmp.out"
cmp "$tmp.want" "$tmp.out"
"$bin" --help > "$tmp.out"
grep 'stdout: selected file lines only' "$tmp.out" >/dev/null
grep 'exit status:' "$tmp.out" >/dev/null
grep 'No writes, no regex, no program language' "$tmp.out" >/dev/null
: > "$tmp.empty"
"$bin" "$tmp.empty" 1 > "$tmp.out"
[ ! -s "$tmp.out" ]
printf 'tail-no-newline' > "$tmp.nonewline"
printf 'tail-no-newline' > "$tmp.want"
"$bin" "$tmp.nonewline" 1 > "$tmp.out"
cmp "$tmp.want" "$tmp.out"
printf 'a\r\nb\r\n' > "$tmp.crlf"
printf 'b\r\n' > "$tmp.want"
"$bin" "$tmp.crlf" 2 > "$tmp.out"
cmp "$tmp.want" "$tmp.out"
awk 'BEGIN { for (i = 0; i < 70000; i++) printf "x"; printf "\nsecond\n" }' > "$tmp.long"
awk 'BEGIN { for (i = 0; i < 70000; i++) printf "x"; printf "\n" }' > "$tmp.want"
"$bin" "$tmp.long" 1 > "$tmp.out"
cmp "$tmp.want" "$tmp.out"
printf 'ok\n\377\000x\n' > "$tmp.binary"
printf '\377\000x\n' > "$tmp.want"
"$bin" "$tmp.binary" 2 > "$tmp.out"
cmp "$tmp.want" "$tmp.out"
"$bin" "$tmp.in" 18446744073709551615 > "$tmp.out"
[ ! -s "$tmp.out" ]
if "$bin" "$tmp.in" 0 > "$tmp.out" 2> "$tmp.err"; then
echo 'smoke: invalid START unexpectedly succeeded' >&2
exit 1
fi
[ ! -s "$tmp.out" ]
grep 'START must be positive' "$tmp.err" >/dev/null
if "$bin" "$tmp.in" 3 2 > "$tmp.out" 2> "$tmp.err"; then
echo 'smoke: END < START unexpectedly succeeded' >&2
exit 1
fi
[ ! -s "$tmp.out" ]
grep 'END must be >= START' "$tmp.err" >/dev/null
if "$bin" "$tmp.missing" 1 > "$tmp.out" 2> "$tmp.err"; then
echo 'smoke: missing file unexpectedly succeeded' >&2
exit 1
fi
[ ! -s "$tmp.out" ]
grep 'lines: .*missing' "$tmp.err" >/dev/null
if [ "$(id -u)" -ne 0 ]; then
printf 'secret\n' > "$tmp.denied"
chmod 000 "$tmp.denied"
if "$bin" "$tmp.denied" 1 > "$tmp.out" 2> "$tmp.err"; then
chmod 600 "$tmp.denied"
echo 'smoke: permission-denied file unexpectedly succeeded' >&2
exit 1
fi
chmod 600 "$tmp.denied"
[ ! -s "$tmp.out" ]
grep 'lines: .*denied' "$tmp.err" >/dev/null
fi
"$bin" "$tmp.long" 1 2 2> "$tmp.err" | head -n 1 > /dev/null
[ ! -s "$tmp.err" ]