Skip to content

feat(bin): run on Windows via terminal-multiplexer abstraction#86

Open
kevinamick wants to merge 7 commits into
kunchenguid:mainfrom
kevinamick:windows-compat
Open

feat(bin): run on Windows via terminal-multiplexer abstraction#86
kevinamick wants to merge 7 commits into
kunchenguid:mainfrom
kevinamick:windows-compat

Conversation

@kevinamick

Copy link
Copy Markdown

What Changed

  • Added bin/fm-mux.sh, a multiplexer abstraction with tmux (macOS/Linux) and wezterm (Windows) backends behind a common fm_mux_* verb set, plus opaque window handles (session:window for tmux, wezterm:<pane_id> for wezterm) and auto backend selection.
  • Added bin/fm-proc.sh for portable process/ancestry introspection, walking the Windows process tree via a PowerShell CIM query where MSYS ps -o is unavailable, and rewired fm-spawn, fm-peek, fm-send, fm-watch, fm-teardown, fm-supervise-daemon, fm-harness, fm-lock, and fm-bootstrap to drive the multiplexer and process helpers instead of calling tmux/ps directly.
  • Added tests/fm-mux.test.sh covering both backends via fakes, a .gitattributes for line-ending normalization, and updated AGENTS.md, README.md, and CONTRIBUTING.md to document the multiplexer/process abstraction and Windows support.

Risk Assessment

✅ Low: The macOS/Linux (tmux) path is preserved byte-identically and the new wezterm backend is purely additive (no prior Windows support to regress), both backends are test-covered, and the only outstanding item is an info-level forward-compatibility note on a parser whose current behavior the author verified.

Testing

On a real Windows ARM64 + Git Bash + WezTerm host (the target platform; no tmux), the full behavior suite was run and the WezTerm port was exercised end-to-end: fm-mux.sh auto-selected the wezterm backend, spawned a live WezTerm tab, round-tripped a unique marker through send-text/capture, reported pane-path/cursor-y/pane-alive, and killed the tab cleanly; fm-proc.sh walked the real Windows process tree and fm-harness.sh/fm-bootstrap.sh produced the correct Windows behaviors. No reviewer-visible screenshot is included because this is a terminal-multiplexer/CLI change whose end-user surface is the WezTerm pane itself — the pane's input and echoed output are captured verbatim in the e2e transcript, which is the faithful surface artifact (capturing the live WezTerm window would expose the user's unrelated working session). Two suite tests (fm-wake-queue singleton timing, fm-secondmate relative-origin path shape) fail only on a Windows dev box due to MSYS-specific test-harness assumptions; both are green on the project's ubuntu-latest CI and the underlying product behavior was verified correct, so they are reported as non-blocking warnings rather than regressions.

Evidence: WezTerm backend end-to-end on real Windows (spawn -> send -> capture marker round-trip -> kill)

[1] backend auto-selection (no FM_MUX set): fm-mux.sh backend -> wezterm [2] spawn a real WezTerm tab -> handle wezterm:2 [3] pane-path -> /tmp/tmp.xbzLLIRMWl (matches expected pwd -P) [4] pane-alive -> TRUE [5] sent: echo FM_E2E_152618440-OUTPUT [6] pane| $ echo FM_E2E_152618440-OUTPUT pane| FM_E2E_152618440-OUTPUT RESULT: marker round-tripped through the real WezTerm pane -> PASS [7] cursor-y -> 6 [8] pane-alive after kill -> FALSE (tab gone, as expected)

=== firstmate WezTerm backend end-to-end (real WezTerm on Windows) ===
date: Thu, Jun 25, 2026 12:55:04 PM
uname: MSYS_NT-10.0-26100-ARM64

[1] backend auto-selection (no FM_MUX set):
    fm-mux.sh backend -> wezterm

[2] spawn a real WezTerm tab in cwd=/tmp/tmp.xbzLLIRMWl
    handle -> wezterm:2

[3] pane-path (normalized cwd of the new tab):
    pane-path -> /tmp/tmp.xbzLLIRMWl
    expected  -> /tmp/tmp.xbzLLIRMWl

[4] pane-alive on the live handle:
    pane-alive -> TRUE

[5] send a shell command that echoes a unique marker, then Enter:
    sent: echo FM_E2E_152618440-OUTPUT

[6] capture the pane and look for the marker echoed back:
    pane| 
    pane| NORTHAMERICA+kamick@LAPTOP-9P64TR8N MSYS /tmp/tmp.xbzLLIRMWl
    pane| $ echo FM_E2E_152618440-OUTPUT
    pane| FM_E2E_152618440-OUTPUT
    pane| 
    pane| NORTHAMERICA+kamick@LAPTOP-9P64TR8N MSYS /tmp/tmp.xbzLLIRMWl
    pane| $ 

    RESULT: marker round-tripped through the real WezTerm pane -> PASS

[7] cursor-y (numeric row index from cli list):
    cursor-y -> 6

[8] kill the tab and confirm pane-alive flips to false:
    pane-alive after kill -> FALSE (tab gone, as expected)

=== done ===
Evidence: fm-proc.sh portable introspection walks the real Windows process tree on MSYS

fm_proc_is_windows -> yes ps -o comm= -p $$ -> ps: unknown option -- o (Unix path cannot work on MSYS) fm_proc_ancestry $$ -> bash.exe -> pwsh.exe -> copilot.exe -> no-mistakes.exe (Windows process tree) fm_proc_alive(live winpid) -> TRUE ; fm_proc_alive(999999) -> FALSE

=== fm-proc.sh portable process introspection (real MSYS on Windows) ===
uname: MSYS_NT-10.0-26100-ARM64

[A] fm_proc_is_windows -> yes

[B] Why the Unix path fails here: MSYS ps has no -o support:
    $ ps -o comm= -p $$
      ps: unknown option -- o
      Try `ps --help' for more information.

[C] fm_proc_ancestry from this bash ($$=44826) walks the *Windows* process tree:
      41572	bash.exe	"C:\Program Files\Git\usr\bin\bash.exe" -lc " ROOT=$(pwd) EV=\"/c/Users/kamick/AppData/Local/Temp/no-mistakes-evidence/01KW033PBC45ZCYY2ZQAYK3JCM\" LOG=\"$EV/fm-proc-windows-introspection.txt\" { echo \"=== fm-proc.sh portable process introspection (real MSYS on Windows) ===\" echo \"uname: $(uname -s)\" . \"$ROOT/bin/fm-proc.sh\" echo echo \"[A] fm_proc_is_windows -> $(fm_proc_is_windows && echo yes || echo no)\" echo echo \"[B] Why the Unix path fails here: MSYS ps has no -o support:\" echo \"    \$ ps -o comm= -p \$\$\" ps -o comm= -p $$ 2>&1 | sed \"s/^/      /\" echo echo \"[C] fm_proc_ancestry from this bash (\$\$=$$) walks the *Windows* process tree:\" fm_proc_ancestry $$ | sed \"s/^/      /\" echo echo \"[D] fm_proc_info for this bash (Windows pid via winpid):\" WP=$(ps -p $$ 2>/dev/null | awk \"NR==2 {print \$4}\") echo \"    winpid of \$\$ = $WP\" fm_proc_info \"$WP\" | sed \"s/^/      /\" echo echo \"[E] fm_proc_alive on a real live pid vs a bogus one:\" fm_proc_alive \"$WP\" && echo \"    alive($WP) -> TRUE\" || echo \"    alive($WP) -> FALSE\" fm_proc_alive 999999 && echo \"    alive(999999) -> TRUE\" || echo \"    alive(999999) -> FALSE (bogus pid, as expected)\" echo echo \"=== harness + lock detection that fm-proc powers ===\" echo \"[F] fm-harness.sh (own harness, via env markers / Windows ancestry):\" bash \"$ROOT/bin/fm-harness.sh\" 2>&1 | sed \"s/^/      /\" echo \"[G] fm-harness.sh crew (resolved crewmate harness):\" bash \"$ROOT/bin/fm-harness.sh\" crew 2>&1 | sed \"s/^/      /\" echo \"=== done ===\" } 2>&1 | tee \"$LOG\" echo \"EVIDENCE: $LOG\" "
      40128	pwsh.exe	pwsh.exe -NoProfile -NoLogo -NonInteractive -Command "try { [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false) } catch {}; try { if (Get-Variable -Name PSStyle -ErrorAction SilentlyContinue) { $PSStyle.OutputRendering = 'PlainText' } } catch {}; $global:LASTEXITCODE = 0; try { cd C:\Users\kamick\.no-mistakes\worktrees\4c595e025d56\01KW033PBC45ZCYY2ZQAYK3JCM; & 'C:\Program Files\Git\usr\bin\bash.exe' -lc ' ROOT=$(pwd) EV=\"/c/Users/kamick/AppData/Local/Temp/no-mistakes-evidence/01KW033PBC45ZCYY2ZQAYK3JCM\" LOG=\"$EV/fm-proc-windows-introspection.txt\" { echo \"=== fm-proc.sh portable process introspection (real MSYS on Windows) ===\" echo \"uname: $(uname -s)\" . \"$ROOT/bin/fm-proc.sh\" echo echo \"[A] fm_proc_is_windows -> $(fm_proc_is_windows && echo yes || echo no)\" echo echo \"[B] Why the Unix path fails here: MSYS ps has no -o support:\" echo \"    \$ ps -o comm= -p \$\$\" ps -o comm= -p $$ 2>&1 | sed \"s/^/      /\" echo echo \"[C] fm_proc_ancestry from this bash (\$\$=$$) walks the *Windows* process tree:\" fm_proc_ancestry $$ | sed \"s/^/      /\" echo echo \"[D] fm_proc_info for this bash (Windows pid via winpid):\" WP=$(ps -p $$ 2>/dev/null | awk \"NR==2 {print \$4}\") echo \"    winpid of \$\$ = $WP\" fm_proc_info \"$WP\" | sed \"s/^/      /\" echo echo \"[E] fm_proc_alive on a real live pid vs a bogus one:\" fm_proc_alive \"$WP\" && echo \"    alive($WP) -> TRUE\" || echo \"    alive($WP) -> FALSE\" fm_proc_alive 999999 && echo \"    alive(999999) -> TRUE\" || echo \"    alive(999999) -> FALSE (bogus pid, as expected)\" echo echo \"=== harness + lock detection that fm-proc powers ===\" echo \"[F] fm-harness.sh (own harness, via env markers / Windows ancestry):\" bash \"$ROOT/bin/fm-harness.sh\" 2>&1 | sed \"s/^/      /\" echo \"[G] fm-harness.sh crew (resolved crewmate harness):\" bash \"$ROOT/bin/fm-harness.sh\" crew 2>&1 | sed \"s/^/      /\" echo \"=== done ===\" } 2>&1 | tee \"$LOG\" echo \"EVIDENCE: $LOG\" ' } finally { $__copilotSuccess = $?; if (-not $__copilotSuccess -and $LASTEXITCODE -is [int] -and $LASTEXITCODE -ne 0) { $host.SetShouldExit($LASTEXITCODE) } elseif (-not $__copilotSuccess) { $host.SetShouldExit(1) } else { $host.SetShouldExit(0) } }"
      38084	copilot.exe	copilot -p "Workspace boundary (important): - Confine source, project, user-data, and system file changes to the current working directory, which is a git worktree. Do not intentionally create, modify, move, or delete those files anywhere outside it. - Do not modify system state outside the worktree. In particular, do not install or upgrade system packages (for example brew install/upgrade, or other system package managers), do not modify applications under /Applications, and do not change global or user-level tool configuration. - This is prompt steering, not true enforcement: treat the worktree boundary as a soft boundary you must follow. - The only allowed out-of-worktree writes are test evidence files under C:\Users\kamick\AppData\Local\Temp\no-mistakes-evidence when a testing prompt explicitly asks for them. - Ephemeral temp/cache writes that are incidental side effects of running the project development toolchain are allowed outside the worktree for tests, linters, formatters, builds, and manual verification commands. - You may read files outside the worktree and run read-only commands, but every other intentional write must stay inside the worktree.  You are validating a code change by testing it. Examine the repository and run the appropriate tests yourself.  Context: - branch: windows-compat - base commit: 2a2fe0566e63b883ec53dc3e34dc14306689f989 - target commit: e17547032ee47ee788d95c32de5ce5ec1e4741c6   Task: - Understand the user intent before testing it. If extracted user intent is present, use it as the primary hint for what success means. - Decide what evidence or artifacts would clearly demonstrate the user intent is satisfied. Unit tests passing is not sufficient evidence by itself. - Demonstrate the user intent working end-to-end in a way consistent with how an end user would actually experience it. - Prefer product-level artifacts: screenshots, GIFs, videos, rendered UI, CLI transcripts, API responses, persisted database state, generated PR markdown, logs, or other outputs that directly show the intended behavior working. - For UI, HTML, CSS, Electron renderer, browser, visual layout, or copy-placement changes, attempt to capture reviewer-visible visual evidence. - Prefer screenshots, images, videos, GIFs, or rendered HTML artifacts that show the actual end-user surface. - DOM snapshots, selector assertions, and text-only render summaries are not substitutes for visual evidence when a rendered surface is available. - If a UI-facing change has no screenshot, image, video, GIF, or rendered HTML artifact, state why in testing_summary. - Write new evidence files into this temporary evidence directory: C:\Users\kamick\AppData\Local\Temp\no-mistakes-evidence\01KW033PBC45ZCYY2ZQAYK3JCM - Do not move, commit, or modify source files only to make evidence linkable. Record local evidence file paths exactly where you created them. - Only use command output as an artifact when that output directly demonstrates the end-user experience or requested behavior. Generic pass/fail, coverage, or clean-worktree output is not sufficient evidence. - Look for existing tests that would generate sufficient evidence. If they exist, run the smallest relevant set. - If no existing test produces sufficient evidence, write or improve a test so that it does. - If automated testing cannot produce the needed evidence, execute manual verification steps and record the evidence-producing steps you performed. - If sufficient evidence is not possible, report a warning finding explaining what evidence is missing and why the user needs to decide what to do. - Include a concise \"testing_summary\" sentence describing what you exercised and the overall result. - The \"testing_summary\" must account for the complete test step: baseline commands that already ran, automated tests, manual or evidence-producing checks, artifacts gathered, and the overall result. - Record the exact tests, manual checks, and evidence-producing steps you ran in a \"tested\" array. Prefer concrete commands or test selectors wrapped in backticks. - Always include an \"artifacts\" array. Leave it empty when you produced no reviewer-visible evidence artifacts. Use artifact path for file artifacts, artifact url for externally visible artifacts, and artifact content for short logs or command output that should be shown directly in the PR. - If tests fail, determine whether the problem is a real product/code failure, a setup/environment problem you can fix, or a flaky/infrastructure issue. - If the issue is setup-related and fixable, fix it and retry the tests.  Rules: - Do NOT run linters, formatters, or static analysis tools. - Focus on testing and test-related fixes only. - Before finishing, remove any transient artifacts your testing created in the working tree (downloaded models, caches, build outputs, large binaries, or generated data directories) so they are not committed and pushed. Do not remove intentional source or test-file changes, and leave evidence files in the dedicated evidence directory untouched. - Keep \"testing_summary\" high-signal and natural language. Avoid raw logs and noisy counts. - Always return a non-empty \"tested\" array describing what you exercised, even when all tests pass. - Only report actionable findings: test failures, unfixable setup issues, flaky tests you identified, or missing evidence that prevents you from demonstrating the user intent. - Do NOT report passing tests (whether existing or new), test counts, coverage summaries, or other non-actionable information. - If all tests pass and there are no issues, return an empty findings array. - Set action to \"ask-user\" for missing-evidence warning findings and only otherwise when a test failure seems desired and you question the author's intent of having the test in the first place. Set action to \"auto-fix\" for objective test failures that can be safely fixed. Set action to \"no-op\" for informational notes. Execution context: - You are running inside an isolated git worktree at the current working directory. - The worktree's `.git` is a pointer file (not a directory) referencing a bare gate repository elsewhere on disk; this is standard git-worktree layout and all normal git commands work as expected. - The worktree is checked out to the change being processed; treat it as the project's source of truth for this run and do not search the filesystem for \"the real\" checkout - this is it. - Operate only within this working directory. Do not modify or read from the gate's bare repository or any other clone of this project.   ## no-mistakes final output contract  When the task is complete, your final assistant response must be only valid JSON matching this JSON Schema. Do not wrap it in Markdown fences. Do not include prose before or after the JSON object.  {   \"type\": \"object\",   \"properties\": {     \"findings\": {       \"type\": \"array\",       \"items\": {         \"type\": \"object\",         \"properties\": {           \"id\": {             \"type\": \"string\"           },           \"severity\": {             \"type\": \"string\",             \"enum\": [               \"error\",               \"warning\",               \"info\"             ]           },           \"file\": {             \"type\": \"string\"           },           \"line\": {             \"type\": \"integer\"           },           \"description\": {             \"type\": \"string\"           },           \"action\": {             \"type\": \"string\",             \"enum\": [               \"no-op\",               \"auto-fix\",               \"ask-user\"             ]           }         },         \"required\": [           \"severity\",           \"description\",           \"action\"         ]       }     },     \"summary\": {       \"type\": \"string\"     },     \"tested\": {       \"type\": \"array\",       \"items\": {         \"type\": \"string\"       }     },     \"testing_summary\": {       \"type\": \"string\"     },     \"artifacts\": {       \"type\": \"array\",       \"items\": {         \"type\": \"object\",         \"properties\": {           \"kind\": {             \"type\": \"string\",             \"description\": \"artifact type such as screenshot, gif, image, video, log, command-output, or other\"           },           \"label\": {             \"type\": \"string\"           },           \"path\": {             \"type\": \"string\",             \"description\": \"artifact file path, including absolute paths for temporary local evidence files when available\"           },           \"url\": {             \"type\": \"string\",             \"description\": \"artifact URL when available\"           },           \"content\": {             \"type\": \"string\",             \"description\": \"short log, command output, or textual artifact content to show inline\"           }         },         \"required\": [           \"label\"         ]       }     }   },   \"required\": [     \"findings\",     \"summary\",     \"tested\",     \"testing_summary\",     \"artifacts\"   ] }" --output-format json --no-color --no-ask-user --allow-all-tools
      41856	no-mistakes.exe	C:\projects\no-mistakes\bin\no-mistakes.exe

[D] fm_proc_info for this bash (Windows pid via winpid):
    winpid of $$ = 41572
      bash.exe	"C:\Program Files\Git\usr\bin\bash.exe" -lc " ROOT=$(pwd) EV=\"/c/Users/kamick/AppData/Local/Temp/no-mistakes-evidence/01KW033PBC45ZCYY2ZQAYK3JCM\" LOG=\"$EV/fm-proc-windows-introspection.txt\" { echo \"=== fm-proc.sh portable process introspection (real MSYS on Windows) ===\" echo \"uname: $(uname -s)\" . \"$ROOT/bin/fm-proc.sh\" echo echo \"[A] fm_proc_is_windows -> $(fm_proc_is_windows && echo yes || echo no)\" echo echo \"[B] Why the Unix path fails here: MSYS ps has no -o support:\" echo \"    \$ ps -o comm= -p \$\$\" ps -o comm= -p $$ 2>&1 | sed \"s/^/      /\" echo echo \"[C] fm_proc_ancestry from this bash (\$\$=$$) walks the *Windows* process tree:\" fm_proc_ancestry $$ | sed \"s/^/      /\" echo echo \"[D] fm_proc_info for this bash (Windows pid via winpid):\" WP=$(ps -p $$ 2>/dev/null | awk \"NR==2 {print \$4}\") echo \"    winpid of \$\$ = $WP\" fm_proc_info \"$WP\" | sed \"s/^/      /\" echo echo \"[E] fm_proc_alive on a real live pid vs a bogus one:\" fm_proc_alive \"$WP\" && echo \"    alive($WP) -> TRUE\" || echo \"    alive($WP) -> FALSE\" fm_proc_alive 999999 && echo \"    alive(999999) -> TRUE\" || echo \"    alive(999999) -> FALSE (bogus pid, as expected)\" echo echo \"=== harness + lock detection that fm-proc powers ===\" echo \"[F] fm-harness.sh (own harness, via env markers / Windows ancestry):\" bash \"$ROOT/bin/fm-harness.sh\" 2>&1 | sed \"s/^/      /\" echo \"[G] fm-harness.sh crew (resolved crewmate harness):\" bash \"$ROOT/bin/fm-harness.sh\" crew 2>&1 | sed \"s/^/      /\" echo \"=== done ===\" } 2>&1 | tee \"$LOG\" echo \"EVIDENCE: $LOG\" "

[E] fm_proc_alive on a real live pid vs a bogus one:
    alive(41572) -> TRUE
    alive(999999) -> FALSE (bogus pid, as expected)

=== harness + lock detection that fm-proc powers ===
[F] fm-harness.sh (own harness, via env markers / Windows ancestry):
      unknown
[G] fm-harness.sh crew (resolved crewmate harness):
      unknown
=== done ===
Evidence: fm-harness.sh detection on Windows (env markers, crew override, ancestry fallback)

CLAUDECODE=1 -> claude ; PI_CODING_AGENT=true -> pi crew-harness=codex -> codex ; default+PI -> pi bare (no marker) -> unknown (actual harness copilot.exe is unrecognized; AGENTS.md says ask the captain)

=== fm-harness.sh harness detection on real Windows ===
[1] env-marker path (OS-independent), recognized harnesses:
    CLAUDECODE=1            -> claude
    PI_CODING_AGENT=true    -> pi

[2] crew resolution honors config/crew-harness override (per-machine):
    crew-harness=codex      -> codex
    crew-harness=default+PI -> pi

[3] ancestry fallback with no env marker on this box -> unknown (actual harness is copilot.exe, an unrecognized crew harness; AGENTS.md says ask the captain on unknown):
    fm-harness.sh           -> unknown
=== done ===
Evidence: fm-bootstrap.sh emits the Windows-specific wezterm multiplexer token

Real box (wezterm present): no multiplexer MISSING line Simulated no-multiplexer Windows box: MISSING: wezterm (install: winget install wez.wezterm # or https://wezfurlong.org/wezterm/install/windows.html)

=== fm-bootstrap.sh multiplexer detection on Windows (MSYS) ===
uname: MSYS_NT-10.0-26100-ARM64

[1] Real box (wezterm on PATH): bootstrap detect output (mux line, if any):
MISSING: gh-axi (install: npm install -g gh-axi && gh-axi setup hooks)
MISSING: chrome-devtools-axi (install: npm install -g chrome-devtools-axi && chrome-devtools-axi setup hooks)
MISSING: lavish-axi (install: npm install -g lavish-axi && lavish-axi setup hooks)

[2] Simulated no-multiplexer Windows box (tmux + wezterm absent):
    -> expect a Windows-specific MISSING: wezterm token, not a generic one
    MISSING: wezterm (install: winget install wez.wezterm  # or https://wezfurlong.org/wezterm/install/windows.html)
=== done ===
- Outcome: ⚠️ 2 warnings across 1 run (25m52s)

Pipeline

Updates from git push no-mistakes

⏭️ **intent** - skipped

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ⚠️ bin/fm-proc.sh:44 - _fm_proc_run_ps leaks a temp file on every Windows invocation. tmp=$(mktemp) creates an empty file (e.g. /tmp/tmp.AbC123), then tmp=&#34;$tmp.ps1&#34; reassigns and cat &gt; &#34;$tmp&#34; writes to the distinct .ps1 path; the original mktemp file is never removed (rm -f &#34;$tmp&#34; only deletes the .ps1). Each fm_proc_ancestry/fm_proc_info/fm_proc_alive call on MSYS (lock acquire/status, harness detection) leaks one 0-byte file. Fix: write to the mktemp path directly, or mktemp --suffix=.ps1, or also rm -f &#34;${tmp%.ps1}&#34;. Secondary nit: the mktemp-absent fallback uses a predictable name in a shared temp dir which cat &gt; would follow through a pre-planted symlink; the primary mktemp path avoids this, so it only matters when mktemp is missing.

🔧 Fix: fix temp-dir leak in fm-proc PowerShell helper
1 info still open:

  • ℹ️ bin/fm-mux.sh:72 - _fm_wez_list's perl parser is order-dependent: it flushes a pane record only when it sees the next &#34;pane_id&#34; line, so it assumes pane_id is the FIRST key emitted within each pane object. If a wezterm version emits title/cwd/cursor_x/cursor_y before pane_id, those values are attributed to the PREVIOUS pane (and the first pane's leading fields are lost), silently corrupting fm_mux_pane_path / fm_mux_cursor_y / fm_mux_pane_alive — which in turn would break spawn worktree detection and the afk busy-guard. The code comment only guarantees key-name non-collision, not field ordering, and tests/fm-mux.test.sh fixture hardcodes pane_id-first so a reorder regression would not be caught. Current wezterm output evidently emits pane_id first (author-verified during the port), so this is a robustness/forward-compat concern rather than a present defect. A field-keyed parse (accumulate per object, emit at object close) or an explicit fixture asserting the assumption would harden it.
⚠️ **Test** - 2 warnings
  • ⚠️ tests/fm-wake-queue.test.sh:303 - On this Windows/MSYS box, test_singleton_start fails with 'expected exactly one live watcher, got 2'. Root cause is environment-specific, not a product regression: the test's 0.5s settle (line 299) is too short for Windows fork/exec, and is_live_non_zombie's ps -p PID -o stat= is unsupported by MSYS ps (the very gap fm-proc.sh exists to bridge). I reproduced the same scenario with a 3s settle and the singleton lock works correctly: exactly one watcher stays live and the loser prints 'watcher: already running pid N' and exits. bin/fm-watch.sh's singleton logic is functionally correct on Windows; this test passes on the project's ubuntu-latest CI. No code change warranted.
  • ⚠️ tests/fm-secondmate.test.sh:906 - On this Windows/MSYS box, test_home_seed_resolves_relative_source_origins fails at 'relative source origin was not cloned through the resolved path'. The assertion compares git's resolved absolute origin (C:/Users/kamick/AppData/Local/Temp/.../relative-alpha.git) against the test helper's pwd -P (/tmp/.../relative-alpha.git) — two equivalent representations of the same directory via the MSYS /tmp mount. The product behavior is correct: the clone succeeded and origin resolved to an absolute path pointing at the right bare repo. bin/fm-home-seed.sh is not modified by this change, and this test passes on the project's ubuntu-latest CI. Environment-specific path-shape artifact, not a product regression. No code change warranted.
  • for t in tests/*.test.sh; do bash &#34;$t&#34;; done (full behavior suite via Git Bash on Windows; all pass except two Windows-only environment artifacts)
  • bash tests/fm-mux.test.sh (both tmux and wezterm backends via fakes — pass)
  • Real WezTerm backend e2e via bin/fm-mux.sh: backend auto-selects wezterm; new-window spawns a live tab; pane-path/cursor-y/pane-alive; send-text+send-enter then capture round-trips a unique marker; kill-window then pane-alive flips to false
  • Real MSYS process introspection via bin/fm-proc.sh: fm_proc_ancestry/fm_proc_info/fm_proc_alive walk the actual Windows process tree (bash.exe -> pwsh.exe -> copilot.exe -> no-mistakes.exe) and confirm ps -o is unsupported on MSYS
  • Harness detection via bin/fm-harness.sh: env markers (CLAUDECODE=1 -> claude, PI_CODING_AGENT=true -> pi), crew override (config/crew-harness=codex -> codex), and ancestry fallback (-> unknown, correct for copilot.exe)
  • Bootstrap multiplexer token via bin/fm-bootstrap.sh: emits Windows-specific MISSING: wezterm (install: winget install wez.wezterm ...) when no multiplexer is present, and detects the present wezterm otherwise
  • Reproduced the singleton-watcher scenario with a 3s settle to confirm the lock leaves exactly one live watcher on Windows
  • Reproduced the relative-origin seed to capture actual-vs-expected origin paths and confirm the clone/origin resolution is correct
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

kamick90 added 7 commits June 23, 2026 12:04
tmux has no Windows port, so route all multiplexer access through
bin/fm-mux.sh (tmux + wezterm backends) and add portable process
introspection in bin/fm-proc.sh (MSYS ps lacks -o). Refactor every
tmux consumer, harness/lock detection, and bootstrap; pin FM_MUX=tmux
in tests; add tests/fm-mux.test.sh; enforce LF via .gitattributes;
update README/CONTRIBUTING/AGENTS.
- fm-spawn: add an OSC-7-independent fallback to worktree detection
  (sentinel $PWD printed into the pane, read back from pane text), gated
  to the wezterm backend so the tmux path stays byte-identical.
- fm-mux: cygpath -w the auto-detected MSYS bash path before passing it to
  the native wezterm cli spawn (a Windows process cannot resolve /usr/bin/bash).
- fm-lock: align HARNESS_RE with fm-harness.sh so it matches the Windows
  process name pi.exe (and pi with args), not only a bare ^pi$.
- fm-bootstrap: emit a per-platform installable token (wezterm on Windows,
  tmux elsewhere) instead of the unknown MISSING: multiplexer token.
Resolve conflicts between the Windows multiplexer-abstraction port and upstream's doc restructure, verified-submit core, and from-firstmate marker:

- fm-send.sh / fm-supervise-daemon.sh: backend-aware - tmux uses the verified fm-tmux-lib submit/composer core; wezterm uses the fm_mux_* abstraction. Marker and settle pause apply on both.

- README/AGENTS: adopt upstream's terse docs/-based structure; Windows specifics migrated into docs/{architecture,configuration,scripts}.md.

- CONTRIBUTING: combine shellcheck bin+tests with the Windows/fm-mux/fm-proc conventions.

- tests: keep upstream helper sourcing, add FM_MUX=tmux backend pin.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants