Skip to content

Contributing

Thanks for taking the time. This is a small project, so the process is short.

Requires Rust 1.88 or newer (edition 2024). Install via rustup.

  1. Clone the repository

    Terminal window
    git clone https://github.com/LebedevKondakovSergeyVach/Terminal-Session-Proxy-Manager.git
    cd Terminal-Session-Proxy-Manager
  2. Build the project

    Terminal window
    cargo build
  3. Run the tests

    Terminal window
    cargo test

To try your build without installing it:

Terminal window
cargo run -- status
cargo run -- profile list

Run what CI runs. Warnings are build failures.

Terminal window
cargo fmt --all -- --check && cargo clippy --all-targets --locked -- -D warnings && cargo test --locked

CI also passes --all-features; the crate defines no features, so this checks the same code.

Every user-facing string is translated. Use i18n.t("key"), or i18n.format("key", &[a, b]) when it has {} placeholders, and add the key to both locales/en.json and locales/ru.json. Tests assert the two files define identical key sets, so a one-sided addition fails the build. Adding a subcommand also needs a cmd_<name> key in both files, otherwise --help prints the raw key.

Never overwrite a config that failed to parse. Defaults are written only when a file is absent. Overwriting an unparsable config.json discards every profile the user has.

Quote anything a shell will evaluate. The env on output is consumed by the shell’s eval, and profile fields come from a file the user can edit or import from a URL. Route values through proxy_env::shell_quote.

Failure means a non-zero exit code. Return Err rather than printing an error and returning Ok — people write proxy profile use "$p" || fallback.

No private data in defaults. AppConfig::default may reference only loopback addresses and well-known public endpoints.

  • Directorydocs/ Documentation markdown files
    • …
  • Directorylocales/
    • en.json English translation keys
    • ru.json Russian translation keys
  • Directorysrc/
    • Directorycmd/ Subcommands logic (ping, monitor, etc.)
      • …
    • Directoryconfig/ Config loading and defaults
      • …
    • cli.rs CLI parsing tree (clap)
    • main.rs Entry point
  • Directorytests/
    • cli.rs End-to-end binary tests
  • Directorywebsite/ Astro Starlight documentation site
    • …
  • AGENTS.md Rules for AI agents
  • Cargo.toml

Name tests as sentences describing the guarantee — using_an_unknown_profile_exits_non_zero rather than test_profile — and assert one thing per test.

  • Pure logic goes in a #[cfg(test)] mod tests next to the code.
  • Anything involving argv, exit codes, or files goes in tests/cli.rs.
  • Integration tests must isolate state with TSPM_CONFIG, TSPM_SETTINGS and TSPM_LANG; use the Cli helper at the top of tests/cli.rs. A test must never touch the real ~/.config.
  • Please don’t add tests that need network access. They are slow and flaky.

When fixing a bug, add the test that fails without the fix.

If you change commands or configuration, update README.md and README.ru.md, the matching file in docs/ and its .ru.md twin, and add a CHANGELOG.md entry under Unreleased plus its CHANGELOG.ru.md twin.

Those same files are the documentation website. website/ holds an Astro Starlight site whose pages are generated from them, so editing one is editing the other. Two things follow:

  • Keep them plain CommonMark. They are read on GitHub as well as on the site, so no JSX and no import line. Site-only structure — tabs, cards, steps, asides — is written as <!--site:...--> comments, which GitHub renders as nothing and the generator expands. website/AGENTS.md documents the syntax.

  • Verify the site still builds:

    Terminal window
    cd website && npm ci && npm test && npm run build

    The build regenerates every page and fails on a dead internal link, so a link to a document that has no page will stop it.

main <-- release/X.Y.Z <-- feat/… fix/… docs/…

Branch your work off the open release branch, not off main, and open the pull request against that same release branch:

Terminal window
git checkout release/X.Y.Z && git pull
git checkout -b fix/dashboard-empty-list

main only ever moves when a release branch is merged into it, and that merge publishes the GitHub release, the Homebrew tap and the tspm crate. CI rejects a pull request that targets the wrong branch or uses an unrecognised branch prefix. Valid task prefixes: feat/, fix/, docs/, chore/, refactor/, test/, perf/, ci/, build/.

Full rules, including how a release is cut, are in .ai/GIT_WORKFLOW.md.

Any pull request touching src/, locales/, shell/, configs/ or Cargo.toml needs an entry under ## [Unreleased] in CHANGELOG.md and the matching entry in CHANGELOG.ru.md. CI checks both: a behaviour change without a CHANGELOG.md entry fails, and so does a CHANGELOG.md change without its Russian twin, because both changelogs are published. Docs-only and CI-only changes are exempt.

Conventional Commits: feat:, fix:, docs:, chore:, refactor:, test:, perf:, ci:, build:.

Enable the repository’s Git hooks once per clone:

Terminal window
git config core.hooksPath .githooks

commit-msg rejects tool attribution lines such as Co-Authored-By: or “Generated with …”, and pre-push refuses pushes to main and tag pushes — releases are cut by merging, and CI creates the tag.

Maintainers only. Cutting a release means opening release/X.Y.Z from main, setting the version and changelog, and merging it into main when ready — the pipeline does the rest. Never tag by hand as part of another change.

Point it at AGENTS.md first; it encodes the rules above in the form agents follow. Claude Code loads it through CLAUDE.md; Cursor routes to it through .cursor/rules/. Path rules and task skills live in .claude/; .ai/PROJECT_OVERVIEW.md maps ownership and execution flows.

Diagram

Drag to move. Pinch or scroll to zoom. Keyboard: arrows, +, −, 0.