更新日志
聚合自各仓库 CHANGELOG · 拉取于 2026-10-19
-
niubash Unreleased 未发布 日期未知
Added
- `plugins::loader`: the generated-loader module — document format (banner, superseded-marker record, per-source sections byte-identical to the old block bodies), structural validation, atomic publish, the startup decision (opt-out switch → double-execution scan → load), the legacy-block scanner behind `migrate-rc`/doctor, and the one-time hint marker. Gate cases 359.4–359.9 and the 359.12 structure ratchet are pinned in `tests/rc_readonly_loader.rs`; the retired writers stay in `assets.rs` as an unwired rollback zone (design §4.3) with the ratchet asserting they are reachable from no verb.
- Shell-face startup is a measured contract (`scripts/perf/shell-face-startup.py`):
- execution-level `-c exit` timing for every installed face — the `niu.exe`
- payload, the `winuxcmd\bin` / `winuxcmd\usr\bin` `bash.exe`/`sh.exe` links,
- and the `C:\Program Files\Git\bin\bash.exe` convention slot — with a
- verify-before-record rule (a face that cannot execute prints the failure
- note, never a fast number), structural annotations (size, hardlink count,
- same-file-as-payload), a `Measure-Command` cross-check mode, and a
- deterministic `--check-links` gate: every declared face must be the same
- file as the payload `niu.exe`. A measured-time-ratio gate was tried first
- and rejected with data — shimmed faces measured 1.04–1.46x the payload
- while healthy relinked faces measured 0.91–1.26x, overlapping intervals,
- so no ratio separates the states without false kills (niubash#377 CR).
- Measured on the 1.4.0
- install (faces still the 243,200 B rubash forwarders): payload
- `niu -c exit` 27.2 ms avg, faces 42.1–47.3 ms (+9.8 to +15.0 ms, one extra
- process), the .NET slot 80.6 ms (+48 ms, two extra processes); after
- `--relink-shells --force` on the same engine the faces measure equal to the
- payload within noise (23.8–29.7 ms, same inode) with `$0`, `BASH_VERSION`,
- POSIX mode and the `NIU_SHELL` contract verified through the links.
- The installer sweeps retired shim shapes out of the PATH entry directory
- (niubash#376 hygiene): `{app}\winuxcmd\bin` and `{app}\winuxcmd\usr\bin` are
- cleared of `*.bak-*` / `*.old-*` / `*.niu-old-*` on install and uninstall —
- the same dead-weight class as the existing `niu.exe.old-*` sweep, observed
- live as `bash.exe.bak-1.4.0` / `sh.exe.old-shposix` (4 × 226,816 B on real
- machines; `*.niu-old-*` covers a relinker crash between park and drop).
- Live entry names are never matched. The PATH entry directory contract
- (hardlinks only, `usr\bin` off `PATH`, declared entry set) is now documented
- in `docs/src/installer.md`.
- The factory prompt's git segment runs the user's own git instead of the
- in-process libgit2 dependency: one bounded
- `git status --porcelain=v2 --branch` inside a repository, and no spawn at
- all outside one (a few `stat`s, where `__git_ps1` forks anyway). Fidelity
- is the reason to switch — sparse checkouts, linked worktrees, submodules and
- `safe.directory` refusals are answered by the git the user actually runs
- (libgit2 silently dropped the branch segment on old-owner trees once) — and
- the cost is unchanged in practice: measured p50 26 ms / max 37 ms per prompt
- inside a repository, i.e. the bare git call and nothing more, against Git
- Bash's `__git_ps1` 100-300 ms (three to five forks). Isolation rules, one
- per incident: never through a shell (a missing git is an OS error, not
- `command not found` noise), stdout captured and stderr discarded (git
- warnings never reach the screen), `--no-optional-locks` +
- `GIT_OPTIONAL_LOCKS=0` (never takes `index.lock`), `-c core.fsmonitor=false`
- (never starts an fsmonitor daemon from a prompt), a 2 s timeout with the
- child killed, and no `CREATE_NO_WINDOW` (measured +28 ms per call: it makes
- Windows allocate a hidden console for every prompt; a console child inherits
- ours and shows no window). This also drops the `git2`/libgit2 dependency:
- ~1.28 MB of the binary and 323 locked packages (libgit2-sys, url/idna/ICU,
- cc, pkg-config, vcpkg, ...). Both niubash#145 startup invariants still pass
- (`legacy_niu_plugins_rc_reaches_prompt`,
- `legacy_framework_rc_reaches_prompt`): nothing is waited on before the first
- paint, and the prompt-scoped child is reaped by the prompt itself. A
- brand-new repository (unborn HEAD) now shows its branch, where libgit2's
- `head()` errored and the segment vanished.
Changed
- The user rc is read-only to plugin management — the generated loader replaces the managed block (niubash#359 case A, 1.4.2). The control plane (`~/.niubash/plugins.toml` + `~/.niubash/sources/registry.toml`) now drives a derived activation plane: `niu plugin` verbs render and publish `~/.niubash/generated/plugins.niu` (deterministic, byte-stable when the manifest is unchanged, validate-then-atomic-publish so a failed generation never replaces the last good one and a concurrent reader never sees a missing file — the #304 invariant moves to the loader). The host sources that loader BEFORE the us
- Double-execution guard for the migration window: the loader generation records the managed-block marker pairs it supersedes; at startup the host scans the rc read-only and keeps the loader OFF while any superseded block still executes (rather than run the plugin set twice), with a one-time hint naming `niu plugin migrate-rc`. Removing the old block by hand — the only removal there has ever been — turns the loader on. `niu doctor` reports the transition state read-only.
- New verb `niu plugin migrate-rc [--diff]`: the read-only migration diagnostic. It scans the rc for legacy managed blocks (exact and hand-edited marker shapes, with line ranges and a begin-without-end malformed state reported, never fatal), publishes the candidate loader generation from the current manifest, records the superseded markers, and with `--diff` writes the suggested post-migration rc under `~/.niubash/candidates/`. It never modifies the rc — the removal is always the user's own edit.
- Rollback switch: `NIU_PLUGIN_LOADER=off` (`off`/`false`/`0`/`no`) stops the host from loading the generated loader — plugins return to manual-source mode per the documented guidance. The switch only disables loading; it never writes anything, and unsetting it rolls forward.
- Control-plane writers are atomic (design §4.4): `save_spec` (`plugins.toml`) and `write_source_registry` (`sources/registry.toml`) swap whole files through a same-dir temp + replace rename, so a concurrent reader never observes a truncate window.
- Windows path contract: the bundled WinuxCmd coreutils own the shell tree —
- they receive the shell's POSIX spellings verbatim and resolve them in-child —
- while every other child, native or a foreign POSIX runtime such as MSYS2 or
- Cygwin, always receives native Windows paths, with no per-child probe and no
- `MSYS_NO_PATHCONV`-style switch (unixwin/niubash#346). This rides the
- WinuxCmd shell-tree layer, so the bundled WinuxCmd pin must be bumped in the
- same release.
- Windows releases bundle niu-git 2.55.0.5 (`scripts/release/preinstall.json`).
- niu-git moves to `layout: flat`: the payload lands under `opt\niugit` and
- `mingw64\bin\git.exe` is mapped straight onto `usr\bin\git.exe`, so the `git`
- on PATH *is* the git binary — no winuxcmd dispatch hop, no launcher, no MSYS
- runtime and no perl in the process tree. `git --version` drops from 72–83 ms
- to ~14 ms (Git Bash: 45–103 ms) and `git status` to ~23 ms. Needs WinuxCmd
- 1.1.9 or newer (the POSIX-unlink hardlink break, unixwin/WinuxCmd#1183);
- the release pin already carries 1.1.10.
Fixed
- A DSR-silent terminal no longer kills the shell, and TAB completion
- commits (unixwin/niubash#373, the #355 follow-up). Interactive startup
- used to anchor the prompt with a `ESC[6n` cursor-position query and ABORT
- the line editor when it timed out: on a terminal that never answers
- (dumb terminals, `script(1)` recordings, some serial/tmux links) every
- startup paid a ~2.6 s stall, printed
- "niubash: line editor error: The cursor position could not be read within
- a normal duration", and exited before painting any prompt. The editor
- (reedline 0.50 -> 0.52) now treats the query as advisory — the prompt
- paints without a reply and later cycles stay interactive; Windows/ConPTY
- is unaffected either way (conhost withholds the client at session attach
- until the terminal answers, a terminal-side contract below niu, pinned by
- the new `tests/issue373.rs` harness). File completion now commits
- like command completion: `cat uniqfi<TAB>` splices the completed word
- into the line (the unique match commits outright, the ambiguous set
- commits its common prefix and still shows the grid) instead of leaving
- the suffix as a menu preview that Enter executed unexpanded — and Enter
- submits the line as typed (GNU accept-line), dismissing the grid, where
- reedline 0.52's menu would have consumed the first Enter as an
- acceptance. The dismissal rides only the completion TAB window: a
- Ctrl+R history search's accepting Enter stays the bare event reedline's
- search handler implements (a whole `Multiple` batch is dropped there —
- unconditionally wrapping it turned the search's Enter into a dead key). Measured TTP on the ConPTY harness is unchanged (niu 3.14 s
- vs GNU bash 3.26 s median, transport-floor dominated; internal startup
- ~70-140 ms — `scripts/perf/ttp-bench.py`).
- dirname/basename: slash-shaped operands stay verbatim — `dirname /a/b`
- reports `/a` instead of a drive spelling such as `A:`; the slash-drive
- argument mask now covers the filesystem-blind string tools
- (unixwin/niubash#346).
-
WinuxCmd Unreleased 未发布 日期未知
Added
- native path: `/mnt/X/...` operands resolve like `/X/...` and `X:\...` -
- the WSL interop spelling (#1172). WSL mounts the Windows drives under
- `/mnt`, and that spelling reached argv verbatim whenever a tool was invoked
- directly (PowerShell, cmd, a scheduler, an agent) rather than through
- rubash, whose argv funnel already pre-translates the form for every child
- (rubash#444, rubash#592). `normalize_api_operand_w` now folds `/mnt/X` to
- the MSYS `/X` spelling before the drive branch, so a single drive-letter
- arm keeps owning every POSIX drive form. The accepted shape matches
- rubash's `windows_mnt_drive_argument` byte-for-byte: a lowercase `mnt`
- literal, a drive letter of either case, exactly `/mnt/X` or `/mnt/X/...`.
- Bare `/mnt` (the WSL root) and non-drive children (`/mnt/data`,
- `/mnt/cfoo`, `/mnt/123`) stay verbatim, `/mnt/cygdrive/d` cannot fold
- twice, and the existing verbatim classes (`/cygdrive/*`, `/dev/*`, bare
- `/`, `/X...` switches) are untouched. Every tool on the shared operand
- boundary (ls, du, sed, cp, ...) picks the form up at once; under a
- configured shell root the resolution is unchanged, since the niubash#346
- shell-ns arm already mapped this exact shape.
-
rubash Unreleased 未发布 日期未知
新增
- 脚本级函数级性能剖析器(#815):`niu --profile <script> [--profile-output <base>]`——解释器确定性插桩(非采样),在函数入口/出口(`execute_cmd.c:5200 execute_function` 家族)、简单命令分发点(`execute_cmd.c:624`,试探帧由真实分发自分类:函数/外部派发取消、其余确认为内建——无第二准入表)、外部 spawn 面、`( )` 子壳与 `$()` 命令替换独立栈帧(subst.c:7143)挂 Drop 守卫;return/exit/展开失败等中止形态经栈展开正确归因。运行时为 thread_local 调用树(self=elapsed−children,唯一 collapsed 路径聚合 count/self/total),归因三分离(内建/外部 spawn/子壳,self-time 切分面总和=100%)。输出文本调用树(self/children/calls 排序)+ collapsed-stack(flamegraph/speedscope 可渲染);报告只写文件(默认进程 temp 目录,绝不 stdout/stderr)——profile 开启时 stdout/stderr/rc 与关闭逐字节一致。关闭路径开销=每缝一次 relaxed AtomicBool lo
性能
- 字面单命令跳过词展开重建(#607,PR #665):每个简单命令在 `expand_command_words` 里付一次全量 `CommandNode` 重建(~1.34µs/次),裸 `:` 的展开恒为 identity。GNU 从不重建(`execute_cmd.c:4468` 跑存储的 SIMPLE_COM;`subst.c:13219` 无触发器文本无事可做)。白名单准入(单字面词、全部结构/重定向/赋值通道为空,`[`/`[[`/分发动词除外),共享 `preserves_word_metadata` 谓词(提取复用、永不复制),契约测试逐词锁定 fast==pipeline。`:` 循环 2.03x→1.77x GNU(~0.74µs/iter),`true` 1.86x→1.67x;`((1))` 路径按设计不动。门禁与 niubash 产品 A/B 零差异。
- 大小写改写去分配+首字符短路(#608,PR #666):`apply_parameter_case_mod` 曾对每个字符跑一次 glob matcher 并 `to_string()` 堆分配;First 算子(`^ , ~`)按 GNU `subst.c case_transform` 只测首字符,空 pattern 即 `?`。First 算子现只测/改首字符后原文追加(栈缓冲零分配),空 `~~` 走直接翻转,主循环改栈缓冲。`${s^}` 500ch x20000 ~1.42s→~0.24s(~6x),`${s~~}` ~10%。33 例 GNU 差分电池 33/33;门禁与产品 A/B 零差异。
- 标量赋值快 RHS 切片一(#609,PR #672):`t=$S` 循环 2.72x。`literal`/`$name`/`"$name"` 三形走白名单快路(字面按构造输出相等直接返回,dollar 拷贝一次借读 cell);dynamics、`set -u`、非名、IFS 标记值走通用路,绑定仍经 `apply_shell_assignment`。100k `t=$S` 1096ms→927ms(1.18x)。30 例差分电池 30/30;门禁与产品 A/B 零差异。
- 标量绑定去双存储漫步切片二(#609,PR #678):`bind_marks_free_scalar`——一次 `attr_of` 探针放行无标记形,同值跳过双写,否则原地双写(镜像不变式成立);只读照常诊断(`variables.c:3258`)。net assign 2.03µs→1.33µs(−35%),GNU 1.25µs。电池 30/30;门禁与产品 A/B 零差异。
- 算术循环机器(#607 切片三,PR #679):body 借指针重走(无逐 entry `Ast` 克隆/reader 包装),DEBUG 探针门控+体后重探,空表 reap 跳过。N=200k `:` 循环 −3.4%。6 契约测试;上游脚本逐字节一致;门禁与产品 A/B 零差异。
- if 条件分发残留(#661,PR #673):空别名表时 raw 首词即展开首词,跳过逐命令 `expand_aliases`;单裸 `:`/`true` 条件跑无标记路径(影子函数/别名回退保 `set -e` 语义,附 4 回归测试)。50 命令 else 体 −26%;`ifcase` 55/55、`casearm` 29/30(仅预存 #17);门禁零差异(niu 50/49=已知负载抖动,隔离绿)。
- case 静态臂零分配(#662,PR #669):`expand_case_pattern` 逐臂全管线+~5 String 层。默认拒绝分类器(`~` 经 GNU 探针确认为展开,掉队)命中则 `Cow::Borrowed`。每臂斜率 ~2.5µs→~1µs;`casearm` 29/30(仅预存 #17)、`ifcase` 55/55;门禁与产品 A/B 零差异。
- mapfile 批量拆分+存储(#663,PR #670):100k 行 243ms vs GNU 52ms。拆分一次解码+字节域切分(`-t` 字节域剥离),无 `-O`/`-C` 时批量直写 dense 存储,跳过中间 index map。243ms→174ms(4.7x→3.3x)。26 探针矩阵 25/26(1 预存 NUL 分歧,新旧一致);门禁与产品 A/B 零差异。剩余存储侧是后片。
- 裸 `` 全局排序(#664,PR #674):globstar 漫步深度优先,GNU `sh_sortglob` 全局重排。单 `` 调用既有 `globsort_matches` 全局重排(C/POSIX 字节序 else live collation)。repro 与 GNU 逐字节一致;glob 电池 33/34→34/34(关 glob-12 观察);门禁与产品 A/B 零差异。纯正确性片。
- 数组元素 quote 单分配快路(#680 切片一,PR #686):`quote_array_value` 逐元素 2 marker 替换+解码扫描+`ansic_shouldquote`+4 链式替换。纯可打印 ASCII(`sh_double_quote` 转义集之外)直接渲染 `"value"` 单分配。seq100k mapfile ~181ms→~99ms(~1.8x)。矩阵 25/26(预存 NUL 分歧);门禁与产品 A/B 零差异。
- mapfile 跳过整输入编解码往返(#680 切片二,PR #688):ASCII 无 carrier 字节输入解码/重编码皆恒等,直接切分(每段单分配)。seq1M −6%。4 契约测试(含 carrier 回退钉死 GNU 输出);门禁与产品 A/B 零差异。
- glob 排序一次 locale 解析(#681 切片 b,PR #689):逐比较 3 env 查询提到逐排序一次;C 下字节序、非 C 走原比较器(结果恒等)。`LC_ALL=C` 300 文件循环 −22%,默认 locale 不变。契约测试;双 locale 电池 34/34;门禁与产品 A/B 零差异。
- `export`/`readonly` 补上越界负下标 abort(#635 后续,PR #677):`export -a a=( [-2]=q )` 曾打印 AFTER rc 0。`-a/-A` 委托内嵌 `declare_builtin`(`setattr.def:240-268`),`arrayfunc.c:804-809` any_failed + DISCARD(`subst.c:11169`)。复用 #658 原语:`oob_word` 上浮、操作数循环遇 OOB 即停、Executor 层裸诊断+exit 1+#637 abort。13 例新测试;18 例 GNU 探针矩阵 18/18;门禁与产品 A/B 零差异。
- 算术票据快路(#607 切片二,PR #671):identity 字母 `(( ))` 仍付全票据。双门(语法形只读+状态门:写日志空、标识符 unset/纯十进制)跳过 shopt/nounset/标记/上下文/同步。dense 探针 ~5%(~200ns/eval),headline 不变。6 契约测试;门禁与产品 A/B 零差异。
- for-list 绑定补 dequote(#675,PR #683):`for p in "f*"` 绑了 `f`+CTLESC+`*`(长 3,全局 glob 死),只有双引号 for-list 路跳过解引用。绑定点统一走存储侧 dequote(`subst.c:4807`)。`casearm` 29/30→30/30,`ifcase` 55/55;门禁与产品 A/B 零差异。根因在 case 上游。
- quoted 复合元素保留引用态(#377,PR #730):`declare -a d3=("$x")` 存了 glob 结果。NoMatch 臂推解引用重包形式,消费端 glob 解引用文本丢引用态。现引号内序列化 CTLESC 保护;`=` 臂解保护判下标。文件 10/12→11/12(剩预存);glob 34/34;门禁与产品 A/B 零差异。
- fd_redirects 并行竞态(#667,PR #676):6 测试并行红、串行绿。`external_fd_copy_bin_dir` 返回固定路径,多测试互删助手目录。现 pid+ThreadId+计数器唯一目录。并行 10/19→19/19(3x),串行 19/19。纯测试改动。
- 平台专属测试分流(#144/#363/#555,PR #692/#693/#694):`cmd /c` 流合并 10 测、`#!/usr/bin/env` 9 测、反斜杠字面量 11 行——在非对应平台上注定红。整文件/逐行 `cfg(windows)` + `$I363_PROBE`/PATH 解析,可移植行全留。纯测试改动。
- 测试进程 CWD 隔离(#690,PR #691):`cd` 内建 chdir 测试进程(语义正确),但 1586 测共用单进程 CWD,一个 `cd` 毒死后面全部相对 `target/` 用例(~990 失败摊满所有 part)。逐测 RAII `CwdGuard`(panic 安全),1589 处机械插入,零语义改动。`executor_tests` 串行 321/1229→1540/10(剩 10 全是预存环境/抖动);门禁与产品 A/B 零差异。串行为限(CWD 进程全局)。
- respawn 传输可写探针回退(PR #752):无 TMPDIR 且 `/tmp` 不可写(Android 有不可写的 `/tmp`)时临时文件建不出,回退内联 300KB `-c` 死于 E2BIG。存在性改可写性探针(pid+计数器),一次退到进程 temp dir、二次退 CWD。6000 函数后台任务恢复;无残留文件。
- Windows-only tmpdir 测试分流(#328/#331,PR #750):`/var/tmp` 映射与 TMPDIR 自注入引擎臂都是 `cfg(windows)`,测试在外失败。`#[cfg(windows)]` 门。纯测试改动。
- 真 Unix stty(#282,PR #732):非 Windows 是空桩(恒 rc 0)。现 `isatty(0)` 探针(非终端全形态诊断+rc 1),终端下左到右执行(`size` 经 TIOCGWINSZ,raw 系经 termios),Windows 路不动。文件 2/4→4/4;门禁与产品 A/B 零差异。
- 交互 rcfile 只 source 一次(#312,PR #712):`--rcfile gate.sh -i` 跑两遍 gate.sh。`run_startup_files` 已 source 后各驱动又直调一次。用 `__RUBASH_SOURCED_RC` 去重(空 HOME/非交互扩展/login 原样保留),`#297` exit 语义不变。append 探针 2→1;文件 6/9→9/9;门禁与产品 A/B 零差异。
- 真 uid/euid 身份,去掉 root 模拟(PR #698):非 root 主机报 `UID`/`EUID` 0、`\$` 渲染 `#`。`uid_value` 回退硬编码 `"0"`(开发基线跑在 root 下的模拟)。现取真 `getuid()`/`geteuid()`(GNU `variables.c:984`,继承值保留,Windows 保持 0)。4 个 prompt 测试曾钉作者账号、`reader` 默认 prompt 钉 `#`,现按宿主校准。三方探针与 GNU 一致;门禁与产品 A/B 零差异。
- 关联数组元素写不再全量重渲染(#618,PR #650):`m[k]=v` 每次写曾做三次 O(keys) 操作(镜像字符串克隆、解析 memo 的 Vec 深拷贝、全量重渲染),填充数组是 O(n²),K=5000 时单次写比 GNU 慢 433 倍。现改为 O(1) 追加一段 `[k]=v`,预算触发规范渲染压缩重复段(均摊 O(1),串长不超 2 倍规范长度);读路径零改动(追加永不过期)。K=5000 单次写快 45 倍,与 GNU 差距 445x→9.9x,key 数涨 5000 倍单次写仅漂 1.14x。附带修好一个 O(n²) tokenizer(每段 collect 整个剩余输入,10001 段真解析 563ms→17.8ms 线性)。50 例 GNU 差分电池 50/50;全量 failure-set 门禁与 niubash 产品 A/B 零差异。
- 索引数组覆盖写不再全量重渲染(#616,PR #652):`a[i]=v` 同理 O(n²),K=5000 时 674x。覆盖/稀疏跳跃同样 O(1) 追加(indexed 读路径天然折叠重复,BTreeMap last-wins,无需改解析/渲染;每次追加同步 typed cell;`declare -p` 等原文打印在有重复时才折叠重渲染)。K=5000 快 177 倍,与 GNU 差距 674x→3.8x。附带修好 `declare -a a=([5]=x [2]=y)` 种下 dense cell 导致后续元素写复刻的正确性 bug(另见 #651)。50 例差分电池 49/50(唯一分歧即 #651);门禁与产品 A/B 零差异。
修复
- alias 同读守卫改单调读取生成身份,跨 read 单元不再误抑制(#817):执行级 alias 同行可见性守卫拿定义时的物理行号当身份(`__RUBASH_ALIAS_LINE_<name>` vs `__RUBASH_CURRENT_LINE`,`arithmetic_aliases.rs:867-882`),而嵌套读单元会重排行号——source 的文件从 1 重新计数、eval 串按 `caller_line+i-1` 续号(`trap_exec.rs` execute_eval_source)——定义行与外层后续物理行相撞时合法使用被误报 `command not found` rc=127,GNU 展开(规则是同读不是同行,parse.y 读时展开)。修复:定义改记 `(读取代, 行)` 二元组(`__RUBASH_ALIAS_GEN_<name>`,`ShellState::read_generation` + 单调高水位 `read_generation_ceiling`,兄弟单元永不共享身份),生成边界正是现有 `CURRENT_LINE` 保存/恢复接合点——source 在 `builtins/source/execution.rs` 的 evalfile.c:253-257 push_stream 处 push、run_unwind_frame(eval
- `set -E` ERR-trap 嵌套上下文语义:折叠压制、扁平区收口 fire、return 归因(#720,PR #831):三面均以 WSL GNU 5.3.0(1)(stdin oracle)逐字节钉死。① 嵌套子壳过火:`( (false) )` 曾 fire 3 次,GNU 2 次——execute_cmd.c:1799-1806 把整体为单个子壳命令的子壳体标 `CMD_NO_FORK`,execute_cmd.c:662-663 原位执行(无 make_child/wait_for),中间帧从不观察失败、其 :769 `run_error_trap` 永不运行;现以 `subshell_fork_collapse` 旗标圈住该形体(node 形 + `f() ( )` 扁平形两路),聚合点与 `set -e` 早转臂在其下跳过,非折叠中间层(`( (false); false )`、`( { (false); } )`)照常 fire。② 扁平函数体欠火:`f() ( false ); f` 曾 fire 2 次,GNU 3 次——子壳完成态的 ERR 裁定属于 wait 帧(execute_cmd.c:756-778,无 `&&/||` 豁免;`$LINENO`=子壳 `)` 行 execute_cmd.c:696;`BASH_COMMAND`=区文本,:703
- 活动控制台 `read` 三面:`-t` 门饿死、deadline 行读被 conhost 预煮劈半、onechar 缺 ICRNL(#806,PR 于 `fix/806-read-conpty`):① `wait_readable`(`src/fd/windows_impl.rs`)只 peek 队头 1 条记录并要求 key-down——行编辑提交 Enter 的 key-RELEASE 卡在队头时,其后可消费的 key-down 永远不被看见,`read -t 2 -n1` 烧满 deadline 返 142 变量空(GNU select 同形 0.2s 取到键,WSL 5.3.0 oracle)。现 peek 16 条一批,批内任一"带字符的 key-down"(ReadFile 能吐出的)即 Ready,peek 不消费次序不动(niubash#280 的"空队列永不 Ready"保护原样保留)。② console 上的 deadline 读改走 one-char 路(`read.def:570-590` ttfd_onechar;read.def:659 每字节 check_read_timeout;GNU 的 zread 从不预煮 tty 行,read.def:736):cooked 行纪律与 raw 队列是两个缓冲,首次 cooked `ReadFile(1)` 把
- tilde 与 cd/pushd/popd 共用同一 $HOME 基准:GNU 缺失/空值之分(#754):path model 家族——`HOME=` 置空后 `cd ~` 曾静默落进 USERPROFILE(rc 0),同脚本裸 `cd` 却报 "HOME not set";`$(cd && pwd)` 快路对置空 HOME 走 tilde 回退输出 USERPROFILE;`pushd ~` 空展开是无声成功、`pushd -n ~` 把 cwd 规范化后入栈(GNU 推原词,栈为 `<cwd> ""`)、`popd ~` 空词误报 invalid argument。GNU 规范(`lib/tilde/tilde.c:344-356` tilde_expand_word):`~`/`~/` 恒按展开时点的活 cell `sh_get_env_value("HOME")` 粘接(`glue_prefix_and_suffix` tilde.c:304-317,空值是真值——`HOME=; cd ~` 即 `cd ""` 报 "null directory",cd.def:331-336),仅变量缺失才落到回退链(_WIN32 APPDATA tilde.c:349-350;passwd `sh_get_home_dir` variables.c:736,getpwuid 固定于
- PROMPT_COMMAND 执行后 `$_` 按 GNU 括回(#797):bash-preexec 类资产 source 后,DEBUG trap 里的 `preexec "$_"` 拿到的是 PC 内部最后的命令词(`bp_install`/`bp_precmd_invoke`)而非用户上一条命令的尾词。GNU `execute_variable_command` 在 PC 的 parse_and_execute 外括 `save_lastarg`/`bind_lastarg`(`parse.y:3017`、`3022-3023`;`save_parser_state` 不携带 lastarg,`7248/:7250` 只管 `$?`/PIPESTATUS);exit 跳转路径跳过恢复,与 `$?` 恢复同一规则。`bind_lastarg(NULL)` 绑空串(`execute_cmd.c:4192-4193`)。piped `-i` 最小 bash-preexec 形与 wezterm 全资产 transcript 与 WSL GNU 5.3.0 逐字节一致;回归 `tests/issue797_bash_preexec_debug_trap_regressions.rs`。
- source 的 DEBUG trap 作用域改表驱动(#797):source 内安装的 DEBUG trap 曾在本次 source 余下命令上全部哑火(`-c`/脚本/交互三路同病)。GNU `source_builtin` 只快照+摘除入口 trap(`source.def:209-217` `restore_default_signal`),文件内新装的是活表状态照常触发(`trap.c:750-756`),unwind-protect `trap_if_untrapped`(`trap.c:737-743`)仅在退出时 DEBUG 无 trap 才归还入口 trap,且排在 RETURN trap(`evalfile.c:395`)之后。旧的 duration flag 对"入口本无 trap + 文件内新装"过宽。ignore 边界(CR R1/R2/R3):`trap '' DEBUG`(IGNORE,trap.c:900 同置 SIG_TRAPPED)与 `trap - DEBUG` 按 trap 表成员资格(`__RUBASH_TRAPS`)区分而非 action 串内容——入口 ignore 不快照不摘除(`TRAP_STRING` 对 ignored 返回 NULL,trap.h:57-59),文件内 ignore 令 `trap_if_untrapped`
- #798 判定面钉死:source 中 `exit 1` 语义与 spawn 失败 `$?` 与 GNU 等价:LibreSSL testenc.sh 沙箱形态下 `cat openssl.cnf >p` 先截断后失败 → `cmp` 比的是两个空文件 → `exit 1` 本就不可达,47-cipher 循环跑穿是正确行为(WSL GNU 5.3.0 同样整循环,stdout 100 行/stderr 201 行 rc 0 逐字节一致;同机 rubash 4.5s < msys bash 5.2 5.3s,CI HANG 是 2 核 runner 8 并发下 ~400 次 spawn 的体量问题)。真实失败形态(cmp 差)首个失败即整体 rc 1 退出、交互会话同终止。回归 `tests/issue798_testenc_exit_in_source_regressions.rs`(hermetic:cmp/rm 用函数替身,钉 spawn-fail 127、空重定向目标、循环+if 形的 exit 终止)。
- 算术下标补上 `set -u` 检查(写路径)(#639,PR #654):`set -u; a[k]=v` 曾静默存储 rc 0。GNU 所有下标经 `evalexp → expr_streval` 统一设卡(`expr.c:1190-1216`);写路径求值器补上与兄弟函数相同的 prologue,五处下游诊断在 flag 下静默, abort 走 FORCE_EOF 通道(`-c` 下 127、脚本 1),另用独立 cell 区分 DISCARD 以免状态错位。25 例差分电池 10/25→25/25;回归文件 `tests/issue639_nounset_subscript.rs`。
- 复合赋值 `[@]`/`[*]` 报整词拒绝而非算术错(#636,PR #656):`a+=( [@]=q )` 曾报 `@: arithmetic syntax error`,GNU 在算术求值前整词拒绝(`arrayfunc.c:786-792`,`cannot assign to non-numeric index`)。两处 compound 臂加精确匹配 guard(不 abort,走 GNU break 语义:保留部分存储、eval 可包含)。15 例差分电池 4/15→14/15(剩 declare-abort 例归 #635);回归文件 `tests/issue636_atstar_compound.rs`。
- `eval` 包含赋值 DISCARD 跳转(#637,PR #657):`eval 'a[-2]=q'` 曾结束整个脚本。GNU 在 eval 边界捕获 DISCARD(`evalstring.c:365,422-442`),eval 返回失败、调用者继续。新增 bad-subscript class marker,eval 边界精确吸收(算术/nounset 照常逃逸);eval 内按行继续,返回末行状态。20 例差分电池 9/20→20/20;回归文件 `tests/issue637_eval_discard.rs`。
- declare/local 越界负下标 abort 而非静默丢弃(#635,PR #658):`declare -a a=( [-2]=q )` 曾打印 AFTER rc 0。builtin 解析器区分无头/越界,扫描中断并保留部分存储,报错词经 out-param 上浮到 Executor 层做 fatal 诊断+abort(复用 #637 原语实现 eval 包含)。20 例差分电池 6/20→20/20;回归文件 `tests/issue635_builtin_oob.rs`。`export`/`readonly` 的同类 abort threading 记作后续。
- 函数定义 stdin 优先于调用点 stdin(#655,PR #684):#653 把优先级翻转了(`f() { cat; } < deffile; f < callfile` 打印 call)。GNU 调用点在外、定义在内(`execute_cmd.c:5205`),定义装了活 fd 0 就赢。`part_054` 14/14;门禁与产品 A/B 零差异。
- 复合赋值字段分裂传输保留空字段(#298,PR #762):`IFS=.; A=($1)`(`a.b..b`)存了 3 个元素(`b" "`),GNU 存 4 个。`compound_field_split_transport` 对空字段发合成 tag+空格,重分只在空白 IFS 下认;现空体等下个定位 gap,用真实分隔符按序分发,空白 run 仍坍缩一边。`a.b..b`/`a::b`/`:a:` 全对 GNU;298 文件 12/12;GitHub CI 全绿(本地 master 腿 diff 因设备击杀循环未竟,见 PR 备注)。
- issue358 探针目录沙盒化(#358,PR #766):`mkdir -p /tmp` 在 Android 失败→`cd`失败→`touch zzq` 落 repo 根(测试靠自身污染通过)。temp_dir pid 唯一目录,用后删除。14/14,无残留。纯测试改动(#763/#765 同类)。
- fd 偏移测试经 PATH 解析外部 true(#655 后续,PR #687):硬编码 `/usr/bin/true` 在 Android(无 /usr/bin)上 127。测试时经 PATH 解析(绝对路径绕过 builtin 仍是外部 spawn),缺席则跳过。双测 Android 绿。纯测试改动。
- prompt 渲染不再破坏读者的 `$?`/PIPESTATUS(#604):交互驱动(niu 原生控制台通道,`expand_prompt_string_mut`)渲染 `PS1`/`PS2`/`PS0` 时,内嵌 `$(false)` 等命令替换的退出状态此前经赋值展开引擎的替换边界写入 `exit_code`,下一个命令的 `$?` 读到 1;GNU 的 prompt 渲染是读侧操作,从不动读者的状态(WSL GNU Bash 5.3.0 piped-`-i` 探针:`PS1='$(false)'` 渲染后 `echo $?` 仍打印渲染前的值)。修复:渲染通道快照并在收尾恢复 `$?` 与 PIPESTATUS——赋值 RHS 通道(`v=$(false)` → rc=1 的 POSIX 赋值退出规则)不受影响。附带 #604 的通道 parity 回归与相对性能上限测试(PS1 渲染路径 vs PROMPT_COMMAND 路径的中位耗时比值上限),并按 profile 数据澄清该 issue 的主刀:四主题 rr 成本为外部 spawn 数 × Windows spawn 地板(每次渲染 4-6 次 git spawn;裸 `std::process::Command` 对同一 git.exe 需 208.9ms,rubash 会话内 217.6ms),两条引擎通道执行
构建
- 命令替换 pipeline-filter 死链删除(#710):`command_substitution_pipeline_filter` 的 8 条 filter 臂(sort/sed/tr/head/grep/wc/tail/uniq)连同其唯一调用链——`command_substitution_pipeline_output`、`timed_command_substitution_inner` 的 `|` 臂、`command_substitution_pipeline_first_stage`、孤儿助手 `split_pipeline_words`/`echo_raw_output`——自 rubash#117 平凡性 gate 之后不可达:gate 字节白名单拒绝 `|`,能抵达 filter 的替换体不可能含管线;且该链还只住在 `time` 前缀快捷方式内,进一步收窄。运行时复核:入口探针在 producer×filter 电池(含反引号、嵌套 comsub、alias 注入 `|`、`cat`/generic 首段、`time` 前缀形)上零命中;阳性对照(gate 临时放行 `|`)全部探针命中并复现休眠特征(`$(…|wc)` 默认单列 `1`,而活跃路径是三列)。`$( … | … )` 的可观测行为本就由真执行器管线(pipeline_exec)与外部
-
oh-my-niu Unreleased 未发布 日期未知
更新
- Release artifact: `oh-my-niu-2.0.0.zip` (`api = "niubash:[email protected]"`,
- `min_niubash = "1.0.0"`).
- Host compatibility: the `git` prompt segment (`[segments.git] id = "vcs"`
- in `prompts/segments.toml`) now resolves to niubash's native
- `SegmentId::Vcs` as of niubash 1.4.1 — branch / `branch*` / short SHA,
- themed `[git_clean]` cyan / `[git_dirty]` yellow, rendered in-process with
- zero child git processes. No bundle format change: every shipped preset
- already carries the segment (the `lean` preset is the documented showcase).
- Renamed the bundle from `oh-my-winuxsh` to `oh-my-niu` to match the
- niubash 1.0.0 brand. Installed `~/.oh-my-winuxsh` directories keep
- working: niubash discovers the pre-rename layout as a fallback.
- Bundle/plugin API identifiers move from the `winuxsh:` prefix to
- `niubash:` (`niubash:[email protected]`, `niubash:[email protected]`,
- `niubash:[email protected]`). Manifests may declare either
- `min_niubash` or the legacy `min_winuxsh` field.
- Entry point renamed to `oh-my-niu.winux`; scripts now use the `NIU_*`
- environment prefix (`NIU_PLUGINS`, `NIU_THEME`, `NIU_THEME_PLUGIN`)
- and the `NIUBASH` bundle-root variable.
- New first-party plugins: `command-timer` (per-command duration and exit
- code), `auto-env` (automatic `.venv` activation per directory),
- `dirmarks` (directory bookmarks), `git-fetch-reminder` (upstream
- divergence notices), and `env-sync` (push/pull shell config and the WPM
- package manifest through any rclone remote).
- All first-party pack manifests move to version 2.0.0 to match the
- bundle release.
-
WinuxCmd 1.1.14 2026-10-17
Added
- wpm: `wpm links verify` reports, by path, every command link that does not
- run the installed `winuxcmd.exe` - the diagnosis that was missing while a
- stale link could keep one command on an older build for hours without a word.
- wpm: `winuxcmd-shim.exe`, a small forwarder behind `layout: shim`
- packages. `usr\bin\python.exe` and its siblings used to be a hardlink to an
- older `winuxcmd.exe` whose only job was to spawn `opt\<pkg>\<command>.exe`,
- so every `python`/`node`/`curl`/`ssh` call paid a whole WinuxCmd startup; the
- dispatch hop drops from a measured 24.5 ms to 12.1 ms. An installation without
- the forwarder keeps the previous behaviour, and `wpm links rebuild --force`
- (which activation already runs) switches existing installs over.
Fixed
- wpm: `wpm update winuxcmd` no longer leaves a command link on the previous
- image silently. A link that cannot be deleted is parked and replaced, the
- failing link is named, the links are verified after the swap and relinked
- once, and the detached update helper writes its output to
- `.wpm\logs\self-update-<stamp>-<pid>.log` (newest three kept) instead of
- discarding it.
- wpm: the self-update keeps the newest two rollback copies of
- `winuxcmd.exe` and prunes older ones - one machine had accumulated 61.8 MB -
- and `wpm clean backup` (and `clean all`) reclaims them.
-
WinuxCmd 1.1.13 2026-10-17
Fixed
- wpm: a package upgrade now replaces files that a running program holds
- open, instead of failing halfway. 1.1.12 refused up front when it *thought* a
- destination could not be replaced, but the probe it used — opening the name
- with `DELETE` access — succeeds against a file another process has mapped as
- its image, while the delete and the overwrite that follow both fail with
- Access denied. On a real installation this meant `wpm install niugit --force`
- still failed while git's `fsmonitor--daemon` held
- `libexec/git-core/git.exe`, and it failed *after* the reconcile and the entry
- links had run, leaving the package half-updated.
- Destinations are now renamed aside (`<name>.wpm-old-<pid>`) before
- anything is written: a rename is exactly what a mapped image permits, the
- running process keeps its old bytes, and the operation is reversible. If a
- destination cannot even be renamed, the ones already parked are put back and
- the install refuses with the culprit and the processes running from it — so
- an upgrade is either complete or untouched. Once the install commits, the
- parked names are deleted; while the old process still holds them the name
- stays, and `wpm clean` removes it later.
- wpm: a superseded path the new version no longer maps is parked the same
- way instead of aborting the upgrade when a live process holds it.
- wpm: `wpm clean staging` and `wpm clean all` also remove parked
- `.wpm-old-<pid>` names left by earlier installs.
- Verified on a live installation with 28 `fsmonitor--daemon` processes holding
- the payload: the forced upgrade succeeds, `usr/bin/git.exe` and `bin/git.exe`
- both share the payload inode, and `.wpm/staging` is pruned.
-
WinuxCmd 1.1.12 2026-10-16
Added
- wpm: every package command gets a PATH entry link at
- `<root>/bin/<cmd>.exe` — a hardlink to the real binary (the payload under
- `opt/<pkg>/` for shim packages, the mapped binary in `usr/bin/` for flat
- ones), never a launcher, so nothing extra runs per invocation. The links are
- recorded in the package receipt and kept in sync by install, update,
- `wpm links rebuild` and uninstall.
Fixed
- wpm: an install is refused up front when a destination is held open by
- a running program, instead of failing halfway through. A file held by another
- process cannot be replaced on Windows at all — delete and overwrite both
- return *Access denied* — so the old behaviour could leave a package
- half-updated; an upgrade of niugit was observed with `opt/niugit` on the new
- version while the PATH entry still ran the old one. WPM now probes every
- destination before writing anything, lists the files and the programs running
- from them, and leaves the installation exactly as it was. Close the listed
- programs and retry.
- wpm: flat-layout file mappings are copied from the package's payload
- rather than the extraction directory, so a mapping that duplicates a path
- already covered by a directory mapping no longer produces a second copy of
- the package and a PATH entry pointing into `.wpm/staging` (the two
- destinations now share one inode). The staging tree is pruned after a
- successful install.
-
WinuxCmd 1.1.11 2026-10-16
Fixed
- wpm: `wpm update winuxcmd` swaps the image by unlinking the old name
- and moving the staged binary into place instead of overwriting a
- running/mapped image in place. The in-place write fails with "being used by
- another process" whenever any process holds the file (observed on a live
- install, where the update silently kept the old version), and writing under
- a mapped image is unsafe for the processes already running from it. The
- in-place path remains the fallback when the unlink itself fails.
- wpm: installing a package reconciles its own previous install first -
- destinations the new artifact no longer maps are removed and, on a version
- change, files inside still-mapped directories that the new payload dropped
- are pruned. Before, a layout change left inert leftovers behind (the retired
- shim launcher `opt\<pkg>\<cmd>.exe` after a move to `layout: flat`, or a
- stale DLL in a PATH directory from older flat packaging). Only paths
- recorded in the package's own receipt are touched, they must resolve inside
- the root, and the winuxcmd image and the running executable are never
- deleted.
- wpm: `outdated` orders versions instead of comparing strings, so a stale
- mirror advertising an older version no longer reports a
- `update available: 2.55.0.5 -> 2.55.0.4` downgrade. The order is the
- dotted-numeric core plus, when present, a Debian-style packaging revision
- (`5.4.1-unixwin2` > `5.4.1-unixwin1` > `5.4.1`) - what a repackaged artifact
- needs, since the upstream version stays and our rebuild gets a revision.
- Vendor spellings (`9.5.0.0p1`, `r42`, ...) keep the conservative "any
- difference is an update" rule.
-
WinuxCmd 1.1.10 2026-10-16
Added
- native_path: normalized API operands now resolve the shell tree — the bare
- `/` and every path below the configured install root, `/tmp`, `/var/tmp`,
- `/home` and the `/mnt/X` WSL drive form — and a shell-namespaced operand's
- canonical result is re-expressed in the POSIX face, so `readlink -f` and
- `realpath` agree with `pwd`. The executable-leaf open order (an existing
- as-spelled path wins, else the `X.exe` sibling) keeps `test -f /usr/bin/seq`
- and `head -1 /usr/bin/seq` consistent while `ls -d` keeps the operand
- spelling. The layer is inert unless a shell root is configured, so standalone
- WinuxCmd keeps its native dialect (unixwin/niubash#346).
-
WinuxCmd 1.1.9 2026-10-16
Fixed
- wpm: installing a payload file over an existing `usr\bin` command name
- now breaks the old WinuxCmd multicall hardlink with POSIX-semantics unlink
- instead of a plain `DeleteFileW`. Every `usr\bin` entry is a link to the
- same on-disk image as the running `wpm.exe`/`winuxcmd.exe`, so the plain
- delete self-locked and failed with `Access denied` ("failed to break
- WinuxCmd hardlink"). Only `layout: flat` packages, whose payload maps the
- real binary onto `usr\bin\<cmd>.exe`, ever reach this path — first hit by
- `wpm install niugit --force` (unixwin/niu-git#8). The same POSIX unlink is
- now used by the stale-link cleanup and alias-replacement paths, which had
- the identical hazard.
-
WinuxCmd 1.1.8 2026-10-15
Changed
- wpm: shim payload resolution checks shallow per-package locations
- (`<pkg>/<cmd>.exe`, plus `bin/`, `libexec/`, `lib/`, `sbin/`, `usr/bin/` at
- depth 1-3, shallowest wins) before falling back to the bounded recursive
- scan, so command-differs-from-package shims (`git` -> `niugit`, `cargo` ->
- `rust`, `clang` -> `llvm`, `nu` -> `nushell`, `nvim` -> `neovim`) no longer
- pay a whole-`opt/` tree walk on every invocation (~15k files). `time git`
- through the shim 212 ms -> 77 ms; a prompt-style git hook batch
- 847 ms -> 291 ms; direct-hit shims (`python`, `curl`, `node`) are unchanged
- (unixwin/niu-git#8).
Fixed
- ls: `/` (also spelled `//`, `///`) resolves to the shell root named by
- `__RUBASH_SHELL_ROOT`/`NIU_ROOT`/`WINUXSH_ROOT`/`RUBASH_ROOT` instead of
- enumerating the drive root, independently of the current directory;
- recursive listings keep the logical `/...` headers while lookups and entry
- metadata go through the shell root (unixwin/niubash#346).
-
niubash 1.4.0 2026-10-10
Added
- Windows Terminal-form desktop/Start-menu shortcuts on Windows 10
- (niubash#211, #290): Win10 has no default-terminal setting, so
- direct-exe shortcuts used to be hosted by conhost. When a `wt.exe`
- resolves (WindowsApps alias -> PATH -> App Paths registry) and the
- Niubash WT profile is registered, generated shortcuts take the
- `wt.exe -p Niubash` form; without WT the conhost baseline stays
- unchanged. `niu --install-wt-shortcuts` (`--update-only`,
- `--desktop|--start-menu`) is the standalone verb and the installer
- handoff, and it only rewrites links that already exist, so a declined
- desktop icon is never grown.
- `niu --cwd-state <file>` directory memory for one-shot agent
- invocations (#272): opt-in, so a chain of one-shot `niu -c` calls
- behaves like one shell — the directory recorded in `<file>` is
- restored before startup (before niu's own startup logic, relative
- script paths, and `source_non_interactive_env`) and the shell's final
- directory is recorded back after every one-shot exit path. Without
- the flag, behavior is byte-for-byte unchanged.
- `niu --encoded-command <base64>` (#271): agents and CI callers
- get a quoting-free channel — an RFC 4648 payload (padding optional,
- ASCII whitespace ignored, must decode to valid UTF-8) executes
- through the same one-shot path as `-c`, with the same
- `BASH_ENV`/`NIU_ENV` sourcing and `BASH_EXECUTION_STRING` contract.
- An optional `v1:` prefix is stripped defensively: MSYS path
- conversion rewrites arguments starting with `/`, and a base64 payload
- can start with `/`.
- Doctor timing advice (niubash#201): `niu doctor` now carries an advisory
- row pointing timing/performance-sensitive scripts at the bash 5 builtin
- `$EPOCHREALTIME` / `$EPOCHSECONDS` instead of looping on the external
- `$(date +%s%N)` — each `date` call pays Windows process creation
- (~10-30ms) while the builtin expands in microseconds with the GNU format
- `1699999999.123456`. Regression tests pin the format, second-level
- agreement with `date +%s`, live monotonic updates, and the speed gap.
- Android and OpenHarmony release-CI legs incubating (roadmap niubash#207,
- niubash#247): `build-android` links aarch64-linux-android and
- armv7-linux-androideabi against bionic (NDK r27, API 24) and ships a zip
- per leg with ELF-arch verification in place of an on-runner runtime
- smoke; `build-openharmony` is check-only for now
- (aarch64-unknown-linux-ohos is Tier 2 with host tools — real linking
- needs the OHOS SDK clang wrapper, which has no version-pinned runner
- fetch path yet, so no binary ships from that leg). Both jobs run
- continue-on-error and stay out of the release needs, the same incubation
- shape the musl leg had before it was tightened; the platform-support
- matrix documents both targets.
- Vi editing mode wired end-to-end (niubash#184): `set -o vi` / `set -o
- emacs` now switch the LIVE line editor mid-session, like GNU bash — both
- options route to one readline editing-mode state (bash
- `builtins/set.def:424 set_edit_mode` → `rl_variable_bind("editing-mode")`,
- rebindkeymap applied immediately by readline `bind.c:2092 sv_editmode`).
- The product re-reads the engine option flags at every prompt
- (`Shell::refresh_edit_mode`), rebuilds the Reedline editor on a change,
- and writes the winner back exclusively, so `set -o vi` → `set -o emacs` →
- `set -o vi` round-trips and `set -o` listings stay mutually exclusive the
- way GNU reports them. The rc line `set -o vi` works at startup (the
- editor is built after the rc resolves the mode). Every fresh line starts
- in insert mode even in vi mode (readline.c:1243 "Each line starts in
- insert mode"; reedline keeps ViMode across reads) — a line submitted in
- normal mode no longer leaves the next prompt in normal mode. The floor
- prompt renders a minimal theme-neutral vi-mode indicator (`i ` insert /
- `- ` normal, terminal default color, configurable through
- `prompt_indicators`); a claimed PS1 keeps the theme's own prompt slot
- (GNU bash ships no built-in indicator). ESC enters normal mode through
- reedline's built-in vi parser; normal-mode motions (`k` history recall,
- `dd` line kill) are covered by ConPTY journeys.
- Release-CI pre-install of external packages via wpm (niubash#189): the
- release workflow installs the packages named in
- `scripts/release/preinstall.json` into the staged WinuxCmd root with
- `winuxcmd wpm install <pkg> --root <root> --yes` (wpm ships inside
- winuxcmd.exe — no separate binary) between WinuxCmd staging and packaging,
- so the Windows release zip and installer carry a working GNU awk out of the
- box. First entry: `gawk` (GNU Awk 5.4.1; bash-it plugins and completions
- hard-depend on awk). The manifest also records the shim (`awk.exe`, a
- winuxcmd.exe hardlink that forwards to the `opt/gawk` payload — wpm's gawk
- package registers only `gawk`, and bash-it calls plain `awk`) and the
- owner-ordered exclusions as durable policy: no compression tools
- (bzip2/gzip — users `wpm install` them themselves), `goawk` forbidden
- (gawk only), and `link` forbidden forever (MSVC link.exe collision).
- Installs are fail-open: a failed package (e.g. the arm64 legs while the
- wpm index carries windows-x64 artifacts only) warns in the step summary
- and release notes, and never blocks a release; whatever DID install is
- hard-verified in the staged package (`--version` plus a plugin-shaped
- `awk '{print $1}'` pipeline through the packaged niu.exe) before upload.
- The completions corpus carries real `awk --help`/`gawk --help` transcripts;
- the embedded applet inventory goes 178 → 180 (SKILL.md quickref
- regenerated, golden-gated).
- AI agent skill bundle with `niu skill install` / `niu skill status`
- (niubash#188): the shell ships its own agent-facing guide
- (`skills/niubash/SKILL.md` — identity, capability surface, pitfalls,
- niu-vs-cmd-vs-powershell decision guidance) and installs it into agent
- skill dirs — `--target claude|zcode|cursor|all|<dir>`, default claude;
- `status` compares installed bytes against the embedded bundle and prints
- a sha256 digest; `niu doctor` gained an advisory `agent skill` row. The
- command tables inside the bundle are generated, not hand-written:
- `scripts/generate-skill.py` renders the builtin table (from a captured
- `help -s '*'` transcript), the launcher/plugin verb surface (captured
- `niu --help`), and the 178-applet winuxcmd inventory (from the completion
- assets) into GENERATED-marked regions of `SKILL.md` and
- `references/quickref.md`; `scripts/test-skill-bundle.py` golden-checks
- both the regions and the include_str! manifest (in CI and in the release
- pipeline). Releases now attach `niubash-skill-v*.zip` with the
- WinuxCmd-skill layout (one top-level `niubash/` directory).
- WinuxCmd applet completions are compiled into the shell (niubash#172 L1):
- `grep --col<Tab>` offers `--color`/`--colour` with descriptions, `ls --color`
- offers `always`/`auto`/`never`, `wpm <Tab>` lists its 20 subcommands, and
- every one of the 178 WinuxCmd 1.1.5 applets (including `[`) carries its real
- flag set with no plugin enabled. Definitions are generated from
- `winuxcmd --help` transcripts by `scripts/generate-winuxcmd-completions.py`,
- embedded from `crates/niubash-runtime/assets/completions/winuxcmd/`, and
- load as the base layer: bundle/pack and user-dir TOMLs still override per
- command. Generator fixes en route: bracket command names (`[`), headerless
- help (`top`), multi-alias specs (`pr -F, -f`), `EXIT STATUS:`/footer
- pollution of descriptions, optional-value flags (`--color[=WHEN]`) no longer
- swallow filename completion, path-valued flags decided by spec hints instead
- of description prose, and wpm `Commands:` parsing. Golden coverage:
- `crates/niubash-runtime/tests/winuxcmd_completions.rs` plus
- `scripts/test-winuxcmd-completions.py` (corpus replay must be
- byte-identical).
Changed
- The factory default prompt on Windows is now the Git Bash skeleton
- (owner adjudication 2026-10-12, #292): green `user@host`, yellow
- POSIX-persona cwd, cyan branch (+ dirty `*`) and `$ ` — the MINGW64
- environment segment is deliberately omitted (that is MinGW identity,
- not this shell's) and there is no static product tag. The branch/dirty
- segment reads the repository through git2 (owner-approved library
- dependency, Windows-target-only compile) instead of a git child
- process, so the prompt path never spawns (the niubash#145 red line):
- measured cold 39 ms, warm p50 2.5 ms / p90 3.7 ms per prompt against
- Git Bash's per-prompt `__git_ps1` fork (~100-300 ms). Claims still
- win: a PS1/theme claim displaces the floor per prompt, and `set -o
- vi` keeps its live editing-mode indicator (niubash#184). The skeleton
- follows Git Bash's own layout after live owner passes on 2026-10-14:
- two rows (info row, then the `$ ` input line on its own row) and the
- branch in MINGW64's parentheses — `(master*)`, dirty star inside.
- Default TAB completion style is now the multi-per-row grid
- (`CompletionStyle::Column`, owner rulings 2026-10-12 + 2026-10-14):
- every TAB shows the candidates at once, several per row — the look of a
- GNU-style printed listing without the silent first TAB (the literal GNU
- readline shape shipped briefly as the default and read as "dead" at the
- prompt when TAB #1 had nothing to complete; live owner feedback flipped
- it). The literal GNU shape remains an opt-in
- (`NIU_COMPLETION_STYLE=gnu` or `niu config set completion.style gnu`):
- TAB completes the longest common prefix (a lone candidate completes
- outright), and a second TAB on an unchanged line prints the candidates
- as width-adaptive columns next to the prompt and repaints it —
- implemented as a reedline `Menu` (`completion::gnu_menu`) so it renders
- through the normal repaint pipeline. The Ide popup stays opt-in
- (`ide`, also accepted: `smart`, `popup`); precedence is env > config
- file > default, and `niu doctor` shows the effective style with the
- switch commands. The setup wizard gained a completion style step
- (current value pre-highlighted) on first run and on `niu setup`
- re-runs.
- Architecture correction before release: the carapace-bin completion source
- merged from #243 is removed from the shell core. No shell hardcodes a
- third-party completion provider into its source — the provider belongs in
- the rc data layer, integrating through the standard bash completion
- protocol the shell already speaks (`complete` compspecs, `NIU_COMPDEFS`
- compdef functions, `<cmd>.toml`/`<cmd>.bash` definition files). The native
- completion layers are unchanged (embedded WinuxCmd applet TOMLs, bundle
- definitions, user completion files); `niu setup` grows an opt-in (default
- off) rc snippet for carapace-bin — the same block a bash user writes by
- hand, guarded and a no-op until carapace-bin is installed — and a new
- docs page documents the rc-based contract for any provider that speaks
- the bash completion protocol (carapace is one example). Binary-level tests
- pin the rc route: a `complete -W` compspec and a compdef hook sourced from
- `~/.niubashrc` both serve Tab candidates, and a standard `.bash` completion
- script serves flag candidates through the bash-import channel. Interactive
- Tab resolution of `complete -F`/`-C` hook functions remains an engine
- (rubash) follow-up, documented in the provider page. Zero compatibility
- debt: no 1.4.0 tag ever carried the embedded source, and the `NIU_CARAPACE`
- variable was never a released surface (niubash#243, niubash#187).
- command-not-found no longer advertises package managers by default
- (niubash#249): a typo like `win` prints only the GNU one-liner — the
- unsolicited "package search hints" block naming winget/scoop/choco is
- removed, and third-party managers are never recommended again. The
- recipe/wpm install hints for exact, known tool names (`rg`, `awk`, …)
- stay. An opt-in channel returns a wpm-only suggestion:
- `niu config set command-not-found-hint wpm` (persisted in the new
- `~/.niubash/config.toml`; `NIU_COMMAND_NOT_FOUND_HINT` overrides) — it
- suggests one `wpm search --name '<word>'` line only for
- high-confidence near-misses (dropping the word's last character hits an
- installed command or a known package name), so pure typos stay quiet
- even with the switch on. New `niu config get/list/set` subcommand surface
- manages the file.
- Release binaries are stripped (niubash#228): the release profile strips
- symbols, shrinking the shipped niu.exe.
- The pre-install manifest bundles niugit, ripgrep and fd alongside gawk
- (niubash#230), and the setup wizard detects release-bundled components as
- already-present instead of re-offering them (niubash#231).
- Documentation passes: a "Calling PowerShell from niu" page joins the
- platform docs (when to reach for powershell.exe from niu, and how)
- with a README style pass (niubash#203, niubash#253); the READMEs were
- then restructured evidence-first around quick answers and the
- five-platform identity (niubash#255); the Platform Support page
- documents the status matrix, what the HarmonyOS bash actually is, and
- HNP packaging (#225, #226, #227); and the MSYS labels (`uname` ->
- `MSYS_NT`, `OSTYPE=msys`, `MACHTYPE` `x86_64-pc-msys`) are documented
- as what they are — a compatibility persona of a pure native Windows
- build with no msys-2.0.dll/cygwin1.dll, with the measured behavioral
- boundary (niubash#248).
- One-shot `niu -c` invocations take a fast path that skips REPL-only
- initialization (prompt/theme backend, completion directory scan,
- completion-state snapshot); the history provider and managed aliases
- keep their `-c` contract, and `bash -i -c` still sources the rc.
- Measured 13.9 -> 12.1 ms in-process startup for `niu -c 'echo hello'`
- with a 29-case behavior battery at zero diff (#270).
- REPL-only commands (`self-update`, `update-niubash`, `logout`) are
- highlighted as builtins instead of unknown-token red (#281).
- The easter eggs are pruned to snake and about; the about logo is
- pyfiglet-exact (#264).
- The declared MSRV is 1.95 — reedline 0.50 in the lock requires it
- (#267).
- Installer disclosure (niubash#313): the "Create a desktop shortcut"
- task is new in 1.4.x and default-checked. Silent installs and upgrades
- (`/SILENT`, `/VERYSILENT`) never show the task page, and Inno has no
- saved selection for a new task name — so a silent upgrade of a 1.3.x
- machine runs the task with its default state and grows a desktop icon
- (rewritten in place to the Windows Terminal form by the shortcut step)
- without ever asking. Owner ruling: the behavior is intended; it is
- disclosed here and in the installer source. Interactive installs keep
- remembering an unchecked choice.
Fixed
- The self-update installer is SHA256-verified, fail-closed, and the
- release ecosystem is pinned (niubash#311, niubash#312, PR #318):
- every Windows setup.exe ships with a coreutils-style `.sha256` sidecar
- (plus an aggregate `SHA256SUMS` for the whole release — both are
- release assets, and the same digests feed the winget manifest).
- `niu self-update` downloads the sidecar published beside the same
- asset it fetched, hashes the installer, and aborts before launch on
- any mismatch — or when the sidecar cannot be fetched at all (the
- error points at the manual-download path). Release builds no longer
- taste the live ecosystem: workflow dispatches require a pinned
- WinuxCmd tag plus per-arch zip digests, tag pushes read the committed
- `scripts/release/winuxcmd-pin.json`, and the wpm pre-install manifest
- pins every package's artifact SHA-256 per arch (pin == index ==
- shipped bytes; a pin failure keeps the loud fail-open warn-and-ship
- path).
- Plugin verbs can no longer hang forever on a dead origin, and a failed
- add no longer strands its spec declaration (niubash#293, niubash#294,
- niubash#295, niubash#296). Every git transport a plugin verb spawns —
- `niu plugin add`/`sync`/`update`/`restore`/`distro import` — now runs
- under a 120 s wall-clock budget (`--timeout <seconds>` on `add`/`sync`,
- or `NIU_PLUGIN_FETCH_TIMEOUT_MS`, overrides it per run; the rc
- `--bootstrap` form keeps its tighter 3 s): at the budget the clone's
- whole process tree is killed (no more orphan `git.exe` helpers after a
- hand-killed niu) and the failure row names the timed-out origin. The
- spawned git also carries the user's proxy environment explicitly
- (`HTTP_PROXY`/`HTTPS_PROXY`/`ALL_PROXY`/`NO_PROXY`, each mirrored to
- its case-variant, so an uppercase-only environment still reaches git's
- case-sensitive transport). A sync stays per-source isolated: one
- stalled origin costs its own budget and a failed row while the rest of
- the spec reconciles. A declaration whose install just failed joins the
- stranded population, so `niu plugin sync --prune` removes it in the
- same run — and `niu plugin add` of a dead origin rolls its declaration
- back on the bounded failure, so the target can be re-added instead of
- deadlocking on "already declared".
- The Windows paste-chunk detection no longer swallows the first Enter of
- every line (niubash#284, a niubash#252 regression): whether a bare Enter
- submits or opens a paste chunk is now decided structurally — by the LF
- partner (`Enter + CONTROL`) queued right behind it, the console's shape
- for a machine-fed CRLF line break — not by the 50 ms arrival-burst
- heuristic. "Text + Enter" in one burst (fast human typing, ConPTY test
- drivers, expect scripts, `send-keys`) submits on the first Enter again;
- multi-line pastes keep buffering quietly until the user's own Enter
- (0 mid-paste executions, niubash#202 contract unchanged). A typeahead
- line queued behind a submitting Enter is pushed back and lands, in
- order, at the next prompt. The TypeaheadGuard no longer double-injects
- swept console input records: the sweeper's self-heal path drains the
- buffer before writing it back, so a record can no longer be reinjected
- twice (once by the self-heal, once by disarm; niubash#287).
- A paste burst's trailing bare Enter now submits the buffered chunk
- (niubash#308): a paste followed immediately by Enter — one write, the
- Enter inside the burst window — used to be swallowed, and a second
- Enter was needed. The same LF-partner lookahead that decides the
- chunk-open Enter now runs in the pending branch: silence (or any
- other queued key) behind the CR proves the chunk complete, and the
- chunk flushes and submits in one batch on the first Enter.
- Execution-level paste regressions pin empty-line fidelity across
- every shape (niubash#291, PR #317).
- The plugin trust gate now runs at runtime, not only at trust time
- (niubash#297): `niu plugin sync` and `plugin list` re-verify every
- trusted source against its lockfile pin, so a tree modified after
- `trust` reads `drift` (never a lying `unchanged`) and
- `degraded=true` with the drift reason in `--json` — the tree stops
- contributing assets until it is repaired. sync records drifted ids in
- `sources/drifted-sources.txt`, and the managed rc blocks carry a static
- guard that prints the warning at every startup (the rc stays static
- text; no hashing in it). `plugin restore` wording is honest about
- rebuilding from the pin ("rebuilt from the lockfile pin", not "tree
- matches"). bash-it's `enabled/` entries — the one legitimate tree
- mutation `niu plugin enable` itself makes — are transparent to the pin,
- while an injected `enabled/` file with unpinned content still trips
- both `verify` and `trust`.
- `niu plugin mirror set http://…` is refused without an explicit
- `--allow-plaintext` confirm (niubash#298): a plaintext mirror ships
- every git fetch unencrypted and unauthenticated, and the confirm still
- prints the standing warning. The first fetch of an unpinned non-catalog
- origin is now flagged for what it is: `plugin add` prints a
- "NOT integrity-verified" warning and the record carries
- `first_fetch_verified: false` (visible in `plugin list --json`); a
- `--checksum` pin or a curated catalog origin marks it verified.
- `niu plugin clean` reports every orphan directory under `sources/` the
- registry does not claim (niubash#299) instead of silently keeping
- manager-shaped ones and ignoring the rest; `--orphans` is the explicit
- confirm that deletes them.
- `niu plugin add <path>` positional form is first-class (niubash#300):
- a bare local directory snapshot-copies in, a single `.sh`/`.bash`
- file becomes its own one-file wild source, a nonexistent path fails
- honestly (instead of a baffling `git clone` status 128 after the
- trust-boundary banner), and the help documents the path and
- `file://` forms.
- Managed rc-block rewrites converge and self-repair (niubash#324,
- PR #325): an rc already carrying "user lines moved out" debris (the
- issue's six-section fixture — five dangling `if`s, a syntax error on
- every startup) stayed corrupt through every sync/enable/disable
- forever, because the rewrite paths never inspected the user area.
- Every plugin verb now normalizes the migrated sections first: a
- section whose every non-comment line is guard-shaped goes entirely, a
- section mixing genuine user lines with debris is kept verbatim
- (whole-section edits only — niubash#176 keeps user lines
- sacrosanct), and a re-fed user line merges into the pile instead of
- appending a duplicate section per verb. Even the startup
- `niu plugin sync --bootstrap` repairs the file — no one-shot cleanup
- command needed.
- Completion covers the commands people actually type (niubash#305,
- #306, #307, PR #319): git, tar, curl, ssh, docker, kubectl and npm
- are not WinuxCmd applets and completed as bare filenames — a second
- compiled base layer now ships hand-curated static subcommand/flag
- tables for them (no external completion script is ever executed).
- `NIU_COMPDEFS` lines that resolve to an existing directory now
- declare completion-definition directories (`<cmd>.toml` on the
- embedded applet schema, `<cmd>.bash` statically parsed by the
- bash-import reader — file bodies are never executed); previously the
- declared surface had no user-reachable entry point and zero effect in
- every mode. And `--completion-probe` sources `NIU_ENV` (`BASH_ENV`
- fallback) before the interactive rc, so compspecs declared in an
- agent init file reach the probe.
- `niu -s` reads stdin scripts on demand (rubash#586 host half,
- PR #314): the whole stdin used to be slurped with `read_to_string`
- before execution, so a `$(exec cat)`/`read`/top-level `exec cat`
- consumer hit EOF immediately and the unconsumed tail came back as
- code. The host now drives the engine's on-demand reader (the rubash
- #593 library surface): the consumer sees exactly the bytes after the
- last submitted command, and the repro is byte-verified against WSL
- GNU bash 5.3.0.
- The bash/rbash invocation faces report their name (niubash#321,
- PR #323): `bash -c 'echo $0'` printed `niu` — the shim's
- `NIU_INVOKED_AS` forward now carries all four faces
- (sh/ash/bash/rbash, login marker and `.exe` stripping preserved) and
- POSIX mode stays keyed off the face; the d3 setup-rerun journey
- answers the new TAB-completion wizard step (niubash#322).
- The `niu plugin` help and doc examples no longer point at a dead repo:
- the wild-GitHub-plugin example was `niu plugin add
- rcrowley/bash-preexec`, but rcrowley/bash-preexec 404s (the project
- moved to rcaloras/bash-preexec years ago), so a copy-paste of the
- example failed. Every live reference — help text, the plugins.toml spec
- example, the plugins guide, the wild-source fixture — now names
- rcaloras/bash-preexec, and the example repo was verified cloneable
- (niubash#301).
- The config subsystem honors `HOME` isolation (niubash#302): every niu
- subsystem — config.toml, mirrors.toml, the plugin rc, doctor, prompt,
- the setup wizard — resolves the home directory through one shared
- resolver in which an explicit `HOME` environment override wins on every
- platform. Before, the Windows resolver ranked `USERPROFILE` first, so a
- HOME-only sandbox (tests, CI, portable setups) silently read and wrote
- the real profile. A default Windows session (`HOME` unset) resolves
- exactly where it always has, and the classic Unix `HOME`-first
- convention is unchanged.
- The freshly-rendered mirrors.toml template no longer recommends
- hub.fastgit.xyz (dead since ~2021; niubash#303). The second community
- example is now ghproxy.net, verified live at edit time with a real
- `git clone` through the `insteadOf` rewrite (gitclone.com, the first
- example, re-verified the same way). The examples stay comments marked
- UNSUPPORTED — they may die at any moment and are the user's to verify.
- `.niubashrc` rewrites are atomic (niubash#304): every managed-block
- rewrite (`niu plugin enable/disable/sync`) and the setup wizard's rc
- write now stage the new content in a same-directory temp file and swap
- it in with a single replace-rename (`MoveFileExW
- MOVEFILE_REPLACE_EXISTING` on Windows, `rename(2)` elsewhere). The
- former truncate-in-place write and the wizard's former
- remove-then-rename left windows where a concurrent reader — a pipe or
- `sed` run against the rc — saw the file empty, partial, or briefly
- absent; a reader now always observes a complete old or new generation.
- The installer cleans up after itself (niubash#313). Uninstall now also
- removes the `{app}\winuxcmd\bin` entry from the user `PATH` — the
- install-time `wpm links rebuild` adds it, but the uninstaller only ever
- removed `{app}`, leaving a dangling PATH entry after every uninstall.
- The installer gained `[InstallDelete]`/`[UninstallDelete]` sweeps for
- files earlier builds shipped and the current payload retired (the
- reverted dual-name build's `niubash.exe`, stray self-update
- `niu.exe.old-*` backups), which previously survived upgrade AND
- uninstall on real machines.
- A closed stdout pipe terminates niu (niubash#245, PR #256): when the
- shell's own stdout is the pipe whose reader went away, non-interactive
- executions interpose a kernel pipe + pump thread and exit with one
- diagnostic and status 1 — previously, with no SIGPIPE on Windows,
- every later write failed again and an unbounded builtin loop printed
- one diagnostic per iteration forever (`niu -c 'yes' | head -c 1`
- never exited). The contract is pinned across every non-interactive
- execution shape — script-file producer, stdin-script producer,
- external child as the producer, and stderr gone too (niubash#286,
- PR #289).
- The history provider writes the final list back to `HISTFILE` at
- session end (niubash#265) — the missing exit half of the
- niubash#182/#250 GNU `-c` semantics: after a one-shot
- `niu -c "history -c"` the host file holds the empty list, like GNU
- bash's `exit_shell()` write. The rc-0 one-shot path returns from main
- instead of `process::exit` so the write runs.
- `sh -c` enters POSIX mode with `$0 = sh` (niubash#262): the installed
- `sh.exe` is the same forwarder binary as `bash.exe`, so Windows
- argv[0] could not carry the invocation contract to niu. The shim
- forwards its argv[0] stem as `NIU_INVOKED_AS` (actively scrubbed
- under every other name, so a nested `sh -c 'niu ...'` keeps its own
- face).
- Path-form expectations align with the engine's POSIX persona
- (niubash#283): the host cwd sync no longer re-stamps the executor PWD
- into native drive form, so the rendered form no longer depends on
- which route executed the command (simple sequences vs compound lines
- and command substitutions).
- Plain-text `--help` and a non-tty-safe banner (niubash#229), plus a warning
- when a hand-written rc would clobber the wizard theme.
- PROMPT_COMMAND exit jump now ends the session instead of returning to the
- prompt (niubash#232).
- PS0 expansion writes to stderr, matching GNU bash (eval.c:176) (niubash#233).
- Bracketed paste enabled with GNU bash multiline-paste semantics: pasted
- newlines no longer execute mid-paste (niubash#234).
- The bash-it theme history interlock (niubash#182) is closed: themes
- running `history -a && history -c && history -r` from PROMPT_COMMAND
- doubled the history file every prompt and wedged the session. `history
- -a` appends only what the file lacks (bashhist.c), `history -r` rebuilds
- the reader from disk without re-appending reedline's unsynced buffer,
- and `history -c` clears the in-memory list only — the backing file is
- snapshotted and restored instead of deleted
- (builtins/history.def:185). A ConPTY regression drives 12 prompt cycles
- of the exact theme pattern and asserts every command lands in the file
- exactly once (niubash#250).
- An unparsable plugin source registry is never silently rewritten
- (niubash#178): registry.toml bytes that fail to parse are snapshotted to
- `registry.toml.corrupt` and warned on stderr, and every registry write
- is refused — sync/install fail loudly instead of dropping the persisted
- pin commit, checksum and trust state riding in the file; install refuses
- before any fetch, and `niu doctor` names the parse error's line and the
- sidecar instead of "none installed" (niubash#246).
- Multiline paste on the Windows console no longer executes line-by-line
- (niubash#202): Windows gets no bracketed paste (ConPTY swallows the
- ?2004 markers), so a terminal paste arrived as per-line key events that
- executed as they came — a 200-line paste self-executed 186 times
- (176 KB of output, 15-17 s). A PSReadLine-style arrival-burst heuristic
- (`PasteChunkEditMode`, 50 ms inter-key gap, cfg(windows) only — Unix
- keeps the real bracketed paste above) buffers the burst, including bare
- Enters and the console's CRLF Enter pairing, and commits it as one
- insertion; the script executes exactly once on the user's Enter
- (paste-phase executions 186 -> 0; the issue's 257-line payload drops
- from >300 s timeout to ~4.6 s) (niubash#252).
- The setup wizard audit P2 batch (niubash#179): a fresh install journals
- rc_created so the finish screen's undo receipt covers the generated rc
- itself; duplicate undo receipts for one source dedupe; Ctrl-C at the
- niu-git question prints the same cancelled note as every other question;
- `niu plugin discover` enumerates the theme layer of trusted sources; zh
- translation keys realign with the runtime literals (now guarded by the
- vocab test); a gutted trusted source makes `niu plugin list`/`discover`
- name the repair verb (`niu plugin source verify <id>`) instead of
- silently showing no assets (niubash#239).
- `niu doctor` on Linux/macOS reports the Windows-only checks (winuxcmd
- core, command links, bash/sh shims) as n/a rows outside the critical
- tally instead of "1/3 critical checks passed"; the header names the
- platform and the terminal row falls back to `TERM` (niubash#194)
- (niubash#242).
- A raw ESC byte as the first character inside single quotes in a script
- file is preserved (niubash#200): the word-initial data ESC was claimed
- by the lexer's QUOTED_WORD_PREFIX marker and silently dropped; the
- engine lexer now raw-byte tags it (rubash#474) and integration tests
- pin the byte at the niu boundary across single/double-quoted, unquoted,
- ANSI-C and piped-stdin spellings (niubash#235).
Engine (rubash 1.4.0)
- Login shells: `-l`/`--login` (and an argv[0] starting with `-`) run the
- GNU startup chain on Unix — `/etc/profile`, then the first of
- `~/.bash_profile` | `~/.bash_login` | `~/.profile`; non-login
- interactive shells source `/etc/bash.bashrc` + `~/.bashrc`;
- `--noprofile`/`--norc` suppress the corresponding branch. Windows
- accepts the flags with unchanged behavior (no login concept).
- A bare-console `rubash` gets real readline line editing: a raw-mode
- console reader translates keystrokes through the engine's editor
- dispatcher — history recall (arrows, C-p/C-n), in-line cursor motion,
- C-r reverse search, per-keystroke redraw — and restores cooked mode for
- child processes.
- Command-substitution exit status is published to `$?` during expansion,
- like GNU: `echo "$(cd nope && pwd)" $?` reports 1,
- `false; echo "$(true) $?"` reports 0 (rubash#485).
- Compatibility families zeroed against the GNU 5.3.0 true-baseline
- suites: dbg-support (635→0), restricted rbash (194→0), invocation
- (BASH_ARGV0, login argv0, long-option table — 14→0), trap, function
- definition (POSIX funcname rules, AST printer), multi-operand
- `complete`; the 83-suite diff drops 3427 → 2072 lines (−40%), 32
- suites now byte-equal.
- History: `history -d start-end` range deletion (a GNU 5.3 feature), fc
- -s re-execution semantics; globstar `` fixes (non-adjacent patterns,
- zero-depth trailing slashes); array/assoc compound-assignment quoting
- fixes; same-line `#` comments after `f() {`; unquoted variable command
- words split by IFS in pipelines; `eval` inside command substitution
- re-parses like GNU; `set -u` arithmetic no longer flags assigned
- variables as unbound.
- Signal numbers unified on the Linux table (USR1=10, CHLD=17, RTMIN=34)
- so `kill -l`/`trap -l` match GNU 5.3.0; aarch64/armv7 Android compile
- against bionic; SIGPIPE no longer aborts the interpreter (`yes | head`
- streams like GNU).
- Utilities: GNU cp port (-r/-n/-i/-v/-p/-u/-t with buffered stderr),
- /bin/echo and /usr/bin/echo route to the buffered builtin channel;
- printf/ANSI-C `\u` partial-read and hex-width rules; locale subsystem
- (setlocale warnings, MB_STRLEN).
-
rubash 1.4.0 2026-10-10
新增
- 登录 shell 启动链(#487,PR #489):argv[0] 首字符 `-` 或 `-l`/`--login`(含 `-lc` 组合形态)置 login 标志后,Unix(`not(windows)` 门,覆盖 Linux 与 macOS)上登录 shell 依次 source /etc/profile → 首个存在的 ~/.bash_profile | ~/.bash_login | ~/.profile;非登录交互 shell 依次 source /etc/bash.bashrc(存在时)+ ~/.bashrc(--rcfile/--init-file 只替换个人 rc);启动文件中 `exit` 终止 shell 且后续不执行(与 --rcfile 一致)。`--noprofile`/`--norc` 分别抑制对应分支。Windows 无真实登录概念:-l/--login 接受且行为与现状完全一致(不读任何链文件)。链顺序/fallback 以注入式存在性检查做单元测试;Linux/macOS 端到端由 CI 对应 job 验证。
- 真 readline REPL(#419 readline 腿,PR #482):裸 `rubash.exe` 真控制台交互补齐 readline 行编辑——此前控制台分支 stdin 留在 cooked 行输入模式,编辑由 conhost 代管,shell 历史(↑/↓、C-p/C-n)、行内光标移动(C-a/C-e/←/→)与 C-r 反向搜索全部失效。新增 `RawConsole`(`src/console_readline.rs`):提示符与接受行之间切 raw 模式,按键逐事件翻译成引擎编辑分派器已有的字符流,编辑行每键重绘(raw 模式下终端不再回显);命令执行前恢复 cooked 模式,子进程仍见正常行纪律控制台。编辑分派器从读循环抽出为 `apply_edit_char`,cooked 字节流路径(#434 桥接语义不变)与 raw 控制台路径共享同一套绑定;PS1/PS2(含 `--rcfile`)渲染共用。补全(Tab)与 i-search 的 raw 态渲染为后续分层。
- `--version` 构建元数据(#488,PR #490):GNU license 块(保持逐字节不变)之后追加 `build: <git-hash> (<profile>)`——build.rs 编译期捕获 `git rev-parse --short HEAD`(git 不可用时回退 unknown,发布打包可 `RUBASH_BUILD_HASH` 钉值),profile 为 debug/release;Windows 附加一行 persona 说明:原生 Windows 构建、无 MSYS runtime,`*-pc-msys` MACHTYPE 不应读作 MSYS2/Cygwin bash 移植。
- GNU `cp` 移植:-r/-n/-i/-v/-p/-u/-t、递归、缓冲 stderr;`/bin/echo` 与 `/usr/bin/echo` 路由到缓冲 builtin 通道。
- `history -d start-end` 范围删除(GNU 5.3 特性)。
- `stty` builtin (PR #567): console mode queries and mutations without leaving the shell (`stty size`, `stty raw -echo`, ...), complementing the `read` fd-semantics work below.
- `bash --help` / `sh --help` fork to the GNU usage contract (#585, PR #596): the help screen follows the invocation name and prints GNU's usage contract — option table and exit status — instead of a product-generic help screen.
性能
- 性能三连(#437/#281/#242)与 bats-core 热点(#375)推进:
- #281 增量 reader 收官(PR #486,closes #281):续行扫描 admission-gate + 物理行二分戳、零拷贝参数体视图、逐行 batch-tokenizer 停靠(GNU read_token 流式模型),以及 fold 完成趟的 opener-state checkpoint——fold 趟只需支付组自身文本,不再从字节 0 重新 lex 整个累积缓冲(nvm 形状 4281 次全量趟中 19 次大重扫的 270 KB 归零)。
- #437 热点:算术循环 per-command boundary O(1) 化(数组追加与算术循环 micro-benchmark 报告的执行热点)。
- #242 执行器热路径(部分):named-event catch_flag 信号轮询、单遍 `${}` 算子索引 + 直接 simple-glob 匹配 + 算术快照容量、walker `${}` 臂 inline fragment 解析与模式快路径、allocation-free shopt 咨询、字面词快路径、comsub-scan O(n²) 修复等。
- #375 第一刀(部分):`$(cd X && pwd)` 命令替换路径解析单句柄化——一次 CreateFileW 同时取目录属性 + 物理路径(PR #484);`$(cd && pwd)×5000` 878→779 ms(−11%),bats_debug_trap 形状 −8%,输出逐位等价。
- perf-suite:专用基准 harness 与首个基线(24 probes,rubash vs WSL GNU 5.3.0)。
- Lexer construction wins(rubash#241,PR #554): fold-before-push moves, gated leading_ws capture, and inline TokStr strings — parse throughput up under alias-correct ground truth.
- Executor dispatch floor(rubash#375,PR #562): shape-only cd/pwd command-substitution admission and a DEBUG-trap parse-once cache retire the handle-table scale myth.
- Hot-path construction-only wins(rubash#242,PR #501): test argv zero-copy, gated fd marks, single word materialization, copy-on-write IFS strip, cached debug switches.
修复
- 命令替换的退出状态在展开期内即刻成为 `$?`(#485,PR #491/#494):GNU 在父壳回收替换子进程时就把其状态写入 last_command_exit_value,同一命令行内后置的 `$?`(后续词、同词后缀片段、同一简单命令的后续赋值 RHS)都看得见,成功的替换同样覆盖(`echo "$(cd nope && pwd)" $?` 报 1、`false; echo "$(true) $?"` 报 0、`v="$(exit 3)$?"` 得 v=3 且 rc=3),命令自身执行后照常覆盖。此前 rubash 只把状态记进 `last_command_substitution_status` 单元、仅在空词/赋值收尾时提升,词展开路径上丢失该时序。修复:AST/function 替换与 `&self` 包装器三个回落点的父侧回收处直接写 `exit_code`(fork 模型保存/恢复中的 `saved_exit_code` 不再回退状态),整词引号 `"$( )"` 快路径发布同一状态;反引号嵌词从遗留 `&self` 展开器改道可变嵌入 walker(与 `$()` 词共用生产路径与同一批提升点)。`local` 屏蔽赋值状态、`set -e` 下替换赋值失败中止(rc=1)而词内替换失败不中止(echo 自身 rc=0)均保持 GNU 一致。
- 函数定义与花括号组的同行 `#` 尾注释不再解析失败(#118):`f() { # note`、`f() { echo x; } # note`、`{ echo x; } # note`、`function f { … } # note`、TAB/空注释等形态此前被 lexer 把注释切进花括号组 token(`{ # note`),parser 因 `function_command.rs` 的 `value.trim() == "{"` 判定失败而回落成 `syntax error near unexpected token `('`。`skip_brace` 现在汇报配对 `}` 是否命中以及组内顶层注释起点,scanner 只把注释之前的文本作为 token 并把位置回退到注释处;`brace_close_can_end_compact_group` 也把 `}` 之后的行内 `#` 视为组结束。另修正 `skip_brace` 的 `comment_start` 初值,`f() {#note` 仍按 GNU 报语法错误而非静默接受。
- `set -u` 下算术展开不再把已赋值变量判为 unbound(#67):nounset 扫描器去掉了无法识别错误 token 时的合成 "syntax error in expression" 回退,并跳过赋值左值(`for ((i=0; i<n; i++))` 的 init/update 不再报 `i` unbound)。
- `set -u` 下算术展开的 unbound 错误与普通参数展开对齐:直接上下文中终止脚本,命令替换与管道段内只终止该子上下文(GNU expr.c expr_streval 的 FORCE_EOF 语义),并消除了随后把展开文本当命令执行的 `command not found` 级联。
- 管道中未加引号变量作命令字现在按 IFS 分词(#68):`v="echo hi there"; $v | cat` 以 `echo` 为命令名、`hi there` 为参数执行,管道任意段、子 shell 与进程替换内一致。
- 命令替换内的 `eval` 恢复完整重解析语义(#69):`x=$(eval "echo hi")` 得到 `hi`;移除把 `eval ` 前缀剥掉后当普通命令执行的捷径,`$(eval "$cmd" | sort)` 等真实脚本形态正常工作。
- dbg-support 全族归零(635→0):AND-列表双触发(execute_cmd.c 无 connection 节点火点)、source-scope trap 继承(source.def:208-216)、非行首 `{` 回归普通词法、for 每迭代行号重置。
- rsh 全族归零(194→0):`set +o restricted` 静默解除修复(set 快速路径跳过 GNU 拒绝检查)、set +r 文案/退出码、BASH_CMDS 赋值守卫、hash -p、管线成员斜杠拒绝、受限只读集、argv0 rbash 自动受限。
- invocation 全族归零(14→0):BASH_ARGV0 环境导入 $0/shell_name、login-shell argv0、长选项表、-o/-O 启动期报错 prolog、--pretty-print。
- trap 归零(3→0):ERR action $LINENO 绑失败命令行、SIGCHLD 通知排队重放、后台子进程剥离继承 trap 表、traced 函数 DEBUG 行号用 body_open_line。
- func 全族归零(58→0):POSIX funcname 规则、AST printer 移植、special-builtin 优先级。
- complete 全族归零(115→0):多操作数 compspec 注册(`complete -F f c1 c2` 双双生效)。
- history 改善(190→127):HISTIGNORE harness 修复、fc -s 语义对齐、命令替换内 fc/session 历史路由(`history -d start-end` 范围删除见新增)。
- globstar 改善(182→101):非相邻多个 `` 重复发射修复、相邻 `` 折叠后零深度目录尾斜杠。
- array/assoc 改善(444+358→246+242):复合赋值引号分组四层修复(解析器 RAW 合并、declare 操作数收集器、Word 臂原子复合、执行器逐字守卫)、元素赋值词边界、嵌套引号 patsub 逐元素替换。
- unicode 修复:printf 和 ANSI-C 引号中 `\u`/`\U` 部分读取保留、精确十六进制位数要求。
- locale 子系统:从 feat/locale-subsystem 合入,setlocale 警告、MB_STRLEN 长度。
- comsub/pipeline:pathname-expand 替换、管线 word list、assoc hash order 共享 join。
- assignment:全单引号 RHS 为字面数据、元素赋值词永不分词、转义引号内下标为数据。
- nameref:无值 nameref 接受有效目标赋值。
- SIGPIPE 语义:管道上游写端遭遇 broken pipe 不再中止解释器,`yes | head` 等流式管线行为与 GNU 一致(#455)。
- 输入规范化(粘贴语义):桥接层送达的交互式输入行在进入引擎前规范化 C0 控制字节(#458)——终端粘贴携带的 NUL/控制字节不再以未定义方式进入词法。
- source 边界的语法错误不再终止会话/脚本(#451,PR #481):sourced 文件的语法错误按普通失败中止剩余文本(GNU evalstring.c 语义)——交互会话报错后继续读下一条,非交互脚本继续执行后续命令;此前中止被误分类为 reader exit jump,交互 shell 在诊断后立即结束(ConPTY 下表现为 source 后首条命令永不执行),非交互脚本以 rc 2 中止。
- PS1 `\[`/`\]` 忽略标记在所有渲染路径剥离(#431,PR #480):oh-my-bash inretio / bash-it 主题形状下不再把标记渲染为字面文本或泄漏 raw `\x01`/`\x02` 字节;`${var@P}` 提示符变换同步剥离;真实 ESC 颜色序列与光标定位几何不受影响。
- 赋值前缀 + 子 shell 的 GNU 形态(审计语料 #452 集群1,PR #453)。
- `complete -F` 接受带连字符的函数名(#427,PR #454)。
- 复合体内 `]]` / `(( ))` 之后无终止符的 then/do 被接受(#460,PR #475)。
- 函数定义名含未引号 `$( )` 被接受(#462,PR #476)。
- `=~` 正则词片段永远是词数据,不再被误判为保留字(#461,PR #473)。
- 嵌套 keyword-form 函数体计入组 opener 追踪,不再提前 EOF(#465,PR #472)。
- 词附着的多行进程替换体跨行 join(#463,PR #483)。
- 词首数据 raw ESC 字节(如单引号首字符)不再被吞(niubash#200,PR #474)。
- ERR trap 不再对词/赋值展开错误触发(PR #467)。
- Unix 上进程替换 fd 作为外部命令参数传递(PR #468)。
- coproc 子进程继承父函数表快照,DEBUG trap 函数可解析(#441,PR #469)。
- 引号整词默认展开保持单一复合元素(PR #466)。
- Pipeline members now honor shell functions — `date(){ ...; }; date | cat` runs the function, `echo(){ printf ...; }; echo hi | cat` shadows the builtin: two dispatch sites classified a pipeline stage before any function lookup, where GNU resolves functions before the PATH search in every stage (#587, PR #594).
- A `PATH=` assignment prefix owns command resolution for its own command — `PATH=/opt/bin:$PATH git status` resolves `git` through the new PATH within that same simple command, including the pipeline and concurrent fast-path stages (#588, PR #594).
- `cat`/`mkdir`/`touch`/`cp`/`rm`/`rmdir` stop re-expanding already-expanded argv (P0 pair) — `v='*.txt'; cat $v` silently read the wrong file and the mkdir/touch/cp/rm/rmdir family created or removed wrong paths; the builtins no longer re-run expansion over words the shell already expanded (#511, PR #552; #555, PR #558).
- `$PWD`/`pwd` render the POSIX persona form unconditionally — the native-drive-style hatch is retired; it starved nvm's find_up loop, and native drive spelling now survives only at the Win32 spawn boundary per the path contract (#537, PR #556).
- GNU semantics waves (each batch issue-locked and WSL-verified against GNU 5.3.0):
- Wave 1 (PR #561): trap `exit` unwind, pipeline redirect rejection, nameref unbound gating, brace-expansion shape validation, redirect `set -u`, arithmetic latch (#524 #525 #529 #532 #535 #536).
- Wave 2a (PR #560): SIGQUIT ignored in non-interactive shells, `-s` argument scan order, readonly attribute direction, `declare -na` structure wins, ERR single-fire guard.
- Wave 2c (PR #573): RANDOM reseed on `declare -i`, heredoc-max fatal ordered before the unclosed-substitution scan, export/array/dynamic-variable/encoding semantics (#533 #534 #538-#543).
- Wave 3 (PR #569): xtrace quote fidelity, redirect message rendering, `caller` frame limits, ERR trap function-entry, extdebug DEBUG-skip sites, `kill` signal names (#517-#522, #550).
- Wave 4a (PR #576): redirect ambiguity gate, backtick diagnostic window, command-position paren, compgen tilde expansion (#514-#516, #523).
- Wave 4b (PR #578): `$[ ]` EOF scan, `declare -aA`, procsub fd window, trap listing/DEBUG freeze, `bash -o` listing, jobs/alias parameter semantics (#543-#547, #549).
- `read` console/fd semantics — empty-line exit status, fd 0 file-loop ownership, carrier-byte domain normalization, dup-stdin console reads, fractional `-t` deadlines, and the dup-fd ladder behave like GNU (#275-#282, PR #567).
- `-s` stdin-script reads on demand through the canonical library driver — the parser consumes stdin one command at a time like GNU's non-seekable stream, so a `$(exec cat)`/`read`/top-level `exec cat` consumer sees exactly the bytes after the last submitted command and the unconsumed tail never executes as code (the `curl | bash -s` install-script hazard). The driver moved into `script_driver::run_stdin_script`, so embedding hosts get the same contract instead of slurping (#586, PR #593). Related: an exec'd child keeps the shell's stdin channel in every context (#575, PR #583).
- CLI entry-shape semantics — POSIX special-builtin/assignment fatals map per entry shape (`-c` exits 127 like GNU `run_one_command`, scripts/stdin keep 1, subshell-contained stays 1); the shim forwards the bash/rbash invocation faces so `$0` reports the invoked name (and `sh`/`ash` keep the POSIX-mode contract) (#589 #590 #591, PR #598; #504).
- `eval` EOF-shape probe is comment-aware — restores the modernish init battery (the #318 `unclosed_comsub_eof_shape` regression) (#282, PR #572).
- Multiline `[[ ]]` with folded `(( ))` continuations parse like GNU, the `bind -v`/inputrc engine surface is honored, and the #433 rc regression ledger is pinned (PR #571).
- Arithmetic-for sections evaluate the verbatim `(( ))` capture and carrier bytes never reach eval-error echoes (#548, PR #584).
- Compound redirection failures reach the ERR trap again (the body-exemption gate was type-blind, PR #557), and the redirect expansion-error verdict propagates on the function fast path (PR #579).
- P1 semantic batch — errexit body adjudication, stdin parse-error status, printf write-failure status, dirstack `EX_USAGE` (PR #553).
- POSIX-mode coverage audit completed — 42/75 audited GNU posix-mode behaviors implemented: fatal assignment families, redirect no-split, `time` fd-2 routing, trap usage exits (PR #559).
- Diagnostic fidelity — the C-family message formats match GNU: stdin line offsets, dolbrace scanning, PST_REGEXP two-stage EOF, outermost unclosed-block line naming, write-error prefix, and the `[[ =~` two-part diagnostic (#551, PR #563); a top-level unclosed procsub continues to the next line and `[ (` is reported as a diagnostic token (#564 #565, PR #577).
- Windows path contract — POSIX drive-form operand pre-translation now covers every non-shell-wrapped child class with context/position/existence invariance: `/d/...` operands work the same inside loops, subshells, functions, pipelines and command substitution, for to-be-created targets and flag values, while data operands (grep/sed/awk patterns, `basename`/`dirname`) stay verbatim (#592, PR #600); `/mnt/c` is retired from the verbatim loop per the #568 adjudication (PR #574); bare drive-root POSIX forms are accepted (#566, PR #577).
- 回归钉子(无用户可见行为变化):sourced `exit N` 终止语义(PR #457)、函数定义花括号行 token 语义(PR #459)、#424 重定向目标拒绝与 procsub 输入侧(PR #464)。
更新
- GNU Bash 5.3.0 兼容性大幅推进。83 套件 true-baseline 总差异从 3427 行降至 2072 行(−40%),零差套件从 31 增至 32。
平台
- Android 目标:aarch64/armv7 android 目标可针对 bionic 编译(#456,`sa_restorer` 平台门修正);kill/trap 信号表在 android 上同样编译 Linux 编号的 1..31 槽位(bionic 与 Linux 内核编号逐值一致)——此前 `kill -l 17` 在 android 上输出为空、`trap -l` 列出 bionic 不用的 BSD 名;RT 字面量块保持 glibc 契约的 Linux 专属(PR #492)。
- 平台门全量审计(PR #492):src/ 下 511 处平台 cfg 盘点分类;确认无「漏 macOS 腿」同类问题,真 Linux 专属门(RT 34..64 字面量、`sa_restorer` 等)保留。
- OpenHarmony / musl:`ulimit` 针对 musl 与 OpenHarmony libc 编译修复(PR #449)。
- 信号编号统一:rubash 全表从 BSD/Cygwin 风格切换到 Linux 表(USR1=10、CHLD=17、RTMIN=34),与 GNU 5.3.0 WSL 契约一致;kill -l/trap -l 输出逐字节对齐。
- Residual Windows assumptions are gated outside `cfg(windows)` so non-Windows builds stay honest (PR #499).
文档
- 重写 README.md 和 README.zh-CN.md:数据驱动的兼容性展示、近期修复表、架构概览;其后一次事实保持的风格微调(PR #495)。
- 更新 COMPATIBILITY-STATUS.md:2026-09-11 全量 true-baseline 重跑结果、总体结论刷新。
- 归档过时文档 7 个(gnu-bash-compatibility-implementation-plan、bash-implementation-inventory、bash-source-map、performance-debugging-process、typed-expansion-migration-checkpoint、source-layout、HANDOFF-20260829)。
- README restructured again, evidence-first, with the cross-platform identity (EN+zh) (PR #500); the three boundary invariants — native core, POSIX forms, verbatim fidelity — are codified as contract tests plus a windows-path-contract document and proxy-driver guide (PR #570).
- Platform gap documented: foreign children cannot receive exec-established numbered fds (#378, PR #580).
- Baseline numbers (PERF/CORPUS/COMPAT/LINUX) extracted from archived status ledgers (#477, PR #581); the debugger M0 design (DAP-over-stdio) is published (#497, PR #582); dangling COMPATIBILITY-STATUS references are repointed to the archived ledger (PR #502); the GNU suite ledger closes at 83/83 with the nameref residual proven to be GNU's own coproc reap race (PR #506).
构建
- crates.io 打包瘦身:发布包不再携带 `tests/` 与 `third_party/`(参考源码与测试语料,仅本地开发/CI 需要),包内容 2053 个文件 / 15.6 MB → 400 个文件 / 8.9 MB,回到 crates.io 10 MiB 上限之内;`exclude` 同步移除失效的 `!third_party/bash/tests/*.right` 反规则。功能与构建不受影响(`cargo publish --dry-run` 验证通过)。
- Internal: twelve ghost module skeletons that never entered the compile tree are removed (PR #597); stale regression snapshots are re-baselined with an airtight snapshot env and a CI golden-refresh artifact (PR #496).
-
niubash 1.3.4 2026-10-05
Added
- Theme gallery live prompt preview (isolated render, 1.5s bound) in the
- wizard and `niu plugin ui` (verb now registered) - highlight moves, the
- pane below renders that theme's actual prompt.
- Collections install curated independent recipes (complete-alias,
- fzf-git.sh, bash-sensible, git-flow-completion - upstream-audited).
- 178/178 winuxcmd applet completions embedded (zero-config Tab completion
- for every bundled command; user TOMLs keep highest priority).
- The golden user journey is a required release gate: install -> wizard ->
- trust -> theme -> new terminals -> daily battery, plus persistence,
- wizard-rerun, spec hand-editing and remove-active-source phases
- (`--phases all`), all driven from the tag's own fresh build.
- Per-asset timing budgets (437 themes/plugins/completions, zero
- sampling) wired as a second release gate; budgets ratchet down only.
Fixed (engine rubash 1.3.4)
- Theme choices survive `source` and new terminals: the wizard/gallery pick
- is pinned into the spec, sync reconciles same-name claims toward the rc
- (the "activated/deactivated" revert is gone), and the defaults-as-floor
- never resurrects a framework over a claimed theme.
- Multi-line theme prompts place the editing cursor at end-of-input-line:
- the theme's right-align cursor surgery is served through the line
- editor's own right-prompt channel (escape-excluded width math).
- A 6-second first prompt after picking `full` is gone: startup fetches run
- under a hard 3s budget (kill-on-close job object, credential-prompt
- guard) and memoize; in-sync machines never fetch at all.
- `tr -d/-s/-c` in pipelines actually runs (the translate fast path had
- read `-d` as a character set); `for f in "${arr[@]:-}"` yields
- per-element words; `/usr/bin/seq`-style file operands resolve through
- `.exe` existence (directory forms unchanged); parser throughput +13%
- (nvm -n 18.0x -> 15.6x vs GNU).
Fixed (product, found by the audit lanes and the golden journey)
- add/rc audit P2 batch (niubash#176): `niu plugin add -h` is a usage
- request (was an unknown-option error); the plugin usage and the add usage
- document `--url`; a local directory named like a catalog id
- (`oh-my-bash/`) is adopted as a local tree instead of being hijacked to
- the catalog's GitHub origin; cwd-relative path targets are stored
- cwd-independent (normalized absolute, forward slashes) in the spec; the
- already-declared refusal points stranded declarations at
- `niu plugin sync --prune` instead of a remove that would fail; user lines
- hand-written inside a managed rc block are migrated out with a warning
- instead of being silently dropped; a CRLF rc keeps its line endings
- through block rewrites; hand-edited near-marker blocks are repaired
- instead of duplicated; every plugin verb that rewrites the rc backs it up
- first and the backup set is pruned to the 10 newest.
- `plugin add --checksum` is honored through add and adopt; failed adds
- roll back their spec entry and legacy strands are pruned; enable/disable
- surface rc-write failures and collapse duplicate managed blocks.
- The wizard rc's bootstrap line references the running binary
- (`${NIU_SHELL:-niu}`), so a stale niu on PATH can no longer hijack it;
- doctor warns when the PATH-resolved niu differs from the running exe.
- `--help`/usage/docs truth pass (ui/recipe/distro/mirror/source/rollback
- verbs listed; sync --adopt documented; WinHttp 12175 gets a reason).
-
niubash 1.3.3 2026-10-04
Fixed (engine rubash 1.3.3)
- Keystrokes typed while the prompt rebuilds are no longer eaten: external
- children spawned by PROMPT_COMMAND (the theme clock's date/git/awk probes)
- used to consume the first pending console input record - a human typing
- right after a prompt refresh lost their first key (`echo` -> `cho`). A
- typeahead guard now sweeps pending input during prompt machinery and
- reinjects it verbatim before the editor reads.
- `tr` with options in a pipeline actually runs (`tr -d b` was silently
- passed through - the translate fast path had read `-d` as a character
- set).
- `for f in "${arr[@]:-}"` yields per-element words like GNU (bash-it
- theme loading works).
- Parser throughput: nvm -n ~13% faster (backslash-continuation scan
- admission gate + binary-search line stamping), GNU ratio 18.0x -> 15.6x.
-
niubash 1.3.2 2026-10-04
Engine fixes
- Alias expansion no longer leaks into compound array assignments OR
- `[[ ]]` conditional / extglob pattern words - the oh-my-bash +
- bash-completion interactive corruption family is closed (the
- `bash_completion: line 1376` syntax error).
- Single-quoted words keep their integrity through pipeline and
- command-substitution stages (three stacked root causes fixed).
- `cat` applies all formatting options when reading a pipe (`cat -n`
- in a pipeline numbers lines).
- `cd` reaches external children (interactive reader no longer restores
- the process cwd per line).
- `-c` subshell fatal expansions exit 1 like GNU, not 127.
- `\u` renders the OS account name, env-independent.
- Virtual system-root arguments (`/usr/bin`, `/etc`, `/tmp`, ...) are
- resolved through the root map for every child class.
- Console-attached bare rubash renders the real PS1/PS2 channel instead
- of a placeholder REPL (GNU interactivity rule, error survival, exit
- codes).
First three-platform release (engine rubash 1.3.2)
- Linux (x86_64 + aarch64, glibc) and macOS (arm64 + x86_64) portable
- tarballs join the Windows packages. Every platform's build job
- smoke-runs the binary it ships, on that OS, before any artifact is
- attached. Windows asset names and the self-update link logic are
- unchanged.
Product fixes (found by the new golden user journey gate)
- Same-name themes (powerline-multiline exists in both frameworks) now
- route to the owning framework's native loader and actually apply.
- The curated bash-preexec recipe entry matches the real upstream file;
- all curated entries are pinned against upstream roots.
- A failed collection apply journals and reports the failure honestly
- with the retry command.
- The golden user journey (fresh install -> wizard -> trust -> theme ->
- new terminals -> daily battery) now runs as a required release gate.
-
niubash 1.3.1 2026-10-03
Fixed
- Interactive arithmetic corruption (engine rubash 1.3.1): aliases no
- longer expand inside compound array assignments. Under oh-my-bash, the
- convenience alias `1='cd -'` leaked into the literal
- `OMB_VERSINFO=(1 0 0 0 ...)` and corrupted it to
- `(_omb_directories_cd - 0 ...)`, killing version arithmetic with
- `-: arithmetic syntax error` in interactive sessions (any theme,
- powerbash10k included). GNU parity: assignment words are never
- alias-expanded (parse.y parse_compound_assignment keeps the whole
- `( ... )` in one ASSIGNMENT_WORD token).
- Plugin sync state machine: the startup "installed but not declared"
- nag now prints only when a spec exists (imperative mode is silent);
- `niu plugin sync --adopt` declares installed sources with a
- round-trip-stable snapshot (enablement + theme); the setup wizard ends
- spec-managed; `niu plugin add` adopts an already-installed source
- instead of erroring "remove it first".
- No re-download loop: the startup bootstrap memoizes failed installs
- (per origin+ref ledger); retries happen only on an explicit
- `niu plugin sync`. The bash-it fingerprint now matches the real
- upstream layout (its `lib/composure.bash` was removed upstream).
- `niu plugin source remove` also drops the spec declaration (no
- resurrection), and duplicate spec declarations of one source merge
- instead of flipping the rc block every sync.
-
niubash 1.3.0 2026-10-03
Download retraction (BREAKING)
- niu no longer downloads anything except git clones: ureq, flate2,
- tar and zip are gone, the download module is deleted, and the binary
- shrinks ~16%. The smoke suite asserts a zero-network surface.
- Fonts become detection + recommendation (winget nerd-fonts packages,
- scoop, brew, nerdfonts.com).
- Executable tools become package-manager recommendations only: `wpm`
- first on Windows, native managers (apt/dnf/yum/brew) elsewhere.
- The `niu plugin tool` verbs are retired.
Engine (rubash 1.3.0) and bundled components
- TMPDIR child boundary fixed: invented defaults stay shell-only and a
- genuinely exported TMPDIR crosses in Windows-native form - Bun-
- compiled TUIs (opencode etc.) launch again.
- Associative kvpair compound assignments, `:` in redirect filenames,
- command-substitution case-depth regions, physical-line diagnostics
- across continuations, and spawn diagnostics preserved verbatim.
- WinuxCmd 1.1.5 bundled: xargs stops its own option parsing at the
- utility name; mv/mktemp/cp/ls accept POSIX drive-form operands; yes
- dies with 141 on a broken pipe like GNU coreutils.
Fixes / tests
- Unix cross-check compile break in the setup wizard (a baseless
- cfg gate); a one-character zh translation key mismatch.
- Release smoke suite now 17 legs, fully offline, including the
- one-run wizard journey under ConPTY.
Plugin system - the bash ecosystem, managed
- Declarative spec `~/.niubash/plugins.toml` (single source of truth) +
- `niu plugin sync` reconciliation, startup bootstrap, and a
- commit-pinned lockfile - the lazy.nvim spec/lock conventions.
- Any-plugin generality: `niu plugin add <owner/repo | url | path>`;
- source adapters for oh-my-bash, bash-it, bash-completion and bpkg;
- framework assets enable only through their native loaders (loader
- fidelity - no shims, no bypass paths).
- Recipe index (500+ curated rows across managers/themes/plugins/
- completions/prompts), collections/distros (`niu plugin distro
- import`), menu TUI (`niu plugin ui`), read-only `niu plugin discover`.
- Mirror pipeline: transport-layer git `insteadOf` rewriting
- (`niu plugin mirror set <url>`); lockfiles keep canonical URLs.
- Trust model: tree-checksum verification, explicit `niu plugin trust`,
- and a trust question in the setup wizard.
Setup wizard - one-run out-of-box
- Plugin collection page (recommended = oh-my-bash + its default theme
- completions; full adds the second framework and bash-preexec with
- fzf/starship as suggested installs).
- Post-install theme pick: after a collection applies, the wizard asks
- the trust question and then offers the freshly installed themes - a
- complete look in ONE run, no second `niu setup`.
- defaults-as-floor: enabled external frameworks own the prompt; the
- built-in default is the floor, never an override.
-
WinuxCmd 1.1.5 2026-10-03
Fixed — upstream lane wt41 (2026-10-02)
- mv: operands in MSYS drive form (`/d/x`) are routed through the
- shared API-path boundary like cp/install, so mixing Windows-form and
- POSIX-form operands in one invocation works in any combination (#1140,
- unixwin/niubash#124); GNU coreutils under MSYS accepts either dialect.
- The copy fallback also no longer mislabels regular files as directories,
- actually moves directories across volumes (they used to report success
- while doing nothing), refuses to move a directory into itself
- (`cannot copy a directory, X, into itself, Y`, GNU copy.c:2091), and
- honors a trailing separator on DEST (`failed to access 'DEST': Not a
- directory` instead of silently creating/overwriting the stripped name).
- mktemp: the printed name keeps the `-p`/`--tmpdir` operand's dialect
- (`-p /d/...` echoes `/d/.../x.XXXXXX`, `-p /cygdrive/d/...` likewise),
- so shell round-trips that capture the output stay self-consistent with
- POSIX-form variables (#1141); Windows-form operands and the TMPDIR/
- default-temp paths keep the native display form.
- xargs: option parsing stops at the utility name; every argument
- after it is passed to the utility verbatim (POSIX xargs operand
- semantics, GNU findutils) — `xargs cat -n` no longer errors on `-n`,
- `xargs wc -l` no longer silently consumes `-l` as `--max-lines`, and
- `xargs echo -- help` keeps the `--` (#1139).
Fixed — upstream lane wt48 (2026-10-03)
- cp: every stat/open route now goes through the shared API-path
- boundary (`native_path::make_api_path_operand`, the mv/install
- invariant), so POSIX-drive-form operands (`/d/x`, `/cygdrive/d/x`) work
- in any combination with Windows-form operands instead of failing
- `cannot stat ... No such file or directory` at the source-existence,
- destination-directory, `-t`, same-file, symlink, and recursive-child
- gates (#1145, unixwin/niubash#124 Option B). `cp -s` stores the
- dialect-resolved source spelling as link text (a typed `/d/x` link text
- can never resolve on native Windows).
- ls: file operands and `-d` directory operands resolve POSIX drive
- forms — `std::filesystem::absolute` used to fold `/d/x` against the
- current drive root (`D:\d\x`) before the boundary could convert it
- (#1145). A bare root operand (`ls /`, `ls -d /`, `ls //`) lists the
- current drive root (the platform's `/` mapping, consistent with
- `cygpath -w /`): the `\\?\` spelling of a drive root is rejected by
- attribute probes, and the root/`\*` enumeration pattern is now built
- with a path-aware join (`GetFullPathNameW` mis-parses the concatenated
- `/\*` form).
- native_path (shared boundary): `normalize_api_operand_w` folds the
- Cygwin drive prefix (`/cygdrive/d/x`, both separators) into the MSYS
- spelling before drive-letter conversion, giving every command that
- already routes through the boundary (cat, cp, install, mv, ...) cygdrive
- support uniformly; `to_extended_path` keeps bare drive roots un-prefixed
- and folds separators before `GetFullPathNameW` (#1145).
Fixed — upstream lane wt50 (2026-10-03)
- yes: a broken-pipe write death now reports exit status 141 (the
- SIGPIPE emulation other streaming tools use) instead of exit 1, so
- `yes | head -n 3` shows 141 on the producer side exactly like GNU
- coreutils (#1142). Non-pipe write errors keep the fatal exit-1 path.
-
niubash 1.2.5 2026-10-02
Fixes
- `$(case y in (b|case) ...)` family (rubash #380, P0): the comsub
- body is a fresh command stream — its first word is at command position
- — and the esac keyword rule looks at the PREVIOUS token, not forward
- evidence. All paren-list keyword patterns now parse and run.
- posix round-trip stickiness (rubash #383): the set_posix_mode
- walk is ported — enable arms inherit_errexit and friends, disable
- resets only the two GNU resets (inherit_errexit stays sticky).
- SIGPIPE-shaped lingerer status (rubash #382): `yes | head -3;
- echo ${PIPESTATUS[0]}` prints 141 like GNU.
- Fatal expansion inside $( ) under -c (niubash #154): reports 1
- (EXECUTION_FAILURE), not 127.
- Foreign Git Bash PS1 discarded (niubash #117): a PS1 carrying
- `__git_ps1`/MSYS title escapes is unset before rc — the own theme
- renders, no more command-not-found per prompt.
- Test suites rebuilt against GNU 5.3.0 probes (rubash #373:
- 88 reds -> 0; #374 first pass 174 -> ~119).
Performance
- Pipeline-floor round 2 (rubash): __RUBASH_CURRENT_LINE single-writer
- $_ equal-skip rebind — p-null -4.7%, p-f1 -5.7%.
- Engine bump: niu 1.2.5 builds against rubash 1.2.5 (c2f8e8a6).
-
niubash 1.2.4 2026-10-01
Fixes
- Distributable binaries no longer require VCRedist: release builds
- now statically link the CRT (`-C target-feature=+crt-static`). niu.exe
- previously imported VCRUNTIME140.dll — not an OS component — so on a
- clean Windows without VCRedist 2015+ the loader failed with
- STATUS_DLL_NOT_FOUND (0xC0000135) before any shell code ran. This was
- the winget validation sandbox failure on winget-pkgs#437563 (and the
- same exit code had masked it behind the portable-tree issue in #150).
- Imports are now OS in-box DLLs only (verified with objdump).
-
niubash 1.2.3 2026-10-01
Fixes
- External-pipeline data loss (#141, #155, P0): the Windows
- broken-pipe hard-kill window for non-final pipeline members armed
- unconditionally at call time, killing healthy producers mid-stream
- while their consumer was still reading (`seq 200000 | wc -l` returned
- ~17k with rc=0, drift per run). The window now arms only after the
- downstream member is observed to have exited, plus a 100ms natural-exit
- grace (rubash 2c781657). Verified: seq 5000000 x5 full count;
- `yes | head` lingerer termination intact.
- `$()` children see pre-opened fds 3/4 (rubash #368): the nvm-exec
- `3>&1`/`1>&4` juggle protocol holds; fd-1 dup snapshots escape the
- substitution to the real stdout.
- `${assoc[*]@A}` keeps the assignment body (rubash #371);
- case-pattern keywords stay inert (rubash #372, issue308 residue
- green).
- Engine bump: niu 1.2.3 builds against rubash 1.2.3 (a6eb8451).
-
rubash 0.3.0 2026-08-22
修复
- 修正关联数组算术下标的引号处理;带引号的 `$()` 和算术下标不再被错误地预展开。
- 改进数组元素下标诊断,对空、`*` 和负下标报告与 Bash 一致的错误,同时保留参数展开的非致命行为。
- 修正参数展开扫描器对单引号内 `}` 的处理,并拒绝 Bash 不支持的空格包围命令替换形式。
- 修正算术条件错误 token 的渲染,以及 `enable -d` 无效 builtin 的使用状态和退出码。
- 保留进程替换、coproc、printf 溢出和 extglob 解析等近期修复的回归覆盖。
- alias 展开为 `if ...; then`、`while ...; do`、`for ...; do` 等复合语句前缀时,会继续拼接到匹配的 `fi`/`done` 后重解析执行,补齐更接近 Bash 的 parser-level alias 行为。
- 赋值 RHS 紧邻 `<(...)` 进程替换时会作为赋值值解析并物化为可读路径,补齐 `p=<(cmd); cat "$p"` 这类 Bash 语法框架。
- 赋值 RHS 中的 `>(...)` 输出进程替换会登记为可写路径,并在后续重定向写入该路径后执行替换命令。
- `[[ -e <(...) ]]`、`[[ -p >(...) ]]` 等条件文件测试会识别进程替换操作数的 pipe/fd 形状,补齐条件命令中的进程替换文件测试框架。
- `for`/`select` 词列表中的 `<(...)`/`>(...)` 会先物化为可读/可写路径,再作为循环变量值使用。
- `case` 匹配词中的 `<(...)`/`>(...)` 会先物化为路径,再参与 pattern 匹配。
- 同一 word 中混合 `$((...))`/`$[...]` 算术展开和参数展开时,按 Bash 的从左到右顺序保留自增等副作用。
- `test -v` 与 `[[ -v ... ]]` 会识别 `RANDOM`、`SECONDS`、`BASH_COMMAND` 等动态 Bash 参数的已设置状态。
- Windows/Git Bash 环境下 `test -O`、`test -G` 与 `[[ -O/-G path ]]` 对当前用户创建的现有路径返回成功,补齐所有权条件 unary 的跨平台框架。
- `case` pattern 中被引号保护的 `*`、`?`、`[` 等 glob 元字符会按字面量匹配,变量在双引号 pattern 中展开后也保持字面匹配语义。
- 关联数组 key 含 `]` 时会在内部存储和 `declare -p` 输出中强制加引号,避免 `declare -A a=([x] one)` 这类交替 key/value 语法把字面 key `"[x]"` 误解析为下标 `x`。
- alias 引入复合语句前缀时,匹配 `fi`/`done`/`esac` 会按嵌套复合语句深度处理,避免内层 `if` 或循环提前截断外层 alias 语句。
- alias 引入 `time` 前缀并计时复合命令时,同样按嵌套深度收集被计时的 `if`/循环主体,避免内层结束词提前截断 `time` 语句。
- alias 引入 `case` 时,case clause body 边界会跳过内层 `case ... esac`,避免内层 `;;`/`esac` 提前截断外层 clause。
- alias 值提供 `function name` 前缀、函数体由后续 `if` 或循环复合命令提供时,会继续收集到匹配结束词后重解析为函数定义。
- 输出方向进程替换作为普通参数传给管道外部命令阶段或函数调用时,会物化为可写路径,并在阶段/函数结束后执行替换命令。
- 管道中的复合命令和函数阶段改为子 shell 风格执行,避免 `cd`、函数定义和算术赋值等状态泄漏到外层 shell。
- `builtin` 命令统一走完整内建分发表,避免无重定向调用时漏掉 `let`、`read`、`mapfile`、job control 等已接入内建命令。
- `shopt -s lastpipe` 开启后,管道最后一段可在当前 shell 执行,使 `read` 和 `while read` 等 final stage 状态更新保留到外层。
- `coproc` 默认管道的父子端接线修正,并将 coproc 子进程纳入 `wait` 可识别的作业表,避免 brace body 在 Windows 上出现 stdio 访问错误。
- 反引号命令替换外部的转义反引号会保留为字面量,反引号命令替换内部的转义反引号分隔符可用于嵌套命令替换。
- 命令替换内部的简单 word splitting 会将嵌套 `$()` 保持为同一个 word,避免 `$(echo $(echo nested))` 被内部空格拆碎。
- `printf -v` 支持将格式化结果写入 indexed array 和 associative array 的元素目标。
- `printf -v arr[subscript]` 的 indexed array 目标会按 Bash 算术规则解析 subscript,并支持已有数组的负下标。
- `compgen -W` 支持从 wordlist 生成匹配候选,并接入 `-P`/`-S` 前后缀和无匹配返回状态。
- `compgen -V varname` 支持将生成的候选写入 indexed array 变量而不是输出到 stdout。
- `compgen -A builtin` 和 `compgen -A keyword` 会生成内建命令/保留字候选,并支持 prefix 过滤和 `-P`/`-S` 包装。
- `compgen -b` 和 `compgen -k` 会分别按 Bash 的短 action 形式生成内建命令与保留字候选。
- `compgen -A signal` 会复用当前 `trap`/`kill` 支持的信号名称表生成候选。
- `compgen -A shopt` 和 `compgen -A setopt` 会复用当前支持的 `shopt` / `set -o` 名称表生成候选。
- `compgen -G` 会展开 glob pattern 生成路径候选,并支持 `-P`/`-S` 包装和无匹配状态。
- `compgen -X` 会过滤已生成候选,支持 `!pattern` 反向过滤,并保留 Bash 的候选生成状态语义。
- `compgen -d` 和 `compgen -A directory` 会从文件系统生成目录候选,并支持 prefix 过滤、`-P`/`-S` 包装和 `-X` 过滤。
- `compgen -f` 和 `compgen -A file` 会从文件系统生成文件名候选,包含普通文件和目录,并支持现有候选过滤与包装流程。
- `compgen -A helptopic` 会复用 `help` 主题表生成帮助主题候选。
- `compgen -A enabled` 会生成当前支持且未被禁用的启用内建命令候选。
- `compgen -A disabled` 会基于 `enable -n` 状态生成禁用内建命令候选。
- `compgen -j` 和 `compgen -A job` 会基于当前后台 job 表生成 job 命令候选。
- `compgen -A running` 会基于当前后台 job 表生成运行中 job 命令候选。
- `compgen -A stopped` 接入停止状态 job 候选框架,当前无 stopped job 状态时保持空成功。
- `compgen -A hostname` 会基于当前 shell 的 `HOSTNAME`/`COMPUTERNAME` 生成主机名候选。
- `compgen -A binding` 会基于 GNU Readline 默认函数名表生成 binding 候选。
- `compgen -u` 和 `compgen -A user` 会基于当前 shell 的 `USER`/`LOGNAME`/`USERNAME` 生成用户候选。
- `compgen -g` 和 `compgen -A group` 会基于当前 shell 的 `GROUP`/`GROUPNAME`/`USERDOMAIN` 生成用户组候选。
- `compgen -s` 和 `compgen -A service` 会基于当前 shell 的 `SERVICE`/`SERVICENAME` 服务名状态生成服务候选。
- `compgen -v` 和 `compgen -A variable` 会基于当前 shell 变量表生成变量名候选。
- `compgen -a` 和 `compgen -A alias` 会基于当前 shell alias 表生成别名候选。
- `compgen -A function` 会基于当前 shell 函数表生成函数名候选。
- `compgen -c` 和 `compgen -A command` 会合并内建命令、保留字、alias、函数和 `PATH` 文件生成命令名候选。
- `compgen -A arrayvar` 会基于当前 shell 的 indexed/associative array 标记生成数组变量候选。
- `compgen -e` 和 `compgen -A export` 会基于当前 shell 的 exported 变量标记生成导出变量候选。
- `compgen -A readonly` 会基于当前 shell 的 readonly 变量标记生成只读变量候选。
更新
- 本版本将项目版本、兼容性证据和近期 Bash 语义修复同步到当前源码状态。
文档与验证
- 发布当前兼容性归因账本,区分真实 Bash 输出差异、宿主/fixture 问题和历史 runner 期望文件。
- 更新 Windows-native 路径、bashdb 调试边界及 GNU Bash 源码语义映射说明。
- 明确 `cargo` 包版本为 `0.3.0`;上游 `run-*` 的 `87/87` 仅表示旧 `.right` 期望通过,不代表真实输出已经 83/83 对齐。
构建
- CI 在推送 `vN.N.0` 发布 tag 后,会等待测试通过并自动创建 GitHub Release。
- Release workflow 支持在 Actions 页面手动输入发布 tag 触发,并会校验 tag、checkout 提交和 Cargo 版本一致。
测试
- 增加 alias 值内含复合语句控制词和分号的 `if`、`while`、`for` 回归覆盖。
- 增加赋值 RHS 中独立和嵌入式输入进程替换的回归覆盖。
- 增加赋值 RHS 中独立和嵌入式输出进程替换的回归覆盖。
- 增加 `[[ ... ]]` 条件文件测试中输入/输出进程替换操作数的回归覆盖。
- 增加 `for`/`select` 词列表中输入/输出进程替换的回归覆盖。
- 增加 `case` 匹配词中输入进程替换的回归覆盖。
- 增加同一 word 中算术展开与参数展开交错时序的回归覆盖。
- 增加 `test -v` 与 `[[ -v ... ]]` 检测动态 Bash 参数的回归覆盖。
- 将 `test -O/-G` 与 `[[ -O/-G ... ]]` 的现有路径覆盖对齐 Windows/Git Bash 行为。
- 增加 `case` 引号 pattern 与双引号变量 pattern 的字面匹配回归覆盖。
- 增加关联数组交替 key/value compound assignment 和普通元素赋值中 bracket key 的回归覆盖。
- 增加 alias 引入的外层 `if`/`for` 内嵌套 `if`/`while` 的结束词匹配回归覆盖。
- 增加 alias 引入 `time` 前缀后计时嵌套 `if` 和嵌套循环的回归覆盖。
- 增加 alias 引入 `case` 后 clause body 内嵌套 `case` 的回归覆盖。
- 增加 alias 引入 `function name` 前缀后接 `if` 和 `for` 函数体的回归覆盖。
- 增加 `tee >(cat ...)` 管道阶段和函数参数中的输出进程替换回归覆盖。
- 增加管道 brace group 与函数阶段的工作目录隔离覆盖,并将函数定义、算术命令 pipeline 阶段预期对齐 Bash。
- 增加 `lastpipe` final `read`、final `while read`、final 赋值和 `PIPESTATUS` 状态覆盖。
- 增加 `coproc NAME { ...; }` 默认 stdout pipe 与 `wait $NAME_PID` 的回归覆盖。
- 增加转义字面反引号和嵌套反引号命令替换的回归覆盖。
- 增加嵌套 `$()` 命令替换保持完整 word 的回归覆盖。
- 增加 `printf -v arr[index]` 和 `printf -v assoc[key]` 的回归覆盖。
- 增加 `printf -v` indexed array 算术下标和负下标目标的回归覆盖。
- 增加 `compgen -W` prefix 过滤、`-P`/`-S` 输出和无匹配状态的回归覆盖。
- 增加 `compgen -V varname` 数组写入和 stdout 抑制的回归覆盖。
- 增加 `compgen -A builtin` 和 `compgen -A keyword` 候选输出、过滤和无匹配状态的回归覆盖。
- 增加 `compgen -b` 和 `compgen -k` 短 action 候选输出与无匹配状态的回归覆盖。
- 增加 `compgen -A signal` 信号候选输出与无匹配状态的回归覆盖。
- 增加 `compgen -A shopt` 和 `compgen -A setopt` 候选输出与无匹配状态的回归覆盖。
- 增加 `compgen -G` glob 候选输出、`-P`/`-S` 包装和无匹配状态的回归覆盖。
- 增加 `compgen -X` 普通过滤、反向过滤和全过滤状态的回归覆盖。
- 增加 `compgen -d` 和 `compgen -A directory` 的目录候选、过滤与包装回归覆盖。
- 增加 `compgen -f` 和 `compgen -A file` 的文件名候选、过滤与包装回归覆盖。
- 增加 `compgen -A helptopic` 候选输出与无匹配状态的回归覆盖。
- 增加 `compgen -A enabled` 候选输出与无匹配状态的回归覆盖。
- 增加 `compgen -A disabled` 禁用内建命令候选和 `compgen -A enabled` 排除禁用项的回归覆盖。
- 增加 `compgen -j` 和 `compgen -A job` 后台 job 候选、过滤与包装回归覆盖。
- 增加 `compgen -A running` 运行中 job 候选、过滤与包装回归覆盖。
- 增加 `compgen -A stopped` 无停止状态 job 时空输出成功的回归覆盖。
- 增加 `compgen -A hostname` 主机名候选、过滤与包装回归覆盖。
- 增加 `compgen -A binding` readline 函数候选、过滤与包装回归覆盖。
- 增加 `compgen -u` 和 `compgen -A user` 用户候选、过滤与包装回归覆盖。
- 增加 `compgen -g` 和 `compgen -A group` 用户组候选、过滤与包装回归覆盖。
- 增加 `compgen -s` 和 `compgen -A service` 服务候选、过滤与包装回归覆盖。
- 增加 `compgen -v` 和 `compgen -A variable` 的变量候选、过滤与包装回归覆盖。
- 增加 `compgen -a` 和 `compgen -A alias` 的别名候选、过滤与包装回归覆盖。
- 增加 `compgen -A function` 的函数名候选、过滤与包装回归覆盖。
- 增加 `compgen -c` 和 `compgen -A command` 的命令候选、过滤与包装回归覆盖。
- 增加 `compgen -A arrayvar` 的数组变量候选、过滤与包装回归覆盖。
- 增加 `compgen -e` 和 `compgen -A export` 的导出变量候选、过滤与包装回归覆盖。
- 增加 `compgen -A readonly` 的只读变量候选、过滤与包装回归覆盖。
-
oh-my-niu 1.0.1 2026-08-02
Changed
- Released first-party `.winux` source packs for Git, Docker, kubectl, npm,
- zoxide, direnv, dotenv, fzf, last-working-dir, and thefuck against the
- Winuxsh source lifecycle host.
- Added the framework entry point, directory-first official plugins,
- `prompt-core`, and theme plugins for minimal, classic, pure, compact,
- cyberpunk, forest, and ocean.
- Kept keybindings and command-not-found as bridge plugins while their low-level
- Reedline/provider call sites remain host-owned.
- Kept old `packs/` manifests as compatibility and package-review metadata over
- the directory plugin surface.
- Added `tools/smoke_framework.winux` for runtime verification with an
- installed Winuxsh binary.
- Updated bundle metadata with `api = "winuxsh:[email protected]"` and
- `min_winuxsh = "0.10.0"` so older Winuxsh hosts refuse this RC-first
- source-pack bundle.
- Added deterministic release packaging for `oh-my-winuxsh-1.0.1.zip` and the
- matching `.sha256` checksum file.
Compatibility
- Requires Winuxsh 0.10.0 or newer for source plugin startup and lifecycle hook
- loading through `.winux` files.
- Keeps process and WASM plugin protocols unchanged; WASM provider/effect APIs
- remain future host work.
Release Checklist
- Attach `oh-my-winuxsh-1.0.1.zip` and
- `oh-my-winuxsh-1.0.1.zip.sha256` to the GitHub release.
- Include this changelog entry and `docs/compatibility.md` in the release notes.
- Run `python tools/validate_bundle.py` and
- `python tools/package_bundle.py --check` before publishing.
- Run `winuxsh tools/smoke_framework.winux .` before publishing framework
- changes.
-
oh-my-niu 1.0.0 2026-07-31
Added
- Established the repository as the official Winuxsh plugin bundle rather than
- a legacy `.winsh` script framework.
- Added bundle metadata with `api = "winuxsh:[email protected]"` and
- `min_winuxsh = "0.8.3"`.
- Added first-party source packs for Git, Docker, kubectl, npm, zoxide,
- direnv, dotenv, fzf, last-working-dir, and thefuck with bundle-local `.winux`
- startup and lifecycle hook code.
- Added first-party builtin/native packs for command-not-found, prompts, and
- keybindings where host-owned provider/editor/prompt behavior still applies.
- Added bundle-owned aliases, completions, prompt presets, keybinding metadata,
- official theme assets, authoring templates, and public authoring documentation.
- Added deterministic release packaging for `oh-my-winuxsh-1.0.0.zip` and the
- matching `.sha256` checksum file.
Compatibility
- Requires Winuxsh 0.8.3 or newer for local bundle install, rollback, doctor,
- review, process plugin, and Phase 8 WASM command execution support.
- Uses pack manifest API `winuxsh:[email protected]`, process plugin protocol
- `winuxsh:[email protected]`, and WASM plugin protocol
- `winuxsh:[email protected]`.
- Keeps source, process, and WASM execution visible through manifest
- permissions because they can execute outside pure static asset loading.
Release Checklist
- Attach `oh-my-winuxsh-1.0.0.zip` and
- `oh-my-winuxsh-1.0.0.zip.sha256` to the GitHub release.
- Include this changelog entry and `docs/compatibility.md` in the release notes.
- Run `python tools/validate_bundle.py` and
- `python tools/package_bundle.py --check` before publishing.
-
rubash 0.2.0 2026-07-17
新增
- 补齐复合数组赋值中的未引用命令替换拆词行为,并在拆出的字段上继续执行
- pathname expansion。
- 补齐 `arr=($var)` 这类未引用参数复合数组赋值字段的 pathname expansion。
- 复合数组赋值普通元素支持 brace expansion,并遵守 `set +B` 对 brace expansion
- 的关闭状态。
修复
- quoted parameter expansion 后不再误触发 pathname expansion,行为更接近 Bash。
- Linux/Unix 上执行无 shebang 外部脚本时,遇到 exec format error 会通过 shell
- 回退执行,行为更接近 Bash。
- Unix shell 回退不再完全依赖被脚本或测试改写过的 `PATH`,会兜底查找标准
- `/bin/sh`、`/usr/bin/sh`、`/bin/bash` 和 `/usr/bin/bash`。
文档
- 更新 README 中的当前实现状态、builtins 覆盖、测试规模和代码结构说明。
- 更新 README 中的功能进度,说明当前实现已超过早期骨架阶段,重点转向 Bash
- 兼容细节和上游测试覆盖。
- 更新 README 和 GNU Bash upstream 测试文档中的测试进度、运行命令和更新时间。
测试
- 统一测试中临时外部命令的写入逻辑,在 Unix 上自动设置可执行权限,修复
- Linux CI 中禁用 builtin 后走外部命令相关用例返回 `126` 的问题。
- GNU Bash upstream runner 本地基线更新为 `87/87` 通过。
- 将 `run-minimal` 纳入默认 upstream runner 集合。
- 收敛 upstream bridge 的重复输出逻辑,降低后续维护成本。
-
rubash 0.1.1 2024-06-11
增强执行器
- #### 已完成
- ##### 执行器
- 新增内建命令: `env`, `set`, `unset`, `test`, `[`
- 添加 `redirect_err_append` 字段支持 `2>>` 重定向
- 使用 match 语句简化代码,替代函数指针
- ##### 测试
- 扩展执行器测试: 4 → 18 个测试
- 新增环境变量测试
- 新增命令链接测试
- 新增内建命令测试
-
rubash 0.1.0 2024-06-11
已知问题
- 管道尚未实现真正的进程间通信
- 变量展开尚未实现
- 不支持控制流语句
- ---
待完成
- [ ] 变量展开 (`$VAR`, `${VAR}`)
- [ ] 控制流 (`if`, `while`, `for`, `case`)
- [ ] 管道实现 (真正的进程间通信)
- [ ] 函数定义
- [ ] 作业控制
- [ ] 命令历史
- [ ] 更多内建命令 (`read`, `printf`)
首次发布
- 这是一个重要的里程碑,完成了 Shell 的核心功能。
- #### 已完成
- ##### 词法分析器 (Lexer)
- 基础分词 (单词、符号、关键字)
- 运算符识别 (`|`, `&`, `;`, `<`, `>`)
- 引号处理 (`'`, `"`)
- 变量识别 (`$VAR`, `${VAR}`)
- 命令替换 (`` `cmd` ``, `$(cmd)`)
- 花括号展开 (`{1..5}`, `{a,b,c}`)
- 注释处理 (`#`)
- 转义字符处理 (`\`)
- ##### 解析器 (Parser)
- AST 生成
- 简单命令解析
- 管道解析
- 分号分隔命令
- 赋值语句解析
- 重定向解析
- ##### 执行器 (Executor)
- 内建命令: `exit`, `echo`, `pwd`, `cd`, `export`, `true`, `false`
- 外部命令执行
- I/O 重定向支持
- 退出码处理
- #### 测试
- 词法分析器测试: 33 个
- 解析器测试: 13 个
- 执行器测试: 4 个
- 单元测试: 8 个
- 总计: 58 个测试
- #### 重构
- 使用 Rust 2021 新特性 (`matches!`, `let...else`)
- 代码优化: 减少约 48% 行数
- 添加 `#[inline]` 优化
- #### 文档
- README.md
- CONTRIBUTING.md
- CODE_OF_CONDUCT.md
- LICENSE (GPL-3.0)
-
WinuxCmd 1.1.7 日期未知
Added
- diff3: full GNU diffutils 3.10 alignment on the shared diff engine —
- three-way block merge (`make_3way_diff`), default report with GNU's
- odd-one-out section rules, ed scripts (`-A`/`-e`/`-E`/`-3`/`-x`/`-X` with
- `-i` final write), merged output (`-m`, `-L` labels, `-T` initial tab,
- `--strip-trailing-cr`, `--diff-program`), `-` stdin operands and GNU exit
- statuses (0 ok / 1 conflicts / 2 trouble) with byte-verified usage
- diagnostics (issue #1127, P1).
- sdiff: rebuilt on `diff --side-by-side` output — GNU's gutter formula,
- `|`/`<`/`>`/`(`/`)` row markers and `/`/`\` separators for rows missing a
- trailing newline; `-l`/`-s`, `-w`/`--tabsize`/`-t`, the ignore-option
- family (`-i`/`-E`/`-Z`/`-b`/`-W`/`-B`/`-I`/`-a`/`--strip-trailing-cr`,
- `-d`/`-H`/`--diff-program`) forwarded into matching only, and `-o` merged
- output that auto-merges non-interactively (overlaps keep the left column)
- per issue #1127, P1.
Changed
- winuxcmd: the bare `winuxcmd` listing groups commands into GNU
- coreutils-style sections (File operations, Text processing, Shell
- utilities, ...) with alphabetical entries and a discovery guide
- (`winuxcmd <command> --help`, `winuxcmd help <command>`,
- `winuxcmd --list-all`, `wpm search`); `winuxcmd --list-all` preserves the
- flat alphabetical listing. When the system locale is non-English and its
- i18n catalog is not installed, one hint line points at the matching WPM
- package (`wpm install winuxcmd-i18n-zh-cn` + `WINUX_LANG`) (#1155).
- seq: output is assembled into 64 KiB buffer chunks instead of one
- `WriteFile` per line, and integral first/increment sequences (default
- fixed format, precision 0, no `--format`/`-w`, |operands| <= 2^52) emit
- through a `std::to_chars` fast path like GNU seq.c's integer bypass.
- 100k lines, release build: `seq 1 100000` 332 ms -> ~45 ms and
- `seq 1 100000 | wc -l` 903 ms -> ~45 ms (GNU/WSL oracle 7-10 ms; the
- residual is Windows process spawn). The byte stream is unchanged — a
- regression test pins all 588,895 bytes across several flush rounds
- (#1173).
- diff: side-by-side output now shares the GNU diffutils `side.c`
- printer with sdiff (identical rows, `(`/`)` remainders, `/`/`\`
- separators, common rows for equal inputs); `-E` expands tabs across the
- whole line for matching, `-B`/`-I` drop ignored hunks and realign their
- lines positionally instead of filtering buffers, and a final line without
- a newline only compares equal to the other side's incomplete last line
- (issue #1127, P1).
- docs: the GNU coreutils compatibility records the one deliberate
- Windows deviation around literal-glob operands — NTFS/Win32 forbid `*` in
- file names, so `touch 'x*x'` reports the NTFS error where GNU creates the
- literal file, `touch -c 'x*x'` silently skips like GNU (fixed in #1152),
- and the derived `ls 'x*x'` miss is a consequence of GNU's own creation
- succeeding; the touch test exemption is registered in
- `DOCS/gnu-coreutils/touch.md` and `COMPATIBILITY_MATRIX.md` (#1143, #1167).
Fixed
- spawn: every child-process launch (the xargs/`find -exec`/pipeline
- spawn family) resolves through a shared GNU `execvp`-style resolver —
- bare names search PATH only (no cwd or implicit System32 shadowing, so
- `sort`/`find`/`tree`... pick the GNU-compatible binary the caller put on
- PATH first) and POSIX PATH spellings (`/c/...`, `/cygdrive/c/...`,
- `:`-separated entries) are split and converted before resolution
- (owner-reported, #1169).
- find: POSIX drive-form start points (`/c/...`, `/d/...`,
- `/cygdrive/c/...`) now resolve through the same operand boundary as every
- other tool's path operands, matching msys find semantics where the tool's
- own POSIX layer handles the start point. Command-line roots, `-files0-from`
- entries, and the `-files0-from` source operand all go through it (#1170).
- tests: the grep junction recursion cases degrade to a SKIP on hosts
- without symlink-creation privilege (error 1314) instead of failing, mirroring
- the `*_or_skip` helper every other unit test file uses (#1168). New tput
- GNU-differential tests pin that every tput path — success, unknown
- capability, missing operand — leaves piped stdin untouched, the contract
- GNU tput is known for (#1166).
- i18n: the WPM message catalog resolves at the WinuxCmd install root, so
- localized messages work when `winuxcmd.exe` is invoked through a shim or
- PATH entry outside the install directory (#1151).
- touch: GNU-parity handling of unmatched literal-glob operands — names
- that glob to nothing are treated as missing under `-c` (skipped) instead of
- being created verbatim, matching GNU coreutils (#1152).
- wpm: interaction-audit fixes (#1156). `wpm outdated` now anchors
- installed versions on install receipts, so it keeps reporting a pending
- update after `wpm index update` instead of flipping to "all up to date"
- while old files are still on disk. `wpm install --json` prints one
- machine-readable payload (statuses `installed`, `already_installed`,
- `would_install`, `not_found`, `not_installable`, `preflight_failed`,
- `download_failed`, `error`) with all human progress routed to stderr, and
- `wpm index update --json` prints a matching payload. `wpm --help` now
- documents the `install` `--force` reinstall/overwrite semantics. Verified
- by measurement: piped `y`/`n` confirmation on `wpm uninstall` stdin (and
- `-y` in any argument position) already behaved correctly on this branch,
- and locked destination files already fail loudly with exit 1 on both
- install and uninstall; regression tests pin all of these behaviors.
- du: an operand's trailing separator run collapses to a single slash —
- `du src/` prints `src/` and `src/bin` (never `src//bin`), `du src//` prints
- byte-identically to `du src/`, and `du src` keeps the plain form, matching
- GNU coreutils (#1159).
- du, sed: POSIX drive-form operands (`/d/...`, `/cygdrive/d/...`)
- resolve through the shared operand boundary — du used to reject them
- with ENOENT while accepting `D:/...`, and sed could not read them as
- input files, `-i` targets, `-f` script files or the `r`/`w`/`W`
- script-embedded paths. Both now route operands through the same
- boundary grep/wc/head/find already use (the L2 self-parse contract,
- uniformly applied); error messages and the `=`/`F` filename display
- keep the operand text as typed (#1174).
- wpm: candidate index sources are the built-in registry plus
- `user_sources` only — a `sources` array declared inside the cached index
- file is no longer merged, so one poisoned fetch can no longer persistently
- inject new download locations; `wpm index update` now records the winning
- source (`last_success_source`) and the SHA-256 of the persisted index
- (`last_index_sha256`) and warns when the index content changes while being
- served from a non-preferred source; the `wpm index update --json` payload
- carries `index_sha256` (#1176).
- wpm: the loopback proxy fallback (probing `127.0.0.1:7897/7890/7891/
- 7892/7893/10808/10809` after direct failures) is off by default; any
- unprivileged local process could otherwise listen there and serve both
- index and artifacts past the index's own sha256 checks. Opt in via
- `allow_loopback_fallback: true` in `.wpm/config.json`, or configure a
- trusted proxy with `--proxy`/environment variables (#1177).