diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 510a78f..500b888 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,17 +63,13 @@ jobs: # processes — get genuine coverage on a runner where those facilities # exist. The sandbox has no controlling terminal, so there is nothing for # those tests to attach to. - name: integration tests (${{ matrix.target }}) - runs-on: ${{ matrix.os }} + # One runner, no matrix: aarch64-darwin (macos-14) used to carry this leg + # too, but its runs were unreliable enough under CI load to be worse than + # no coverage. x86_64-linux is what CI gates; aarch64-darwin remains the + # local development target (see flake.nix), just without a CI leg here. + name: integration tests (x86_64-linux) + runs-on: ubuntu-latest timeout-minutes: 45 - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - target: x86_64-linux - - os: macos-14 - target: aarch64-darwin steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/README.md b/README.md index 22ae049..4bdac48 100644 --- a/README.md +++ b/README.md @@ -104,9 +104,8 @@ guide and the [package standard](https://github.com/nerima-lisp/.github/blob/mai ## Support See [SUPPORT](https://github.com/nerima-lisp/.github/blob/main/SUPPORT.md). -Report vulnerabilities privately via the org -[security policy](https://github.com/nerima-lisp/.github/blob/main/SECURITY.md) -rather than a public issue. +Report vulnerabilities privately per this repository's +[security policy](SECURITY.md) rather than a public issue. ## License diff --git a/docs/notes/macro-consolidation-audit.md b/docs/notes/macro-consolidation-audit.md index 4c2ef93..9f0bfe3 100644 --- a/docs/notes/macro-consolidation-audit.md +++ b/docs/notes/macro-consolidation-audit.md @@ -72,7 +72,7 @@ per-site with a full test run, not applied as a blind find/replace. position, and per-slot copier function all vary enough that capturing it would need a small spec DSL for a ~4-5 line-per-site saving across 7 sites — the "three similar-but-different lines" trap this project's own - `docs/cps-audit.md` warns against. Left as raw `defstruct`s, consistent + `docs/notes/cps-audit.md` warns against. Left as raw `defstruct`s, consistent with `docs/notes/value-struct-audit.md`'s stated reason (`define-value-struct` assumes no inheritance). @@ -83,4 +83,4 @@ prior refactoring rounds; this audit's one actionable finding (`ignore-errors` consistency) is applied. What remains unconverted is either better solved without a macro or is a narrow, higher-risk case for a modest win, both consistent with this project's readability-bounded reading of "as much as -possible" (`docs/cps-audit.md`). +possible" (`docs/notes/cps-audit.md`). diff --git a/docs/notes/paredit-usage.md b/docs/notes/paredit-usage.md index d469204..1e1adc5 100644 --- a/docs/notes/paredit-usage.md +++ b/docs/notes/paredit-usage.md @@ -12,7 +12,7 @@ docstring). | command | what it drove | |---|---| -| `inspect unused-definitions src/**/*.lisp t/**/*.lisp` | the dead-code audit (`docs/dead-code-audit.md`): 179 candidates → 18 bulk-removable, each then verified a false positive. Scanning src+t together is what collapsed the 239 src-only candidates. | +| `inspect unused-definitions src/**/*.lisp t/**/*.lisp` | the dead-code audit (`docs/notes/dead-code-audit.md`): 179 candidates → 18 bulk-removable, each then verified a false positive. Scanning src+t together is what collapsed the 239 src-only candidates. | | `inspect duplicates src/**/*.lisp` | found the four byte-identical prefix helpers later consolidated into `nshell.util:string-prefix-p`, and confirmed the top clone shapes were idiomatic `let` bindings that must *not* be macro-ified. | | `inspect duplicates t/unit/*.lisp` | located the same-shape `it`-block clusters that became `it-each` tables (tokenizer word/redirect/command-sub/process-sub, parser diagnostics). | diff --git a/docs/notes/readability-audit.md b/docs/notes/readability-audit.md index 76e1788..c0b303b 100644 --- a/docs/notes/readability-audit.md +++ b/docs/notes/readability-audit.md @@ -23,7 +23,7 @@ A median 5-line, depth-4 function body is the core readability result: the code is built from many small, single-purpose, shallow functions rather than a few large ones. The DDD layering (domain / application / infrastructure / presentation, one concern per file) reinforces this at the -file scale — see `docs/dead-code-audit.md` and the file-split history. +file scale — see `docs/notes/dead-code-audit.md` and the file-split history. ## Deep-nesting outliers, and how they were driven down diff --git a/docs/notes/timeout-audit.md b/docs/notes/timeout-audit.md index 5cb58e1..b37cc17 100644 --- a/docs/notes/timeout-audit.md +++ b/docs/notes/timeout-audit.md @@ -30,7 +30,7 @@ Verified empirically, not just by inspection: `interactive-stream-p` returns `T` for SBCL's real `*standard-output*` under `script(1)` (a genuine PTY) and `NIL` under a plain pipe — confirmed by direct `sbcl --eval` runs before writing the fix. A new end-to-end test, -`e2e-main-interactive-pty-foreground-command-ignores-external-command-timeout` +`e2e-main-interactive-pty-foreground-pipeline-ignores-external-command-timeout` (`t/e2e/test-smoke.lisp`), spawns real `nshell` under a real PTY with `*external-command-timeout*` overridden to `0.5`, runs `sleep 2; echo pty-outlived-timeout`, and asserts the echo occurs and no timeout message diff --git a/docs/notes/value-struct-audit.md b/docs/notes/value-struct-audit.md index 14db0ec..68f38e1 100644 --- a/docs/notes/value-struct-audit.md +++ b/docs/notes/value-struct-audit.md @@ -62,10 +62,10 @@ for **every** raw `defstruct` that remains, with the concrete reason it is not a | `%history-token-window` | `domain/history/last-argument.lisp` | capsule — read-only, but its slots have no public readers (behavior-only API); the macro would leak them | | `%input-edit-snapshot` | `presentation/input-state-undo.lisp` | mutable — writable slot(s): buffer, cursor-pos | | `%input-session-clear` | `presentation/input-state-helpers.lisp` | mutable — writable slot(s): kind, overrides | -| `%kb-command-entry` | `domain/completion/knowledge-base.lisp` | mutable — writable slot(s): description, subcommands, flags, option-values, exclusive-options | | `%mixed-sequence-build-state` | `domain/parsing/parser-assembly.lisp` | mutable — writable slot(s): sequence-commands, sequence-separators, pipe-group | | `%parse-result-facts` | `domain/parsing/parse-result.lisp` | mutable — writable slot(s): ast | | `%parsed-command-line-case-clause` | `domain/parsing/parse-result.lisp` | capsule — read-only, but its slots have no public readers (behavior-only API); the macro would leak them | +| `%path-command-directory-cache-entry` | `domain/completion/filesystem-path-command.lisp` | mutable — writable slot(s): stamp | | `%path-command-query` | `domain/completion/filesystem-path-command.lisp` | capsule — read-only, but its slots have no public readers (behavior-only API); the macro would leak them | | `%redirect-entry` | `domain/parsing/parser-data.lisp` | mutable — writable slot(s): kind, target | | `%redirect-facts` | `domain/parsing/parser-data.lisp` | mutable — writable slot(s): text, kind, fd-dup-p | @@ -98,9 +98,8 @@ for **every** raw `defstruct` that remains, with the concrete reason it is not a | `%tokenizer-left-angle-route` | `domain/parsing/tokenizer-handlers.lisp` | capsule — read-only, but its slots have no public readers (behavior-only API); the macro would leak them | | `%tokenizer-left-paren-route` | `domain/parsing/tokenizer-handlers.lisp` | capsule — read-only, but its slots have no public readers (behavior-only API); the macro would leak them | | `%tokenizer-pipe-route` | `domain/parsing/tokenizer-handlers.lisp` | capsule — read-only, but its slots have no public readers (behavior-only API); the macro would leak them | -| `%tokenizer-right-redirect-route` | `domain/parsing/tokenizer-handlers.lisp` | capsule — read-only, but its slots have no public readers (behavior-only API); the macro would leak them | +| `%tokenizer-right-angle-route` | `domain/parsing/tokenizer-handlers.lisp` | capsule — read-only, but its slots have no public readers (behavior-only API); the macro would leak them | | `%tokenizer-special-dispatch-route` | `domain/parsing/tokenizer-handlers.lisp` | capsule — read-only, but its slots have no public readers (behavior-only API); the macro would leak them | -| `%tokenizer-word-scan-action` | `domain/parsing/tokenizer-readers.lisp` | capsule — read-only, but its slots have no public readers (behavior-only API); the macro would leak them | | `%transient-session-clear` | `presentation/input-state-session.lisp` | mutable — writable slot(s): kind, overrides | | `%type-options` | `application/builtin-type-helpers.lisp` | mutable — writable slot(s): all-p, short-p, no-functions-p, color-p, query-p, path-p, force-path-p, type-p, help-p | | `%undo-recording-step` | `presentation/input-state-undo.lisp` | mutable — writable slot(s): undo-stack, redo-stack | @@ -126,14 +125,16 @@ for **every** raw `defstruct` that remains, with the concrete reason it is not a | `here-doc-target-replacer` | `domain/parsing/parser-here-doc.lisp` | mutable — writable slot(s): bodies, target-pending-p | | `if-node` | `domain/parsing/ast.lisp` | include-hierarchy — `:include ast-node`; the macro cannot generate an inheriting struct | | `incomplete-node` | `domain/parsing/ast.lisp` | include-hierarchy — `:include ast-node`; the macro cannot generate an inheriting struct | -| `input-state` | `presentation/input-state-core.lisp` | mutable — writable slot(s): cursor-pos, completion-index, completion-base-buffer, completion-base-cursor, last-candidates, suggestion, mode, vi-count, vi-visual-anchor, abbreviation-expander, kill-ring, last-yank-start, last-yank-end, last-yank-index, last-argument-start, last-argument-end, last-argument-index, search-query, search-original-buffer, search-original-cursor, search-index, undo-stack, redo-stack, buffer | +| `input-state` | `presentation/input-state-data.lisp` | mutable — writable slot(s): cursor-pos, completion-index, completion-base-buffer, completion-base-cursor, last-candidates, suggestion, mode, vi-count, vi-visual-anchor, abbreviation-expander, kill-ring, last-yank-start, last-yank-end, last-yank-index, last-argument-start, last-argument-end, last-argument-index, search-query, search-original-buffer, search-original-cursor, search-index, undo-stack, redo-stack, buffer, mouse-selection-anchor, mouse-selection-end | | `job` | `domain/execution/job.lisp` | mutable — writable slot(s): state-kw, pgid-int, exit-code-int, pids-list, command-line-str, background-visible-p | | `job-monitor` | `domain/job-control/monitor.lisp` | mutable — writable slot(s): jobs-table, next-id-int | | `job-wait-event` | `application/manage-job.lisp` | mutable — writable slot(s): pid, state, status-code | | `knowledge-base` | `domain/completion/knowledge-base.lisp` | mutable — writable slot(s): commands | | `operator-node` | `domain/parsing/ast.lisp` | include-hierarchy — `:include ast-node`; the macro cannot generate an inheriting struct | | `pipeline-node` | `domain/parsing/ast.lisp` | include-hierarchy — `:include ast-node`; the macro cannot generate an inheriting struct | +| `process-substitution-resource` | `infrastructure/acl/syscall-process-substitution.lisp` | mutable — writable slot(s): path, fd, processes | | `pty-process` | `infrastructure/acl/pty.lisp` | mutable — writable slot(s): pid, pgid, master-fd, stream | +| `redirect-fd-dup-target` | `domain/parsing/parser-data.lisp` | mutable — writable slot(s): source, target, operator | | `redirect-output-destinations` | `domain/parsing/parser-data.lisp` | mutable — writable slot(s): stdout-target, stdout-mode, stderr-target, stderr-mode | | `rule-knowledge-base` | `domain/completion/rule-data.lisp` | mutable — writable slot(s): facts, rules | | `sequence-node` | `domain/parsing/ast.lisp` | include-hierarchy — `:include ast-node`; the macro cannot generate an inheriting struct | @@ -145,7 +146,7 @@ for **every** raw `defstruct` that remains, with the concrete reason it is not a | `while-node` | `domain/parsing/ast.lisp` | include-hierarchy — `:include ast-node`; the macro cannot generate an inheriting struct | | `whitespace-field-scanner` | `domain/expansion/fields.lisp` | mutable — writable slot(s): boundaries, start | -**Total: 137 raw defstructs.** mutable: 93; capsule: 28; include-hierarchy: 13; accessor/type mismatch: 1; encapsulation: 1; helper-collision: 1 +**Total: 138 raw defstructs.** mutable: 95; capsule: 27; include-hierarchy: 13; accessor/type mismatch: 1; encapsulation: 1; helper-collision: 1 `command-history` and `history-entry` (both formerly `domain/history/`) are gone from this count: generic history storage is now `history-kit:history`/`history-kit:history-entry`, diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index 022342f..d1a5d1d 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -68,6 +68,14 @@ and reads batch input from stdin otherwise. With -c/--command, nshell executes COMMAND once in batch mode; trailing ARGS are available as $argv. With SCRIPT, nshell runs the script file; trailing ARGS are available as $argv. + +Options: + -i, --interactive Force the interactive line editor. + --no-config Do not load the interactive startup file. + --config PATH Load PATH instead of ~/.nshellrc. + --no-history Do not read or write interactive history. + -h, --help Show usage and exit. + -V, --version Show version and exit. ``` ## Build from source @@ -84,9 +92,10 @@ nix develop # dev shell with SBCL + cl-weave ``` `flake.nix` declares `x86_64-linux` and `aarch64-darwin`. The full hermetic -flake and release-binary gate runs on `x86_64-linux`; `aarch64-darwin` is the -development and non-sandboxed integration target, and some build checks can be -unavailable there when a pinned upstream package has no Darwin build. +flake gate, the release-binary gate, and the non-sandboxed integration suite +all run in CI on `x86_64-linux` only; `aarch64-darwin` is a local development +target (`nix build`, `nix develop`), and some build checks can be unavailable +there when a pinned upstream package has no Darwin build. Inside `nix develop`, load the system into a REPL: diff --git a/docs/src/project/contributing.md b/docs/src/project/contributing.md index 4d0fcbc..833fe13 100644 --- a/docs/src/project/contributing.md +++ b/docs/src/project/contributing.md @@ -100,8 +100,8 @@ behaviour you want nshell to own. ## Reporting a vulnerability -Report privately through the org -[security policy](https://github.com/nerima-lisp/.github/blob/main/SECURITY.md), +Report privately through this repository's +[security policy](https://github.com/nerima-lisp/nshell/blob/main/SECURITY.md), never as a public issue. Redact environment values and command history unless they are required to reproduce. diff --git a/docs/src/project/public-readiness.md b/docs/src/project/public-readiness.md index a9cc582..cd99830 100644 --- a/docs/src/project/public-readiness.md +++ b/docs/src/project/public-readiness.md @@ -29,12 +29,11 @@ and scripting subset. Before a public release can claim world-level interactive-shell quality: 1. `nix flake check --print-build-logs` passes on `x86_64-linux` CI, the - hermetic check target. `aarch64-darwin` remains declared for development and - the non-sandboxed integration matrix, but is not a flake-check or - release-binary CI target. + hermetic check target. `aarch64-darwin` remains declared for development, + but is not a flake-check, integration, or release-binary CI target. 2. The non-sandboxed integration suite passes for PTY, subprocess, terminal, - signal, and job-control coverage on every target in the CI matrix: - `x86_64-linux` and `aarch64-darwin`. + signal, and job-control coverage on the sole CI matrix target, + `x86_64-linux`. 3. A release binary is built on `x86_64-linux`, starts successfully, and ships with `README.md`, `LICENSE`, and the `nshell(1)` man page. 4. User-visible behavior changes are represented in README, man page, the diff --git a/docs/src/reference/builtins.md b/docs/src/reference/builtins.md index f8a603e..f6fca45 100644 --- a/docs/src/reference/builtins.md +++ b/docs/src/reference/builtins.md @@ -3,10 +3,11 @@ Run `help` inside nshell for per-command detail, and `type NAME` to check whether a name resolves to a builtin, a function, or an external program. -`alias`, `abbr`, `bg`, `cd`, `complete`, `contains`, `count`, `disown`, `echo`, -`exec`, `exit`, `export`, `false`, `fg`, `function`, `help`, `history`, `jobs`, -`ls`, `not`, `pipeline-graph`, `printf`, `pwd`, `read`, `seq`, `set`, `source`, -`string`, `test`, `true`, `type`, `unset`, `which`. +`alias`, `abbr`, `bg`, `break`, `cd`, `command`, `complete`, `contains`, +`continue`, `count`, `disown`, `echo`, `eval`, `exec`, `exit`, `export`, +`false`, `fg`, `function`, `help`, `history`, `jobs`, `kill`, `ls`, `not`, +`pipeline-graph`, `printf`, `pwd`, `read`, `seq`, `set`, `source`, `string`, +`test`, `true`, `type`, `unset`, `wait`, `which`. ## Notes on a few of them diff --git a/man/nshell.1 b/man/nshell.1 index 4b47520..61c9143 100644 --- a/man/nshell.1 +++ b/man/nshell.1 @@ -5,6 +5,11 @@ nshell \- a modern, fish-inspired interactive shell written in Common Lisp .B nshell .RB [ \-\-help ] .RB [ \-\-version ] +.RB [ \-i | \-\-interactive ] +.RB [ \-\-no\-config ] +.RB [ \-\-config +.IR PATH ] +.RB [ \-\-no\-history ] .RB [ \-c .IR COMMAND .RI [ ARGS .\|.\|.]] @@ -48,6 +53,22 @@ once in batch mode and exit with its status. Trailing .I ARGS are exposed as .BR $argv . +.TP +.BR \-i ", " \-\-interactive +Force the interactive line editor, even when standard input is not a +terminal. +.TP +.B \-\-no\-config +Do not load the interactive startup file. +.TP +.BI \-\-config " PATH" +Load +.I PATH +instead of +.BR ~/.nshellrc . +.TP +.B \-\-no\-history +Do not read or write interactive history. .SH KEY BINDINGS By default the line editor uses Emacs-style key bindings, including cursor motion (\fBC\-a\fR, \fBC\-e\fR, \fBM\-f\fR, \fBM\-b\fR), editing (\fBC\-k\fR, @@ -103,12 +124,16 @@ provides the following builtins: .BR alias , .BR abbr , .BR bg , +.BR break , .BR cd , +.BR command , .BR complete , .BR contains , +.BR continue , .BR count , .BR disown , .BR echo , +.BR eval , .BR exec , .BR exit , .BR export , @@ -118,8 +143,11 @@ provides the following builtins: .BR help , .BR history , .BR jobs , +.BR kill , .BR ls , .BR not , +.BR pipeline\-graph , +.BR printf , .BR pwd , .BR read , .BR seq , @@ -129,6 +157,8 @@ provides the following builtins: .BR test , .BR true , .BR type , +.BR unset , +.BR wait , .BR which . Run .B help