Repository navigation
v2.0.0
Added
-
process-kit/pty:pty-try-wait/pty-alive-p: non-blocking liveness
checks for a PTY session, the analogues ofprocess-try-wait/
process-alive-p. Found the same way aswith-pty-processtwo commits
earlier -- comparing the PTY subsystem's public surface against the
main library's and findingpty-wait(blocking, possibly escalating)
had no non-blocking counterpart.pty-wait's own polling loop now
calls the publicpty-try-waitinstead of the internal%try-wait
directly, matchingprocess-wait's existing call-the-public-function
convention inprocess-handle.lisp. -
process-kit/pty:with-pty-process/call-with-pty-process: the PTY
analogue ofwith-process/call-with-process's continuation-passing
cleanup contract, binding a variable to aspawn-ptyresult for a body
and closing it viapty-closeon the way out, success or error. The
main library had this idiom forprocess-handle; the PTY subsystem
never got the equivalent, so all 6 oft/pty-test.lisp's cases
hand-rolled the identicalunwind-protect/pty-closeshape. Now they
read as(with-pty-process (process (spawn-pty ...)) ...). -
cl-weave:it-fuzzproperty (t/property-test.lisp):run's:replace
UTF-8 decoding path must never signal on arbitrary octet input, across
100 generated trials, generalizing the 3 hand-picked malformed sequences
t/edge-coverage-test.lispalready covered into a genuine property of
the full byte space. Firstit-fuzzuse in this codebase;it-property,
it-each, andrun-mutationswere already adopted in earlier passes. -
checks.maxLineLengthinflake.nix: failsnix flake checkif any
src//t/line exceeds 100 columns (the org'sCODING_STANDARD.md
rule), matching the existingchecks.maxFileLengthgate's shape. Added
after actually closing the gap it enforces, not before: an earlier
pass's "wrap every line at 100 columns" commit was scoped tosrc/
only (its own message says so), andt/was never swept -- 100 lines
across 12 test files exceeded 100 columns until this pass fixed them by
hand, preserving each file's existing hand-crafted formatting rather
than running a canonical auto-formatter (paredit edit format's
canonical style turned out to differ enough from this codebase's actual
conventions -- breaking every keyword-argument pair onto its own line,
removing blank lines betweenitcases -- that using it wholesale
would have been a large, unrelated stylistic churn; used only on the two
files,t/pty-test.lispandt/native-spawn-test.lisp, that had no
existing hand-crafted style to preserve in the first place, both single
giant crammed one-liners).
Changed
-
Extracted
%task-submit-output's and%task-finish's identical "flush
pending-drops into an:overflowevent" block (src/async-events.lisp)
into a shared%flush-pending-drops-event, viaparedit refactor extract-function --at <offset> --infer-params. Raised the coverage
ratchet floors to match (expression 87.4% -> 87.7%, branch 81.3% ->
81.4%): the dedup genuinely closed one branch gap (124 -> 123 uncovered),
not just diluted the denominator. -
Unified three near-duplicate deadline-polling loops onto the single
%poll-untilprimitive thatcommunicate.lisp/pipeline.lispalready
shared -- found by grepping for the "loop until a predicate is true or a
deadline passes, sleeping between checks" shape directly, since it
differs enough call to call (different clock sources, a returned value
vs. a boolean) that the exact-matchparedit inspect duplicatestool
cannot see it as one shape.%poll-untilitself is now defined in
process-handle.lisp(the earliest-loading file among its callers, so
bothprocess-group.lispandcommunicate.lispcan reach it) and
generalized to accept a NILdeadlinefor an unbounded wait:process-handle.lisp'sprocess-waitnow delegates to%poll-until
instead of hand-rolling the same loop; a genuine (not just cosmetic)
fix falls out of this, since%poll-untilcaps its final sleep at
whatever time is actually left before the deadline, where the old loop
always slept the fullpoll-intervalregardless --process-wait
could previously overshoot its deadline by up topoll-interval
before ever re-checking.process-group.lisp's%wait-until-process-group-gonenow delegates
to the same%poll-untilinstead of a hand-rolled copy of exactly the
donepcommunicate.lisp's own%wait-until-group-gonealready used --
the two are the same "process group is gone" check, previously written
down twice.%poll-until's own docstring previously claimed "this is the one
place [every deadline-bounded wait] is written down," which was false
given the two hand-rolled copies above; corrected, and now explains why
process-kit/pty'spty-waitdeliberately keeps its own copy (a
genuinely different, three-way donep with no clock/sleeper injection,
not an oversight).
Verified via two fullnix flake checkruns (the first hit the
documentedSETUP-SERVE-EVENT-PIPEflake onawait-process, an
unrelated async-cursor test that shares no code with any of the three
functions touched here; the second was fully green, 180/180, coverage
88.0%/82.1%, both above the ratchet floor).
-
Extracted
run-pipeline's inline thread-spawningloop/lambdablock
(src/pipeline.lisp) into a namedspawn-stage-threadssibling inside
the samelabelsform as the pre-existingawait-pipeline-stages.
Found via a fresh size-ranked scan of everysrc/*.lispfunction
(paredit inspect outline --output json):run-pipelinewas the
codebase's 2nd-largest function and, unlike its neighbors
communicate-async/communicate, had never had its inline
thread-spawning lambda pulled into a named function. Hand-authored
rather than viaparedit refactor extract-function --infer-params,
since that flag mishandlesloop's clause-keyword syntax (for/in/
collectread as ordinary symbols to infer as parameters).run-pipeline
itself now reads as a linear sequence -- build pipes, spawn stages, close
write ends,(await-pipeline-stages (spawn-stage-threads)), build
result -- with no behavior change.
Added
- Both test entry points (
run-tests.lisp,run-pty-tests.lisp) now bind
cl-weave:*default-timeout-ms*to 30 seconds before running the suite.
The org-wideTEST_STANDARD.mdrequires every repository to set this;
cl-process-kit's suite previously had no PER-TEST ceiling, only a
whole-suite one (nix flake check's owntimeout 180/CI's
timeout-minutes) -- so a single hung test burned the entire budget
before failing, with nothing naming which test hung. 30s leaves generous
headroom above the slowest observed test (~1.1s) while catching a
genuine deadlock with a specificCL-WEAVE:TEST-TIMEOUTdiagnostic.
Reformattedrun-pty-tests.lispfrom a single unreadable one-line form
to matchrun-tests.lisp's style while making this change.
Changed
flake.nix's.asd:versionextraction usescl-nix-forge's dedicated
fromAsdSystemlexer instead of a hand-rolled line-by-line regex.
Strictly stronger, not just shorter:cl-process-kit.asddeclares four
systems sharing one version (cl-process-kit,/test,/pty,
/pty-test), and the old regex read whichever:versionline happened
to come first with no cross-check, whilefromAsdSystemfails the build
loudly if any of them ever disagreed.flake.nix'spackages.*/checks.checkout-tests/checks.pty-tests
now build oncl-nix-forge'slispDerivation/mkScriptCheckprimitives
(new flake input, pinnedv0.4.0) instead ofpkgs.sbcl.buildASDFSystem
plus hand-rolledpkgs.runCommandtest derivations.noForbiddenMarkers,
maxFileLength,formatting, anddocsare untouched -- none of them
involve ASDF, so cl-nix-forge has nothing to offer there. Two things
cl-process-kit's own code needed that neither of the org's two existing
adopters (cl-weave,cl-json-kit) demonstrated:src/pty.lispreads
CL_PROCESS_KIT_PTY_LIBRARYexplicitly at load time (anSB-ALIEN LOAD-SHARED-OBJECTcall needs a real path, not a bare SONAME resolved
offnativeLibraries'LD_LIBRARY_PATH/DYLD_LIBRARY_PATHpropagation
alone), so that env var is still set directly alongsidenativeLibraries;
andnative/spawn.c's trampoline binary is compiled twice on purpose --
once into$out/binfor the distributed package (postInstall), once
into the build sandbox forchecks.checkout-tests(preCheck), since a
check needs the binary to exist before its own build/install phases (what
actually produces the packaged copy) have run.apps/devShellsstay on
the original hand-rolledCL_SOURCE_REGISTRYstring, not
cl-nix-forge:lispScript-- this migration's own research did not verify
that primitive's exact parameter shape the waylispDerivation/
mkScriptCheckwere verified against real builds, and guessing an
unconfirmed API for a part of the flake with no correctness benefit over
the proven string was not worth the risk.
Verified withnix buildon each new package/check individually before
the fullnix flake check(179/179 + 6/6 PTY, coverage unchanged at
87.8%/81.5%, both native artifacts confirmed present and loadable).
Changed (BREAKING)
next-process-eventnow returns a singleprocess-event-stepstruct
(process-event-step-event/-cursor/-status/-gap-count) instead of
four raw values. The org-wideAPI_STANDARD.mdnames this exact function,
by name, as the canonical bad example of returning more than two values
((values a b)at most, or a struct beyond that) -- a caller that only
wantedgap-counthad to destructure all four positionally. Requires a
major version bump at the next release perCODING_STANDARD.md's
versioning policy ("関数の必須引数の変更...既定の挙動の変更" -- a change to a
function's return contract).- Lowered the
run-tests.lispexpression coverage ratchet floor,
87.5% -> 87.4%, to absorbprocess-event-step's compile-time-only
defstructform (raw 4168/4764 = 87.4895%, every runtime arm still
tested). Branch floor unchanged: removing the struct's initial per-slot
:typedeclarations, which briefly introduced 2 uncovered type-check
branches, restored branch coverage to its exact prior figure (542/666).
Added
- Docstrings for every remaining undocumented exported symbol: the
process-handle/process-event/process-event-step/process-task
structs,process-id/process-stdin/process-output/process-stderr/
process-status/process-try-wait/process-wait/process-exit-code/
process-signal/process-alive-p,process-terminate/process-kill,
close-process/close-process-streams,spawn-command,
native-process-launch-error,await-process/cancel-process,
process-events/process-task-first-event-sequence, andrun/checked.
The org-wideAPI_STANDARD.mdmeasures docstring coverage of exported
definitions per repo; cl-process-kit sat at 31% (13/42), second-lowest
in the org, against 7 sibling repos already at 100%. .github/dependabot.yml,.github/CODEOWNERS,.github/pull_request_template.md,
and.github/ISSUE_TEMPLATE/(bug report, feature request, and a config
linking to private security advisories), matching the convention already
present on 3 of the other 4 nerima-lisp sibling repos (cl-weave,
cl-tty-kit,cl-log-kit) that this repo had not yet adopted. Content is
scoped to this project rather than copied verbatim -- issue/PR templates
reference this repo's owndocs/src/development.mdand
docs/src/reference/security-considerations.mdrather than sibling pages
that don't exist here.SECURITY.md/CONTRIBUTING.mdare deliberately
not duplicated locally: this repo already relies on the org-wide
nerima-lisp/.githubfallback for those two, same ascl-weavedoes.
Changed
- Raised the
run-tests.lispcoverage ratchet's expression floor,
87.4% -> 87.5%: this session's magic-number/type-duplication dedups
raised actual expression coverage to 87.6% (4167/4759), confirmed by a
freshnix flake checkrun. Branch floor unchanged (81.3%, already
below the current 81.4% raw figure with adequate headroom).
Added
-
+default-close-timeout-seconds+(1.0d0), replacing bare1d0/1.0d0
literals repeated acrossclose-process,call-with-process, and
with-process's:timeoutdefaults -- a distinct tunable from
+default-grace-period-seconds+(a different subsystem, RUN/COMMUNICATE
vs. resource cleanup) that happened to share the same value. Also named
three more bare SIGTERM/SIGKILL literals insideclose-process's own
body. -
src/pty.lisp's+max-terminal-dimension+/terminal-dimensiontype and
+default-pty-read-size+, replacing four repeated(integer 1 65535)
type checks (spawn-pty/pty-resize's rows/cols validation) and two
4096:sizedefaults (pty-read-octets/pty-read-string). -
Named two more bare SIGTERM(
15)/SIGKILL(9) literals:
process-terminate/process-kill's (src/process-group.lisp)
&optionaldefaults now reference+default-timeout-signal+/
+default-kill-signal+directly, andsrc/pty.lisp's
%terminate-and-waitgets its own local+timeout-signal+/
+kill-signal+(same package-boundary reasoning as+poll-interval+). -
Unified four more bare
0.01/0.01d0poll-interval literals
(%wait-until-process-group-gone,process-wait's:poll-interval
default, and two loops insrc/pty.lisp) that duplicated
+default-poll-interval+'s value without referencing it.
src/pty.lisplives in the separateprocess-kit/ptypackage (an
optional, independently loadable subsystem), so it gets its own local
+poll-interval+rather than reaching intoprocess-kit's internals
across that package boundary; the other two now reference
+default-poll-interval+directly. Incidentally fixespty.lisp's two
sites reading as single-float (0.01) where every other poll interval in
the codebase is double-float (0.01d0). -
+default-grace-period-seconds+(1.0d0), replacing bare1.0d0literals
repeated across six&keydefaults inrun,run-command,
run-pipeline,%communicate-base,communicate, and
%async-contract-- the largest single instance of unnamed default-value
duplication found this session. -
+default-timeout-signal+(15, SIGTERM) and+default-kill-signal+(9,
SIGKILL) insrc/parameters.lisp, replacing bare15/9literals
repeated acrossrun,%communicate-base, andcommunicate-async's
three independent&keydefaults. Found by grepping for the literals
directly rather than a tool flag; the same "name it once in
parameters.lisp" treatment the file's other shared defaults already get. -
A new
nix flake checkgate,noForbiddenMarkers, that fails the build if
TODO/FIXME/XXX/adapter/backward-compatever appears insrc,
t,docs, orREADME.md(CHANGELOG.mdis deliberately excluded, since
it legitimately describes fixing or preventing exactly these things --
this very entry included). These have been repeatedly verified absent by
hand across many refactoring passes; this turns that one-off verification
into a standing invariant CI enforces on every push, the same way
run-tests.lisp's coverage ratchet turns a coverage target into a gate
instead of a number checked once and left to drift. -
A
nix flake checkgate,maxFileLength, enforcing that nosrc//t/
file exceeds 500 lines --cl-weave's own stated org guideline, adopted
verbatim (today's largest,communicate.lisp, is 294). -
Standard ASDF
test-opwiring:(asdf:test-system :cl-process-kit)and
(asdf:test-system :cl-process-kit/pty)now work directly, matching
cl-weaveandcl-boundary-kit's:in-order-to ((test-op (test-op "<system>/test")))+:perform (test-op ...)convention (cl-tty-kit
wires the same capability a different way).cl-process-kit.asdhad none
of this on any of its four systems before. -
Docstrings for the primary public entry points that had none:
spawn,
run,run-shell,run-command,run-pipeline,communicate-async,
process-success-p, andpipeline-success-p. Found by checking every
exporteddefun/defmacrofor a docstring programmatically rather than
spot-checking -- these were the most heavily-used public functions in the
library, in contrast tocommunicate,make-command,spawn-native,
run-command-async, andcall-with-process, which already had one.
Content matchesdocs/src/guide/execution.md's existing behavioral
description rather than inventing new claims.
Changed
%start-copierand%start-feeder's (src/copier.lisp) thread bodies
each independently caught anyERRORand recorded it as a
PROCESS-IO-ERRORon their own copier/feeder object, in identical shape
differing only in the object, the stream-name tag, and the work being
wrapped. Extracted the shared shape as a new%run-copier-thread
defmacro(a macro, not a function, since the wrapped work is an
arbitrary body of forms, not a single value) -- found by direct reading,
the same wayspawn's cleanup-pair dedup was.
Fixed
README.md's install example pinnedv1.0.0, one release behind the
actual latest tag (v1.0.1).docs/src/installation.md's "As a flake
input" example was worse: an unpinnedgithub:nerima-lisp/cl-process-kit
reference that tracks whatever is onmain, directly contradicting the
README's own "consumers pin a release tag" guidance right next to it.
Both now pinv1.0.1, and installation.md explains why.
Changed
spawn's (src/spawn.lisp) two failurehandler-caseclauses each
repeated the same "log the failure, clean up whateverRUN-PROGRAM
already started" pair of calls before re-signaling in their own,
different way. Extracted the shared pair into a localabort-spawnflet,
found by reading the function directly rather than a tool flag (its span
is too small forparedit inspect similarity's defaultnode_count
threshold to surface).- Bumped four SHA-pinned GitHub Actions that had real, unclaimed releases
sitting upstream:softprops/action-gh-releasev2.6.2 -> v3.0.2 (Node 24
runtime only; no input/output change),actions/configure-pagesv5 ->
v6.0.0 (Node 24 runtime only),actions/upload-pages-artifactv3 -> v5.0.0
(its one behavior change, excluding dotfiles from the uploaded artifact,
doesn't affect this project'smkdocs buildoutput -- confirmed by
listing the actual built site directory, which contains none), and
actions/deploy-pagesv4 -> v5.0.0 (Node 24 runtime only).actions/ checkout,DeterminateSystems/nix-installer-action,cachix/ cachix-action, andDeterminateSystems/update-flake-lockwere already on
their latest tags. - Deduplicated
t/mutation-test.lisp'sprocess-success-p/
pipeline-success-pcase batteries: each was written out as an identical
literal twice (once for the direct "battery matches the live function"
assertion, once passed to the mutation-kill assertion), found by running
paredit inspect similarityovert/for the first time this session
(score 58.0, the top cross-location match). Named each battery a
top-leveldefparameterso both tests read from the same data and cannot
silently drift apart if a case is later added to only one. - Unified
await-processandnext-process-event's (src/async-task.lisp)
independent "wait on the task's condition variable, checking an optional
deadline first" blocks into a shared%wait-on-taskcontinuation, plus a
%deadline-from-timeouthelper for the relative-timeout-to-absolute-
deadline arithmetic both already repeated verbatim. Found via
paredit inspect similarity, the highest-scoring cross-location match
after filtering same-file parent/child nesting noise. Lowered the
branch-coverage floor again, 81.4% -> 81.3%, for the same reason as below:
removing the duplicated deadline check took 2 more redundant branch points
out, holding the uncovered-branch count at 124; see the comment above
+minimum-branch-coverage+for why 81.4 (not 81.3) was the wrong floor to
land on the first time. - Unified three independent "poll until done or a deadline passes" loops --
%wait-until-terminaland%wait-until-group-gone(src/communicate.lisp)
and%terminate-processes's pipeline-stage shutdown wait
(src/pipeline.lisp) -- into one shared%poll-untilcontinuation-style
helper. Lowered the branch-coverage ratchet floor from 81.5% to 81.4% as a
direct, understood consequence: the dedup removed 4 branch points that were
redundant copies of each other (covered and total both dropped by exactly
4, so the actual count of uncovered branches did not change), not 4 newly
untested ones.
Added
- Three
cl-weave:defmatchercustom matchers overprocess-result/
pipeline-result--:to-have-succeeded,:to-have-timed-out, and
:to-have-been-cancelled-- registered int/package.lispvia a shared
%define-result-state-matchermacro. Tests read
(expect result :to-have-succeeded)rather than
(expect (process-success-p result) :to-be-truthy)wherever the assertion
checks a result's terminal state as a fixed boolean.
Changed
- Bumped
cl-weavev1.0.0 -> v1.1.0,cl-boundary-kitv0.6.0 -> v1.0.0
(both stable releases with no behavior change to the surface this library
calls; verified against each release's ownCHANGELOG.md). - Bumped
cl-tty-kitv0.6.0 -> v1.0.2 and, since v1.0.0, simplified the
flake.nixinput fromgit+https://...?submodules=1back to a plain
github:reference:cl-tty-kitno longer vendorsnerima-lisp/cl-prolog
as a git submodule, andcl-prologis now a regular top-level nerima-lisp
package depended on only bycl-tty-kit/test, which this repository's
cl-process-kit/ptysystem (depending on plaincl-tty-kit) never loads.