
behave
io.github.emirmrkaljevic由 GitHub 帳號 emirmrkaljevic 發布v0.1.7更新於 Oct 24, 2026
Record how any CLI behaves and check that a rewrite behaves the same (golden tests).
概覽
記錄命令列工具的行為(依說明文字產生測試案例),再把記錄回放到另一個實作上,證明改寫版本行為一致。
- 可以做什麼
- 在暫存目錄中執行一次指令,回傳結束碼、標準輸出與標準錯誤
- 依工具的說明文字與手冊頁產生並記錄測試案例規格
- 新增手寫案例,並依標籤列出已記錄的案例
- 把規格回放到另一個實作上,回傳失敗案例及其差異
- 適用情境
- 適合把命令列工具移植到其他語言、重構或升級 CLI 時建立回歸防護,或比較同一工具的兩個實作。
- 執行需求
- 以本機程序方式透過 stdio 執行,使用 behave 執行檔加上 mcp 參數啟動。可透過 Homebrew、發行版二進位檔或 go install 安裝。未宣告需要帳號、API 金鑰或環境變數。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 behave,將其新增到工作區。
- 為需要使用其工具的對話啟用該服務。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
其他 MCP 客戶端
參照 儲存庫 中的啟動說明。
README
behave
[Support my open-source work: get a SkinToScan pack]
If you find this useful, you can support me by getting a SkinToScan pack, my other project. It's a one-time purchase that directly funds my open-source work.
[Buy Single Picture Check, €4.49] [Buy 5 Picture Pack, €19.99 (popular)] [Buy 15 Picture Pack, €54.99]
Record how any CLI behaves. Prove your rewrite behaves the same.
Proof: running it against uutils coreutils (the Rust rewrite of GNU coreutils) has so far led to 30 upstream bug reports and 37 fix PRs, 21 of them merged (as of Nov 6, 2026). See what it found.
behave treats a command-line tool as a black box. It reads --help (and the man page),
generates hundreds of test cases (every flag, flag pairs, every value the help lists for a flag,
edge-case stdin, file arguments, bad values, missing files), records what the tool actually does (stdout, stderr, exit code),
and replays those cases against another implementation.
[behave recording jq and checking gojq against it]
Use it when you are:
- porting a tool to Rust or Go and want proof that the port is compatible,
- refactoring or upgrading a CLI and want a regression net in a few seconds,
- comparing two implementations of the same tool.
Porting a CLI?
If you are rewriting an existing command-line tool in Rust or Go, behave gives you a compatibility suite without reading the original source. Three commands:
This is how it led to 30 bug reports and 37 fix PRs in uutils coreutils (21 merged so far).
A second, ready to run pair: behave diff examples/jq --impl gojq passes 29 of 33 jq 1.7 cases,
and the 4 differences are all error wording on stderr (see examples/jq).
Status: early (v0.1). CLI and MCP server work; feedback welcome.
Quick start
Install with Homebrew (macOS and Linux), download a binary from Releases, or use go install:
Or try the committed examples first: behave run examples/cut (27 GNU cut cases),
behave run examples/paste (18 GNU paste cases), behave run examples/join (26 GNU join cases), behave run examples/nl (26 GNU nl cases), behave run examples/fold (28 GNU fold cases), behave run examples/head (24 GNU head cases), behave run examples/expand (21 GNU expand cases), behave run examples/unexpand (26 GNU unexpand cases), behave run examples/tr (29 GNU tr cases), behave run examples/comm (30 GNU comm cases), behave run examples/uniq (31 GNU uniq cases), behave run examples/sort (35 GNU sort cases), behave run examples/cat (39 GNU cat cases), behave run examples/wc (38 GNU wc cases), behave run examples/tac (33 GNU tac cases), behave run examples/numfmt (41 GNU numfmt cases), behave run examples/od (36 GNU od cases), behave run examples/basenc (32 GNU basenc cases), behave run examples/seq (39 GNU seq cases), behave run examples/expr (43 GNU expr cases), behave run examples/printf (38 GNU printf cases), behave run examples/factor (31 GNU factor cases), behave run examples/cksum (35 GNU 9.12 cksum cases), behave run examples/md5sum (37 GNU 9.12 md5sum cases), behave run examples/tail (33 GNU 9.12 tail cases), behave run examples/base64 (34 GNU 9.12 base64 cases), behave run examples/base32 (36 GNU 9.12 base32 cases), behave run examples/basename (35 GNU basename cases), behave run examples/dirname (26 GNU dirname cases), behave run examples/fmt (36 GNU 9.12 fmt cases), behave run examples/realpath (38 GNU 9.12 realpath cases), behave run examples/pr (40 GNU 9.12 pr cases), or behave run examples/jq (33 jq 1.7 cases); see examples/.
For tools that need a positional argument (a filter, a pattern, a field list), give samples:
When the help text lists the values a flag accepts, probe adds one case per value next to the
invalid-value cases. It understands GNU style (WHEN can be: auto, always, never,
--sort=WORD ... to WORD: none (-U), size (-S), ..., The WHEN argument defaults to 'always' and can also be 'auto' or 'never'.), clap's [possible values: ...], argparse's {json,yaml} and
<fast|safe>. It only keeps plain lowercase lists it can tie to that flag. --dry-run shows what it found:
Example output (abridged):
What it found on day one
Run on this machine against real tools, generated cases only, --help/--version excluded:
* with --ignore-stderr; error message wording differs almost everywhere.
† varies by one or two: some --random-sort cases are only sometimes detected as flaky with tiny inputs.
Many of these are deliberate design choices, not bugs. behave just makes them visible.
Record from the release the rewrite actually targets. uutils follows the newest GNU release, and
GNU 9.12 prints ==> 'standard input' <== in tail -v headers where 9.7 prints no quotes, so a
spec recorded from an older GNU reports differences that are really GNU changes. behave.yaml
keeps the --version line of the recorded tool, and run/diff print it next to the version
being replayed.
Some were real bugs and are now fixed upstream:
- uutils
cut --output-delimiter -x(uutils/coreutils#15341, fixed in #15345) - uutils
sort --check --mergeignored the check (uutils/coreutils#15380, fixed in #15381) - uutils
tac -s -1rejected a separator starting with-(uutils/coreutils#15497, fixed in #15498) - uutils
tail 007printed the missing file as'7'(uutils/coreutils#15533, fixed for tail in #15534; the same bug in other utilities is being fixed one by one:tacin #15541 (merged),nlin #15544 (merged),md5sum/cksumin #15548 (merged),sortin #15565 (merged),shredin #15604 (merged)) - uutils bare
tail -candtail +NUMignored_POSIX2_VERSION(uutils/coreutils#15528, fixed in #15529) - uutils
sort -c direxited 0 without a message instead of reporting the read error (uutils/coreutils#15615, fixed in #15616) - uutils
fmt d a.txtstopped at the directory with a bareread errorand never formatteda.txt(uutils/coreutils#15922, fixed in #15923) - uutils
od -t x0(andd0,u0,f0) silently used the default size, where GNU rejects a 0-byte type, caught byexamples/od(uutils/coreutils#16085, fixed in #16086)
Still open: a few error messages that lose the file name, mostly caught by the "file with trailing slash" probe case added in v0.1.7:
cp f/ destprintsNot a directory (os error 20)instead ofcannot stat 'f/': Not a directory(#15691, fix in #15692)pasteopen and read errors without the file name (#15254, fix in #15642)shred dirsaysNot a fileinstead offailed to open for writing: Is a directory(#15630, fix in #15631)mv missing/ gsaysNot a directoryandmv d/f gwith an unsearchabledsaysNo such file or directory, where GNU gives the real reason (#15704, fix in #15705)ln f/ linkandln missing linksayfailed to create hard link 'link' => ...instead of naming the source withfailed to access 'f/'(#15713, fix in #15714)mv a missing/bandmv a ro/bprint onlyNo such file or directory (os error 2)orPermission denied (os error 13), where GNU sayscannot move 'a' to 'missing/b': ...(#15721, fix in #15722)mv -T f d(file onto an existing directory) printscannot overwrite directory 'd' with non-directorywithout the source, where GNU adds'f'(#15724, fix in #15725)mv -n z z(and--update=none) fails with'z' and 'z' are the same file, where GNU skips the move and exits 0 (#15824, fixed in #15825)chown 1000 f/andchgrp 1000 missingsaycannot dereference 'f/', where GNU sayscannot accessand keepscannot dereferencefor dangling symlinks (#15867, fix in #15868)chmod -v 644 missingprints nothing on stdout, and for a dangling symlink a made-upfrom 0000 (---------) to 1500 (r-x-----T)line, where GNU prints'missing' could not be accessed(#15883, fix in #15884)tr a '[:alpha:]'prints the set2 class error on one line and saysset2, where GNU splits it over two lines and saysstring2(#15948, fix in #15949)uniq -f xsaysInvalid argument for skip-fields: x, where GNU saysx: invalid number of fields to skip(same for-sand-w), caught by the newexamples/uniqspec (#15970, fix in #15971)sort -t abandsort -t ''word the bad separator errors differently from GNU, andsort -c a badds a usage hint GNU doesn't print, caught byexamples/sort(#15975, fix in #15976)od -A qandod -t x3word the invalid radix and type string errors differently from GNU, caught byexamples/od(#16071, fix in #16072)seq --foosaysinvalid floating point argumentandseq -inf 1prints-infforever, because option-like arguments before the first operand are read as numbers where GNU treats them as options, caught byexamples/seq(#16113, fixed in #16114)factor '30 'andfactor $'\t30'print factors where GNU rejects the argument, because every argument is trimmed while GNU skips only leading spaces, caught byexamples/factor(#16142, fixed in #16143)md5sum -crejects the lines md5sum itself writes for names with a backslash or a newline (they start with\), and unescapes names on lines without that marker, caught byexamples/md5sum(#16166, fixed in #16167)base64 -w 99999999999999999999fails withinvalid wrap sizewhere GNU turns wrapping off for a size too large to represent, caught byexamples/base64(#16180, fix in #16181)base32 -d(andbase64/basenc) accepts a carriage return in encoded input where GNU rejects it unless-i, caught byexamples/base32(#16223, fix in #16224)fmt -w 20could print a line longer than 20 columns (e.g.at a narrow width), because it sometimes broke a line too late, caught byexamples/fmt(#16190, fix in #16191)realpath '' /usrstopped witherror: error: invalid value for one of the argumentsand never printed/usr, while GNU reports''as a missing file and resolves the rest, caught byexamples/realpath(#16206, fix in #16207)pr -oindented body lines but not the page header (GNU pads the first blank separator line and the header text), caught byexamples/pr(#16211, fix in #16212)
MCP server
behave mcp serves the same features over stdio, so a coding assistant can explore the
original tool, grow the spec while it works, and check its port after every change.
Spec format
A spec is a directory you can commit and review:
Cases can also set env, files (created in a fresh temp dir), and timeout_ms.
Add your own with behave add behave-cut --stdin 'a,b' -- -d, -f2.
How it stays deterministic
- every case runs in a new temp directory with a fixed environment
(
LC_ALL=C.UTF-8,TZ=UTC,NO_COLOR=1,HOME=temp dir) - input files get fixed modification times, and tools that read the current directory
(
ls,du,rg; help text mentions "current directory" or recursion) get a small sample tree (--sample-files auto|on|off) whose names, sizes and extensions sort differently, sols --sort=size,ls -t,du --timeanddu -Sproduce output worth comparing - each case is recorded 3 times; anything that changes between runs is marked
flakyand skipped (e.g.sort --random-sort) - normalizers remove noise before comparing:
ansi,workdir,cmd(jq:vsgojq:),timestamp,hexaddr,duration,pid, plus your own regex rules inbehave.yaml - timeouts kill the whole process group
Commands
run and diff exit with 1 when any case fails, so they drop straight into CI. Pass --junit report.xml to write a JUnit XML report for CI dashboards (GitHub Actions, Jenkins, etc.).
coverage tells you what a spec doesn't test yet. On the hand-picked cut example:
It reads flags the same way probe does, understands --long=value, unambiguous long prefixes and clustered short flags (-sf1), and exits 1 with --min 80 when coverage drops below 80%.
The repo ships a multi-stage Dockerfile that builds the binary and starts behave mcp on stdio (for Glama and other MCP registries that build from a Dockerfile).
Roadmap
- more committed example specs (
head,tail,wc, agrepsubset vs ripgrep) - Homebrew tap
Safety
behave only runs the binaries you point it at, in a temp directory. Don't probe tools that
change things outside their working directory (package managers, rm -rf-style tools)
without --skip-flag and a sandbox.
Links
- MCP Registry name:
io.github.emirmrkaljevic/behave
License
MIT
來源:README.md,提交 2504c27
工具
0版本歷史
7- v0.1.7最新Oct 23, 2026
- v0.1.6Oct 20, 2026
- v0.1.5Oct 18, 2026
- v0.1.4Oct 17, 2026
- v0.1.3Oct 16, 2026
- v0.1.2Oct 14, 2026
- v0.1.1Oct 14, 2026
評分
已滿足適用檢查項的 50%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
- 近 30 天內可連通25 分
請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。
- 已宣告認證方式10 分
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
- 已列出工具15 分
請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。
- 描述必需15 分
- 圖示10 分
- 文件15 分
- 授權條款10 分