Skip to content

v2.0.0

Choose a tag to compare

@github-actions github-actions released this 30 Jul 16:48
· 30 commits to main since this release
v2.0.0
140e8b7

Added

  • process-kit/pty:pty-try-wait/pty-alive-p: non-blocking liveness
    checks for a PTY session, the analogues of process-try-wait/
    process-alive-p. Found the same way as with-pty-process two commits
    earlier -- comparing the PTY subsystem's public surface against the
    main library's and finding pty-wait (blocking, possibly escalating)
    had no non-blocking counterpart. pty-wait's own polling loop now
    calls the public pty-try-wait instead of the internal %try-wait
    directly, matching process-wait's existing call-the-public-function
    convention in process-handle.lisp.

  • process-kit/pty:with-pty-process/call-with-pty-process: the PTY
    analogue of with-process/call-with-process's continuation-passing
    cleanup contract, binding a variable to a spawn-pty result for a body
    and closing it via pty-close on the way out, success or error. The
    main library had this idiom for process-handle; the PTY subsystem
    never got the equivalent, so all 6 of t/pty-test.lisp's cases
    hand-rolled the identical unwind-protect/pty-close shape. Now they
    read as (with-pty-process (process (spawn-pty ...)) ...).

  • cl-weave:it-fuzz property (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.lisp already covered into a genuine property of
    the full byte space. First it-fuzz use in this codebase; it-property,
    it-each, and run-mutations were already adopted in earlier passes.

  • checks.maxLineLength in flake.nix: fails nix flake check if any
    src//t/ line exceeds 100 columns (the org's CODING_STANDARD.md
    rule), matching the existing checks.maxFileLength gate'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 to src/
    only (its own message says so), and t/ 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 between it cases -- that using it wholesale
    would have been a large, unrelated stylistic churn; used only on the two
    files, t/pty-test.lisp and t/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 :overflow event" block (src/async-events.lisp)
    into a shared %flush-pending-drops-event, via paredit 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-until primitive that communicate.lisp/pipeline.lisp already
    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-match paredit inspect duplicates tool
    cannot see it as one shape. %poll-until itself is now defined in
    process-handle.lisp (the earliest-loading file among its callers, so
    both process-group.lisp and communicate.lisp can reach it) and
    generalized to accept a NIL deadline for an unbounded wait:

    • process-handle.lisp's process-wait now delegates to %poll-until
      instead of hand-rolling the same loop; a genuine (not just cosmetic)
      fix falls out of this, since %poll-until caps its final sleep at
      whatever time is actually left before the deadline, where the old loop
      always slept the full poll-interval regardless -- process-wait
      could previously overshoot its deadline by up to poll-interval
      before ever re-checking.
    • process-group.lisp's %wait-until-process-group-gone now delegates
      to the same %poll-until instead of a hand-rolled copy of exactly the
      donep communicate.lisp's own %wait-until-group-gone already 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's pty-wait deliberately keeps its own copy (a
      genuinely different, three-way donep with no clock/sleeper injection,
      not an oversight).
      Verified via two full nix flake check runs (the first hit the
      documented SETUP-SERVE-EVENT-PIPE flake on await-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-spawning loop/lambda block
    (src/pipeline.lisp) into a named spawn-stage-threads sibling inside
    the same labels form as the pre-existing await-pipeline-stages.
    Found via a fresh size-ranked scan of every src/*.lisp function
    (paredit inspect outline --output json): run-pipeline was 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 via paredit refactor extract-function --infer-params,
    since that flag mishandles loop's clause-keyword syntax (for/in/
    collect read 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-wide TEST_STANDARD.md requires 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 own timeout 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 specific CL-WEAVE:TEST-TIMEOUT diagnostic.
    Reformatted run-pty-tests.lisp from a single unreadable one-line form
    to match run-tests.lisp's style while making this change.

Changed

  • flake.nix's .asd :version extraction uses cl-nix-forge's dedicated
    fromAsdSystem lexer instead of a hand-rolled line-by-line regex.
    Strictly stronger, not just shorter: cl-process-kit.asd declares four
    systems sharing one version (cl-process-kit, /test, /pty,
    /pty-test), and the old regex read whichever :version line happened
    to come first with no cross-check, while fromAsdSystem fails the build
    loudly if any of them ever disagreed.
  • flake.nix's packages.*/checks.checkout-tests/checks.pty-tests
    now build on cl-nix-forge's lispDerivation/mkScriptCheck primitives
    (new flake input, pinned v0.4.0) instead of pkgs.sbcl.buildASDFSystem
    plus hand-rolled pkgs.runCommand test derivations. noForbiddenMarkers,
    maxFileLength, formatting, and docs are 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.lisp reads
    CL_PROCESS_KIT_PTY_LIBRARY explicitly at load time (an SB-ALIEN LOAD-SHARED-OBJECT call needs a real path, not a bare SONAME resolved
    off nativeLibraries' LD_LIBRARY_PATH/DYLD_LIBRARY_PATH propagation
    alone), so that env var is still set directly alongside nativeLibraries;
    and native/spawn.c's trampoline binary is compiled twice on purpose --
    once into $out/bin for the distributed package (postInstall), once
    into the build sandbox for checks.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/devShells stay on
    the original hand-rolled CL_SOURCE_REGISTRY string, not
    cl-nix-forge:lispScript -- this migration's own research did not verify
    that primitive's exact parameter shape the way lispDerivation/
    mkScriptCheck were 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 with nix build on each new package/check individually before
    the full nix 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-event now returns a single process-event-step struct
    (process-event-step-event/-cursor/-status/-gap-count) instead of
    four raw values. The org-wide API_STANDARD.md names 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
    wanted gap-count had to destructure all four positionally. Requires a
    major version bump at the next release per CODING_STANDARD.md's
    versioning policy ("関数の必須引数の変更...既定の挙動の変更" -- a change to a
    function's return contract).
  • Lowered the run-tests.lisp expression coverage ratchet floor,
    87.5% -> 87.4%, to absorb process-event-step's compile-time-only
    defstruct form (raw 4168/4764 = 87.4895%, every runtime arm still
    tested). Branch floor unchanged: removing the struct's initial per-slot
    :type declarations, 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, and run/checked.
    The org-wide API_STANDARD.md measures 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 own docs/src/development.md and
    docs/src/reference/security-considerations.md rather than sibling pages
    that don't exist here. SECURITY.md/CONTRIBUTING.md are deliberately
    not duplicated locally: this repo already relies on the org-wide
    nerima-lisp/.github fallback for those two, same as cl-weave does.

Changed

  • Raised the run-tests.lisp coverage 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
    fresh nix flake check run. Branch floor unchanged (81.3%, already
    below the current 81.4% raw figure with adequate headroom).

Added

  • +default-close-timeout-seconds+ (1.0d0), replacing bare 1d0/1.0d0
    literals repeated across close-process, call-with-process, and
    with-process's :timeout defaults -- 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 inside close-process's own
    body.

  • src/pty.lisp's +max-terminal-dimension+/terminal-dimension type and
    +default-pty-read-size+, replacing four repeated (integer 1 65535)
    type checks (spawn-pty/pty-resize's rows/cols validation) and two
    4096 :size defaults (pty-read-octets/pty-read-string).

  • Named two more bare SIGTERM(15)/SIGKILL(9) literals:
    process-terminate/process-kill's (src/process-group.lisp)
    &optional defaults now reference +default-timeout-signal+/
    +default-kill-signal+ directly, and src/pty.lisp's
    %terminate-and-wait gets its own local +timeout-signal+/
    +kill-signal+ (same package-boundary reasoning as +poll-interval+).

  • Unified four more bare 0.01/0.01d0 poll-interval literals
    (%wait-until-process-group-gone, process-wait's :poll-interval
    default, and two loops in src/pty.lisp) that duplicated
    +default-poll-interval+'s value without referencing it.
    src/pty.lisp lives in the separate process-kit/pty package (an
    optional, independently loadable subsystem), so it gets its own local
    +poll-interval+ rather than reaching into process-kit's internals
    across that package boundary; the other two now reference
    +default-poll-interval+ directly. Incidentally fixes pty.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 bare 1.0d0 literals
    repeated across six &key defaults in run, 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) in src/parameters.lisp, replacing bare 15/9 literals
    repeated across run, %communicate-base, and communicate-async's
    three independent &key defaults. 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 check gate, noForbiddenMarkers, that fails the build if
    TODO/FIXME/XXX/adapter/backward-compat ever appears in src,
    t, docs, or README.md (CHANGELOG.md is 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 check gate, maxFileLength, enforcing that no src//t/
    file exceeds 500 lines -- cl-weave's own stated org guideline, adopted
    verbatim (today's largest, communicate.lisp, is 294).

  • Standard ASDF test-op wiring: (asdf:test-system :cl-process-kit) and
    (asdf:test-system :cl-process-kit/pty) now work directly, matching
    cl-weave and cl-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.asd had 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, and pipeline-success-p. Found by checking every
    exported defun/defmacro for a docstring programmatically rather than
    spot-checking -- these were the most heavily-used public functions in the
    library, in contrast to communicate, make-command, spawn-native,
    run-command-async, and call-with-process, which already had one.
    Content matches docs/src/guide/execution.md's existing behavioral
    description rather than inventing new claims.

Changed

  • %start-copier and %start-feeder's (src/copier.lisp) thread bodies
    each independently caught any ERROR and recorded it as a
    PROCESS-IO-ERROR on 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 way spawn's cleanup-pair dedup was.

Fixed

  • README.md's install example pinned v1.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 unpinned github:nerima-lisp/cl-process-kit
    reference that tracks whatever is on main, directly contradicting the
    README's own "consumers pin a release tag" guidance right next to it.
    Both now pin v1.0.1, and installation.md explains why.

Changed

  • spawn's (src/spawn.lisp) two failure handler-case clauses each
    repeated the same "log the failure, clean up whatever RUN-PROGRAM
    already started" pair of calls before re-signaling in their own,
    different way. Extracted the shared pair into a local abort-spawn flet,
    found by reading the function directly rather than a tool flag (its span
    is too small for paredit inspect similarity's default node_count
    threshold to surface).
  • Bumped four SHA-pinned GitHub Actions that had real, unclaimed releases
    sitting upstream: softprops/action-gh-release v2.6.2 -> v3.0.2 (Node 24
    runtime only; no input/output change), actions/configure-pages v5 ->
    v6.0.0 (Node 24 runtime only), actions/upload-pages-artifact v3 -> v5.0.0
    (its one behavior change, excluding dotfiles from the uploaded artifact,
    doesn't affect this project's mkdocs build output -- confirmed by
    listing the actual built site directory, which contains none), and
    actions/deploy-pages v4 -> v5.0.0 (Node 24 runtime only). actions/ checkout, DeterminateSystems/nix-installer-action, cachix/ cachix-action, and DeterminateSystems/update-flake-lock were already on
    their latest tags.
  • Deduplicated t/mutation-test.lisp's process-success-p/
    pipeline-success-p case 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 similarity over t/ for the first time this session
    (score 58.0, the top cross-location match). Named each battery a
    top-level defparameter so both tests read from the same data and cannot
    silently drift apart if a case is later added to only one.
  • Unified await-process and next-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-task continuation, plus a
    %deadline-from-timeout helper 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-terminal and %wait-until-group-gone (src/communicate.lisp)
    and %terminate-processes's pipeline-stage shutdown wait
    (src/pipeline.lisp) -- into one shared %poll-until continuation-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:defmatcher custom matchers over process-result/
    pipeline-result -- :to-have-succeeded, :to-have-timed-out, and
    :to-have-been-cancelled -- registered in t/package.lisp via a shared
    %define-result-state-matcher macro. 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-weave v1.0.0 -> v1.1.0, cl-boundary-kit v0.6.0 -> v1.0.0
    (both stable releases with no behavior change to the surface this library
    calls; verified against each release's own CHANGELOG.md).
  • Bumped cl-tty-kit v0.6.0 -> v1.0.2 and, since v1.0.0, simplified the
    flake.nix input from git+https://...?submodules=1 back to a plain
    github: reference: cl-tty-kit no longer vendors nerima-lisp/cl-prolog
    as a git submodule, and cl-prolog is now a regular top-level nerima-lisp
    package depended on only by cl-tty-kit/test, which this repository's
    cl-process-kit/pty system (depending on plain cl-tty-kit) never loads.