DimitriGilbert / DimitriGilbert/sshm0
Test & quality alignment plan
- Dominant language
- Shell
- Stars
- 3
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
# sshm0 — Test & Quality Alignment Plan
_Revised 2026-09-05: CI removed by owner decision — quality gates are local commands run by the owner/agents; no GitHub Actions._
- **Project**: `sshm0` (`/home/didi/workspace/sshm0`, origin `DimitriGilbert/sshm0`)
- **Date**: 2026-09-05
- **Generated by the fleet test-alignment initiative** (audit + synthesis phases complete; this is the per-project implementation plan — plan only, nothing implemented yet)
- **Audit report**: [../.test-alignment-audit/reports/sshm0.md](../.test-alignment-audit/reports/sshm0.md)
- **Fleet synthesis / decisions**: [../.test-alignment-audit/synthesis-decisions.md](../.test-alignment-audit/synthesis-decisions.md)
**Repo reality at plan time** (HEAD `d7e341e`, clean tree): pure-Bash CLI (`sshm0` entry, 20 subcommand scripts + sourced `bin/utils` in `bin/`, installer in `utils/`), vendored bats-core suite (`test/run`, 3 case files, 37 `@test` blocks, hermetic `test/helpers/common.bash` sandbox), `completely.yaml` → committed `completely.bash`/`completely.zsh`, and a TanStack Start docs site in `doc_site/` (own `package.json` + `pnpm-lock.yaml`). No `.github/`, no CI, no hooks, no `.shellcheckrc`, no root `package.json`, no AGENTS.md, and `readme.md` has no Development/Testing section. `shellcheck`/`shfmt` are **not installed** on the dev machine (Phase 0 makes shellcheck available locally or via container; shfmt only if the report-only path is taken).
---
## 1. Adopted decisions (D1–D7 translated to a Bash repo)
| Decision | Adoption in sshm0 |
|---|---|
| **D1 — runner per ecosystem** | **Vendored bats stays** (explicitly sanctioned by D1: "parseArger: bashunit stays; sshm0: vendored bats stays"). The headless one-command entry already exists: `./test/run`. Deviation from the TS fleet default (vitest): **N/A by design for the bash core** — vitest applies only inside `doc_site/`, which is genuine TS. |
| **D2 — coverage** | **Bash core: coverage not required** (D2: "Bash repos: coverage not required"). Bats TAP/JUnit formatters ship vendored; a TAP output command is available report-only (no kcov, no gate). `doc_site/`, if it keeps tests, reports coverage report-only (no thresholds — D2 state 3 until seeds exist). |
| **D3 — lint & format** | Fleet lint concept → **`shellcheck` (gate) + a committed `.shellcheckrc`**. The codebase is already ShellCheck-shaped (pragmas in 22/23 own scripts). Deviation on shfmt: see §3. ESLint/Prettier (TS standard) apply only to `doc_site/` and are deferred there (small marketing site; `check-types` is its typecheck-analogue gate). |
| **D4 — quality metrics** | knip / jscpd / CRAP / Stryker: **N/A for the bash core** (all are TS-toolchain tools; D4's bash-repo silence is the decision). **Renovate applies**: one-liner `renovate.json` extending `local>DimitriGilbert`, which also fixes the six `"latest"` pins in `doc_site/package.json`. |
| **D5 — no CI by design (owner decision 2026-09-05)** | Adopted: **no GitHub Actions, no reusable-workflow caller, no local workflow.** The earlier "in-repo `ci.yml` migrating to a `DimitriGilbert/.github` `bash-ci.yml` caller" plan is dropped entirely. Enforcement = a root-level headless verification entrypoint (`make check`: `./test/run` + doc-site `check-types`, shellcheck appended in Phase 2) composed in Phase 1 and run by the owner and by agents; the plan's phase validation gates are run by implementer/validator agents before any phase is declared done. |
| **D6 — shared config distribution** | The repo-local `.shellcheckrc` is the template other bash repos copy (org-repo hosting dropped per D5). `@dg/config` (npm): N/A for bash; `doc_site/` may adopt presets only when it touches its config anyway (D6 adoption rule). |
| **D7 — hooks policy** | **No hooks.** The local gate commands run by the owner/agents are the only authoritative gate; `AGENTS.md` (created in Phase 6) states: "the headless local verification command (`make check`) is the quality gate, run by the owner/agents; hooks are local convenience." A repo with no hooks and a green `make check` is compliant. |
## 2. Synthesis Definition of Done — per-item current status
| # | DoD item | Status in sshm0 today |
|---|---|---|
| 1 | Root-level headless test command, documented in AGENTS.md | **Half done.** `./test/run` exists and is headless (vendored bats, zero install). Undiscoverable: no AGENTS.md exists and `readme.md` has no Development section (fixed in Phase 6). |
| 2 | Headless verification entrypoint running typecheck + test + lint in one local command; agent records green before a phase is done | **Missing.** `./test/run` exists but no aggregate command chains it with the doc-site typecheck analogue (Phase 1). |
| 3 | Coverage measured & reported; thresholds only where D2 allows | **N/A-by-decision for bash core** (D2: bash coverage not required). Doc-site: report-only if tests land (Phase 6). No threshold will ever be set on an unmeasured suite. |
| 4 | Language-appropriate lint, zero errors, wired into the headless entrypoint | **Missing.** Pragmas everywhere but shellcheck never runs, no `.shellcheckrc` (Phases 0+2). "Dead turbo lint task" clause: N/A (no turbo/makefile). |
| 5 | AGENTS.md Testing section matches reality | **Missing.** No AGENTS.md; readme documents zero dev workflow (Phase 6). |
| 6 | Dead scaffold test deps removed or used | **Failing today.** `doc_site/` installs `vitest` + `jsdom` + `@testing-library/{react,dom}` with **zero** test files (decided and fixed in Phase 6). Bash core: no dead deps (vendored bats is used). |
| 7 | Renovate one-liner; no `"latest"` deps | **Failing today.** No `renovate.json`; `doc_site/package.json` floats 7 deps on `latest` incl. `nitro-nightly` (Phase 6). |
| 8 | Quality-report job (knip+jscpd, CRAP where coverage) | **N/A** for bash (D4). No TS packages to analyze; doc-site is below the value bar. Explicit deviation, not silent omission. |
| 9 | Nothing fake-green | **Holds today and must keep holding**: no `passWithNoTests` anywhere; if doc-site keeps vitest, `vitest run` with zero test files fails by default — never add the flag. |
## 3. Deviations from synthesis (justified, per synthesis preamble)
1. **TS fleet tooling N/A for the bash core** — vitest/coverage-v8/ESLint/knip/jscpd/CRAP have no target here. Analogues adopted: bats (D1), shellcheck+`.shellcheckrc` (D3), TAP output report-only (D2). This is sanctioned by D1/D2/D3 text itself, restated here for visibility.
2. **shfmt deferred, not gated (D3 delta)** — parseArger regenerates the large parsing blocks inside `sshm0` and every `bin/` script; reformatting them in-repo is generator-fighting churn that the next regeneration would revert. shellcheck (semantic) gates; shfmt waits for an upstream parseArger formatting story or a formatting-cold day, and is report-only until then.
3. **Gate composed before lint lands (D5 delta, revised 2026-09-05)** — no CI at all. Phase 1 composes the `check` entrypoint (`./test/run` + doc-site `check-types`) with the shellcheck leg reserved for Phase 2, so turning lint on is a one-line makefile edit, not a re-implementation.
4. **`set -euo pipefail` promoted from "consider" to a committed phase** — the audit P2 names the partial-state failure class, and the config-corruption bugs fixed in commit `3d42d9c` are exactly that class. Phased carefully (Phase 3) because strict mode interacts with sourced server files and interactive `read` prompts.
5. **Renovate scoped to doc_site** (the only dependency surface); quality-report job dropped entirely (DoD item 8) as there is nothing meaningful to measure.
---
## 4. Phased plan
Execution methodology: **subagent-orchestration**. Each phase (and each sub-phase within a phase) = one **implementer** dispatch (complete requirements pasted from this plan, NO-SLOP policy, runs the phase gatekeeping commands itself before reporting done) followed immediately by a **validator** dispatch (must actually read every changed file line-by-line, enforce NO-SLOP, re-run gates; never modifies code). Validation failure → one **fixer** dispatch with ALL validator findings at once → re-validate; max 3 fix attempts, then halt and report. Multi-sub-phase phases get a **phase-wide validator** after all sub-phases pass individually (integration, shared helpers, no duplicated stub logic, suite coherence).
**NO-SLOP policy, adapted to Bash** (pasted verbatim into every implementer/fixer dispatch; enforced by every validator):
- NO placeholder code, NO `TODO`/`FIXME` comments, NO fake success states.
- NO unquoted variable expansions without a documented reason; no new ShellCheck disables without an inline justification comment.
- NO tautological test assertions — every new `@test` must exercise real `bin/` code through the public CLI surface or sourced functions and assert on observable effects (exit codes, file contents, recorded stub argv).
- NO unused variables/functions; no `echo`-only smoke tests.
- NO network access, NO real `ssh`/`scp`/`rsync`/`git` invocations from tests — stubs only.
- Gatekeeping is mandatory and is NOT validation: implementers/fixers run `./test/run` (and `shellcheck` where in scope) and fix failures before reporting.
Phases are **sequential** (sub-phases within a phase run sequentially too: they all share `test/helpers/` conventions and, in Phase 2, the same 23 scripts — parallel implementers would collide).
### Phase 0 — Lint baseline and `.shellcheckrc` (size: S)
**Goal**: Know exactly what ShellCheck says today, and give the repo a committed lint config — before the lint leg of the gate turns on, so it starts green.
**Tasks (implementer)**:
1. Make shellcheck available locally (install, or use `koalaman/shellcheck` container). Record the version used.
2. Create `/home/didi/workspace/sshm0/.shellcheckrc`: `shell=bash` defaults, disable list limited to findings that are genuinely intentional, **each with a justification comment** (expected: a handful; the audit predicts near-clean).
3. Run `shellcheck sshm0 bin/* utils/install utils/get_sshm0 test/run test/helpers/common.bash` and produce a findings inventory classified as must-fix (Phase 2) vs justified-disable (goes into `.shellcheckrc` now). This inventory is deliverable output of the phase (phase report), not a repo file.
4. No production script edits in this phase.
**Validation gate (validator runs all)**: `./test/run` exits 0 with all 37 cases green (baseline unchanged); `shellcheck` with the new `.shellcheckrc` exits 0 over the full file list; every disable in `.shellcheckrc` carries a justification comment; `git status` shows only `.shellcheckrc` added.
**Roles**: implementer → validator → (fixer loop ≤3).
### Phase 1 — Gate composition: one command runs the 37 cases + doc-site typecheck (size: S)
**Goal**: The regression suite never silently rots again — a single headless command runs `./test/run` plus the doc-site typecheck analogue.
**Tasks (implementer)**:
1. Create a root `makefile` (parseArger precedent; or a `check` script if the implementer prefers — pick one, document it):
- `test:` target → `./test/run` (one line; vendored bats means no setup step).
- `check:` target → `./test/run` **plus** the doc-site typecheck leg: `pnpm --dir doc_site install --frozen-lockfile && pnpm --dir doc_site check-types`. No doc-site `test` leg yet (zero test files — see Phase 6); no site build (nitro-nightly float; build gate deferred to Phase 6).
- The `lint` leg is deliberately absent until Phase 2 (shellcheck appended then) — one-line edit when it lands.
2. No workflow file is created anywhere; nothing depends on `DimitriGilbert/.github` (D5).
**Validation gate**: `make check` (or chosen entrypoint) exits 0 from repo root — the `./test/run` leg green with all 37 cases; the doc-site check-types leg passes, or its known-broken state is documented and the leg is excluded from `check` until Phase 6 fixes the pins (recorded in the phase report); the implementer/validator agents record the green output before the phase is declared done (D5); no workflow file exists.
**Roles**: implementer → validator.
### Phase 2 — ShellCheck remediation: make the lint gate real (size: M — 3 sub-phases + phase-wide validation)
**Goal**: Every own script passes `shellcheck` with the Phase 0 `.shellcheckrc`, and the gate's lint leg turns on.
**Sub-phases** (each: implementer → validator, fix loop ≤3):
- **2a — Core**: `sshm0` + `bin/utils` (the sourced library every command depends on; quoting/`-u`-safety fixes here have the widest blast radius). Fix all must-fix findings; keep pragma discipline; where a finding is intentional, add a targeted inline `# shellcheck disable=` with reason rather than widening `.shellcheckrc`.
- **2b — The 20 `bin/` subcommand scripts**: same treatment. **Constraint**: fixes inside `@parseArger`-generated parsing blocks are committed generated code — record every such fix in the phase report as an upstream-parseArger item (deviation 2); do not restructure generated code.
- **2c — Installer + tests**: `utils/install`, `utils/get_sshm0` (348+235 lines, handles curl piping and shellrc edits — highest real-world quoting risk after the core), plus `test/run` and `test/helpers/common.bash`.
**Phase-wide validation (mandatory)**: one validator reads all changed scripts together — consistent quoting conventions, no fix duplicated inconsistently between `bin/` scripts, no finding "fixed" by deleting functionality.
**Tasks after all sub-phases pass (implementer)**: append `shellcheck` to the `check` entrypoint (turning the lint leg on — one-line makefile edit); add the pinned-shfmt install + `shfmt -d` as a **report-only** (non-blocking, documented) command — gate is shellcheck only (deviation 2).
**Validation gate**: `shellcheck` exits 0 over `sshm0 bin/* utils/install utils/get_sshm0 test/run test/helpers/common.bash`; `./test/run` all green (37 cases, unchanged count — this phase fixes lint, it adds no tests); `make check` (now including the shellcheck leg) green.
### Phase 3 — Strict mode: `set -euo pipefail` in production scripts (size: M — 2 sub-phases)
**Goal**: Mid-script failures stop the script instead of continuing with partial state (the class behind the `3d42d9c` corruption bugs).
**Sub-phases**:
- **3a — Hazard audit (implementer, read-mostly)**: walk `bin/utils` + every `bin/` script and catalogue `-e`/`-u`/pipefail hazards with exact lines: (a) sourced server files may leave optional fields unset (`sshm0_server_proxy`, `sshm0_server_group`, …) — every use needs a `:-` default (note: assoc-array lookups like `${sshm0_plugins[$1]}` are not `-u` errors, positional refs are); (b) interactive `read -rp` prompts (`bin/exec` multi-server confirm, `bin/backup --restore` confirm) — under `-e` a `read` EOF aborts; needs explicit `|| true` + default handling; (c) pipelines whose first command may intentionally fail (`grep -q … |` patterns); (d) arithmetic that may evaluate unset vars. Output: the hazard list, each mapped to its fix.
- **3b — Enable (implementer)**: add `set -euo pipefail` **once in `bin/utils`** (every command sources it; the entry `sshm0` also sources it — one source of truth, no 23-script duplication), apply the 3a fixes, and add regression cases to `test/cases/` covering previously-silent partial-state paths (e.g. aborted confirm → clean exit 0 and untouched config; missing optional server fields → command still completes).
**Validation gate**: full `./test/run` green including the new regression cases; `shellcheck` still exits 0 (Phase 2 gate holds); manual spot-check recorded in phase report: `sshm0 list`, `add`, `connect --help` behave identically pre/post (help/usage paths are `-u`-sensitive).
**Roles per sub-phase**: implementer → validator; 3a's validator additionally confirms the hazard list is exhaustive (no `read`/pipeline/source site unaccounted for).
### Phase 4 — Hermetic remote-execution tests: connect, exec, ping, cp, rsync (size: L — 3 sub-phases + phase-wide validation)
**Goal**: The highest-blast-radius, currently-untested surface — commands that spawn `ssh` — gets real tests without any network or real host.
**Sub-phases**:
- **4a — Stub harness (implementer)**: create `test/helpers/stubs.bash` + `test/fixtures/stub-bin/` containing fake `ssh`, `scp`, `rsync`, `git` (git stub is consumed by 5b; created here so the harness lands once). Design: stubs append `"$0 $*"` (argv, shell-quoted) to `$SSHM0_SANDBOX/stub-log`, then emit canned output / exit `$SSHM0_TEST__EXIT` (default 0); sandbox setup prepends the fixture dir to `PATH`. Extend `test/helpers/common.bash` with `sshm0_setup_sandbox` additions + an assertion helper `sshm0_assert_stub_called `. Seed servers via existing `add` (as `03-config-roundtrip.bats` already does).
- **4b — connect + ping (implementer)**: cases asserting the exact ssh invocation: `user@ip`, `-p port`, `-i key` only for key-auth, `ConnectTimeout` from server field, `ProxyJump` for proxied servers (stub records argv through the real `sshm0_get_proxy_args` path), password-auth path uses `SSH_ASKPASS` + `setsid` and **the plaintext password never appears in argv** (assert on the log; the stub sees `SSH_PASSWORD` in env — assert the askpass script is cleaned up). Ping: reachable (exit 0, "reachable" output) vs unreachable (`SSHM0_TEST_SSH_EXIT=1` → exit 1, "unreachable").
- **4c — exec + cp + rsync (implementer)**: `exec`: missing `--` command → die; single server; comma list; `all`; `--tag`/`--group` selection (seed servers with those fields); multi-server requires confirm (pipe `n` → "Aborted.", stub log empty; pipe `y` → both run); `--yes` skips; `--parallel` fan-out (stub with per-invocation exit codes via a counter file); exit code = failed-server count (`SSHM0_TEST_SSH_EXIT=1` on one of two → exit 1, "1 failed" output). `cp`/`rsync`: argv construction, key vs password auth, proxy args.
**Phase-wide validation (mandatory)**: one validator reads the harness + all new case files together — one stub mechanism (not per-file ad-hoc stubs), sandbox isolation intact (no test can reach a real `ssh`), every case asserts behavior not implementation trivia, no duplicated server-seeding logic (factor into helpers where ≥3 copies appear).
**Validation gate**: `./test/run` green with the grown suite (expect roughly +20–30 cases); new test files pass `shellcheck`; no test takes network (the suite is hermetic by construction — stubs only); suite runtime stays trivial (<30s).
### Phase 5 — Data-fidelity + remaining commands: backup/restore, export, import, update, and the low-danger tail (size: L — 3 sub-phases + phase-wide validation)
**Goal**: Close out the 15 untested commands; data-movement commands first, the self-updater under strict confinement, the trivial tail last.
**Sub-phases**:
- **5a — backup/restore + export + import (implementer)**: `backup`: creates `$SSHM0_CONFIG_DIR/backups/sshm0-backup-*.tar.gz` (or `-o` path) containing exactly the config dir; **restore negative paths are mandatory** — crafted archives with `../` traversal members, absolute-path members, and out-of-base members must be rejected (the guard at `bin/backup:258-272` is security code with zero tests); `--restore` of a round-trip backup restores server files + config byte-semantics (assert via re-source, per existing helper conventions); non-`.tar.gz` and missing file rejections; confirm prompt honored/bypassed (`-y`). `export`: golden-file test — seeded two-server config (one key-auth, one password-auth with proxy) → assert the generated `~/.ssh/config`-format text matches an expected heredoc (stable fields; normalize the date line). `import`: import a sample ssh-config fixture → servers created with correct fields (incl. decryption round-trip and rejection of malformed input).
- **5b — update (implementer)**: runs **only** against the git stub from 4a. Assert: non-git root → die with the reinstall hint; normal path → stub records `fetch origin main` + rev-list counting, and **`git reset --hard` is never called without `--force`**; `--force` path → reset + `chmod +x` recorded; network-failure path (`SSHM0_TEST_GIT_EXIT=1`) → clean die. This command is the reason the stub harness exists: `bin/update` executes `git -C "$SSHM0_ROOT_DIR" reset --hard origin/main`, and `SSHM0_TEST_ROOT` in tests is the real checkout — a test that lets real `git` through is a working-tree destroyer. The validator must verify confinement explicitly (PATH order + stub-log evidence in every update case).
- **5c — Low-danger tail (implementer)**: `history` (record on connect via stub, list/format, clear), `tags` (list/add/remove on seeded servers), `show` (masking of encrypted passwords — assert ciphertext or mask, never plaintext, in output), `config` (get/set global fields incl. encryption key round-trip), `doctor` (healthy sandbox → exit 0, diagnostics; missing openssl-on-PATH scenario via PATH manipulation → warning path), `plugin` (register/list/run a fixture plugin script in the sandbox).
**Phase-wide validation (mandatory)**: suite coherence pass — after 5a–5c all 20 commands have at least one case; no coverage-theater (each new case named for behavior); helpers updated once, not per-file.
**Validation gate**: `./test/run` green; `shellcheck` green over all new files; README command list cross-checked: every documented command appears in at least one `@test` block (grep-based check recorded in the phase report).
### Phase 6 — doc_site decision, Renovate, and docs (size: M — 2 sub-phases)
**Goal**: Resolve the dead-scaffold question, pin the floating deps, and make the whole workflow discoverable.
**Sub-phases**:
- **6a — doc_site: give the scaffold real tests (primary decision)**: keep `vitest` and write real tests: `src/lib/utils.ts` (`cn`), `src/components/docs/terminal-demos.ts` (pure data/logic), and one component render test (`ThemeToggle` or `SidebarNav`) using the already-installed RTL + jsdom; wire `pnpm test` into the doc_site leg of the `check` entrypoint alongside `check-types`. **Fallback (decided now, not open-ended)**: if the component test cannot be made to pass against the current site code within the phase, strip the dead scaffolding instead — remove `vitest`/`jsdom`/`@testing-library/*` from `doc_site/package.json`, delete the `test` script, keep `check-types` as the leg's only gate. Either landing satisfies DoD item 6; drift (installed-but-unused) does not.
- **6b — Deps + Renovate + docs (implementer)**: replace the 7 `"latest"` pins in `doc_site/package.json` (`@tanstack/react-devtools`, `@tanstack/react-router`, `@tanstack/react-router-devtools`, `@tanstack/react-router-ssr-query`, `@tanstack/react-start`, `@tanstack/devtools-vite`, `nitro → npm:nitro-nightly@latest`) with real resolved versions from `pnpm-lock.yaml` (the lockfile already knows what `latest` meant at install time); add root `renovate.json` = `{ "extends": ["local>DimitriGilbert"] }`; add a **Development** section to `readme.md` (`make check`, `./test/run`, vendored bats, shellcheck gate, gates-are-local statement, doc_site commands); create **`AGENTS.md`** (none exists) with the testing section matching reality post-plan, the gate statement per D7 ("the headless local verification command (`make check`) is the quality gate, run by the owner/agents; hooks are local convenience"), and the pnpm-for-doc_site rule.
**Validation gate**: `pnpm --dir doc_site check-types` exit 0; `pnpm --dir doc_site test` exit 0 with ≥3 real tests (or, in the fallback branch, the deps are gone and `check-types` remains green); `no "latest"` grep over `doc_site/package.json` clean; `./test/run` still green; `renovate.json` present; readme/AGENTS.md claims match verified commands only (validator greps each documented command).
### Phase 7 — Polish: completions freshness, assertion consistency, TAP output (size: S)
**Goal**: Audit P3 items worth their cost; explicitly close the plan.
**Tasks (implementer)**:
1. Completion freshness: a local command (or makefile target) running `completely generate completely.yaml` into a temp dir and `diff`ing against committed `completely.bash`/`completely.zsh` — **only if the `completely` tool is installable locally** (Ruby gem); otherwise record as not-adopted with reason in the phase report and skip. If adopted, regenerate first locally so the check starts green.
2. Migrate the raw `[ ]`/`[[ ]]` assertions in `test/cases/02-password-crypto.bats` to `bats-assert` (`assert_success`/`assert_output`/`refute_output`) for failure-diff quality, matching the style of `01-utils-pure.bats`.
3. Emit `./test/run --formatter tap` (or `--tap`) output report-only as an optional command/target (vendored formatter; no gate, no kcov — D2).
**Validation gate**: `./test/run` green with identical case count (style migration only); completions diff clean (if adopted); the full `check` entrypoint green end-to-end.
### Follow-ups out of scope for this plan (recorded, not scheduled)
- Mirror any Phase 2 fixes made inside `@parseArger`-generated blocks upstream into parseArger's templates.
- `shfmt -d` as a blocking gate once the generated-code formatting question is settled (deviation 2).
- kcov line-coverage over bash (D2 says not required; revisit only if the suite's blind spots start producing bugs).
---
## 5. Risks & notes specific to this repo
1. **Testing SSH commands without real hosts** — everything remote goes through PATH stubs (`test/fixtures/stub-bin/`) recording argv to a log inside the per-test `mktemp` sandbox. The existing hermetic pattern (`SSHM0_CONFIG_DIR` + fixed key, `test/helpers/common.bash:15-29`) extends naturally; no test ever resolves real `ssh`/`scp`/`rsync`/`git`. The `setsid ssh` invocation (`bin/exec:399`, `bin/connect:322`) still resolves the stub because `setsid` execs `ssh` from the inherited `PATH` — verified as an acceptance check of sub-phase 4a.
2. **`bin/update` is a loaded gun in tests** — it runs `git -C "$SSHM0_ROOT_DIR" reset --hard origin/main`, and the test root *is* the real checkout. Sub-phase 5b is built around stub-git confinement, and the validator must prove no case can invoke real `git`. (In-repo `reset --hard` is a legitimate user feature of `update`; it just must never fire from the suite.)
3. **Strict mode vs. sourced user data and prompts** — `bin/utils` is sourced into every command; server files written by `add`/`import` may legitimately lack optional fields, and `read -rp` confirmations EOF-fail under `-e`. Phase 3a's audit exists precisely so 3b doesn't turn "partial state" bugs into "hard crash on legacy configs" bugs. Legacy v1→v2 config migration (`sshm0_migrate`) must keep working — add a migration case in 3b.
4. **parseArger-generated code** — the parsing blocks in `sshm0` and all 20 `bin/` scripts are committed generator output; in-repo fixes there (Phase 2b) are provisional until mirrored upstream. This is also why shfmt is not a gate (deviation 2).
5. **doc_site may already be broken** — `nitro` pinned to `nitro-nightly@latest` plus six TanStack `latest` floats means `build` may not be reproducible. Phase 6 deliberately gates on `check-types` + tests, **not** `build`, and does not let a broken site build block the bash-core alignment.
6. **Local tooling gaps** — `shellcheck`/`shfmt` are not installed on the dev machine; Phase 0 makes shellcheck available (locally or via container) and the report-only shfmt command pins it if adopted.
7. **The suite has no secrets and no network targets — by design** — if any future test needs a credential or a real host, it does not belong in this suite (that would be an integration tier with explicit opt-in, out of scope).
8. **Vendored bats (435 files) stays** — zero-install onboarding is a feature the audit lists as a strength; do not "clean it up".
---
## 6. Effort summary
| Phase | Goal | Size | Sub-phases |
|---|---|---|---|
| 0 | Lint baseline + `.shellcheckrc` | S | — |
| 1 | Gate composition: one command runs the 37 cases + doc-site check-types | S | — |
| 2 | ShellCheck remediation; lint leg on; shfmt report-only | M | 2a core, 2b bin/, 2c installer+tests |
| 3 | `set -euo pipefail` hardening | M | 3a hazard audit, 3b enable+regressions |
| 4 | Stub harness + connect/exec/ping/cp/rsync | L | 4a harness, 4b connect+ping, 4c exec+cp+rsync |
| 5 | backup/export/import, update (confined), low-danger tail — all 20 commands covered | L | 5a data fidelity, 5b update, 5c tail |
| 6 | doc_site tests-or-strip, dep pins, Renovate, readme Development + AGENTS.md | M | 6a doc_site, 6b deps+docs |
| 7 | Completions freshness, bats-assert consistency, TAP output | S | — |
First-phase goal: know the ShellCheck baseline and commit `.shellcheckrc` so the lint leg of the gate (Phase 1–2) turns on green from day one.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with ./test/run, test/helpers/common.bash, sshm0, bin/, utils/install, and doc_site/package.json; run the existing 37-case suite and read the linked audit report and synthesis decisions. Implement the phases in order, keeping the local check entrypoint, ShellCheck configuration, strict-mode changes, dependency cleanup, and AGENTS.md/readme.md workflow documentation aligned. Done means each phase gate passes, no CI or hooks are added, and the documented local quality command is green.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bash, typescript
- Domain
- build-system, developer-experience, documentation, testing, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100