| 1 | # Contributing to Reasonix |
| 2 | |
| 3 | Thank you for your interest in contributing to Reasonix! This guide covers |
| 4 | everything you need to get started. |
| 5 | |
| 6 | ## Prerequisites |
| 7 | |
| 8 | - **Go 1.26+** — use the toolchain pinned in `go.mod` (`GOTOOLCHAIN=auto`) |
| 9 | - **Git** — for version control |
| 10 | - **Node.js 24+ and pnpm 10** (optional) — only if you work on the desktop app |
| 11 | (`desktop/`) |
| 12 | |
| 13 | ## Getting started |
| 14 | |
| 15 | ```bash |
| 16 | git clone https://github.com/esengine/DeepSeek-Reasonix.git |
| 17 | cd DeepSeek-Reasonix |
| 18 | go build ./cmd/reasonix # builds the CLI binary |
| 19 | go test ./... # runs the full test suite |
| 20 | ``` |
| 21 | |
| 22 | ## Project structure |
| 23 | |
| 24 | | Directory | Purpose | |
| 25 | |-----------|---------| |
| 26 | | `cmd/reasonix` | CLI entry point | |
| 27 | | `internal/agent` | Agent loop, session, coordinator | |
| 28 | | `internal/cli` | TUI, subcommands, setup wizard | |
| 29 | | `internal/control` | Transport-agnostic controller | |
| 30 | | `internal/config` | TOML configuration loading | |
| 31 | | `internal/tool/builtin` | Built-in tools (bash, read_file, …) | |
| 32 | | `internal/provider` | Model-backend abstraction | |
| 33 | | `internal/provider/openai` | OpenAI-compatible provider | |
| 34 | | `internal/plugin` | MCP client (stdio + HTTP) | |
| 35 | | `internal/event` | Typed event stream | |
| 36 | | `internal/hook` | Shell hooks (PreToolUse, …) | |
| 37 | | `internal/memory` | REASONIX.md hierarchy + auto-memory | |
| 38 | | `internal/skill` | Skill discovery from Markdown | |
| 39 | | `internal/sandbox` | OS-level sandboxing | |
| 40 | | `internal/serve` | HTTP/SSE server frontend | |
| 41 | | `internal/checkpoint` | Snapshot-based rewind | |
| 42 | | `desktop/` | Electron desktop app + Go service (separate Go module) | |
| 43 | | `docs/` | Engineering spec, migration guide | |
| 44 | |
| 45 | ### Dependency direction |
| 46 | |
| 47 | ``` |
| 48 | cli → {agent, plugin, config} → {tool, provider} |
| 49 | ``` |
| 50 | |
| 51 | Built-in subpackages import their parent to self-register via `init()`. |
| 52 | Parents never import children. |
| 53 | |
| 54 | ## Development workflow |
| 55 | |
| 56 | ### Building |
| 57 | |
| 58 | ```bash |
| 59 | make build # go build ./... |
| 60 | make test # go test ./... |
| 61 | make vet # go vet ./... |
| 62 | make fmt # gofmt -w . |
| 63 | make hooks # install git hooks (pre-push: go vet) |
| 64 | make cross # cross-compile for all 6 targets |
| 65 | ``` |
| 66 | |
| 67 | ### Isolated development environment |
| 68 | |
| 69 | A source-built binary shares no on-disk state with a stable release when launched |
| 70 | with `REASONIX_HOME` set. This gives each build its own self-contained directory |
| 71 | tree — config, credentials, sessions, cache, skills, commands, hooks, and |
| 72 | desktop tab state — so the two builds never interfere: |
| 73 | |
| 74 | **CLI** |
| 75 | |
| 76 | ```bash |
| 77 | REASONIX_HOME=/tmp/reasonix-dev go run ./cmd/reasonix |
| 78 | # or after building: |
| 79 | # REASONIX_HOME=/tmp/reasonix-dev ./bin/reasonix |
| 80 | ``` |
| 81 | |
| 82 | **Desktop** |
| 83 | |
| 84 | ```bash |
| 85 | scripts/desktop-build.sh darwin/arm64 v0.0.0-dev # one platform per run |
| 86 | ``` |
| 87 | |
| 88 | On Windows, use `$env:REASONIX_HOME` in PowerShell or `set REASONIX_HOME=` in |
| 89 | Command Prompt; the binary extension is `.exe`. |
| 90 | |
| 91 | The directory is empty on first launch; the app behaves exactly like a fresh |
| 92 | install. Every subsequent write — config saves, credential storage, session |
| 93 | logs — stays under `REASONIX_HOME`. Legacy migration, OS-home convention |
| 94 | directory scanning, and all other fallback paths are skipped so no production |
| 95 | data leaks in or out. |
| 96 | |
| 97 | ### Cache-first review gate |
| 98 | |
| 99 | Reasonix treats high prompt-cache hit rate as product behavior. Changes that |
| 100 | touch provider-visible system prompt construction, memory prefix, output styles, |
| 101 | skill index behavior, default tool surfaces, tool schemas, provider request |
| 102 | serialization, compaction, or MCP/tool registration need explicit cache review. |
| 103 | |
| 104 | For these changes: |
| 105 | |
| 106 | - Keep system prompt changes low-frequency and require explicit review. |
| 107 | - Fill the PR body `Cache-impact:` line with `none`, `low`, `medium`, or `high` |
| 108 | plus the reason. |
| 109 | - Fill the PR body `Cache-guard:` line with the focused guard test/command added |
| 110 | or run, or explain why an existing guard covers the change. |
| 111 | - Fill `System-prompt-review:` when system prompt, memory prefix, output style, |
| 112 | or skill index behavior changes. |
| 113 | - Prefer focused guard tests near the changed surface; `scripts/cache-guard.sh` |
| 114 | remains the broader release-level cache-hit check. |
| 115 | |
| 116 | CI enforces this metadata for cache-sensitive paths so prompt/tool prefix churn |
| 117 | is called out before review. |
| 118 | |
| 119 | ### Running tests |
| 120 | |
| 121 | ```bash |
| 122 | go test ./... # all tests |
| 123 | go test ./internal/agent/ -v # verbose, one package |
| 124 | go test ./internal/tool/builtin/ -run TestGrep # one test |
| 125 | ``` |
| 126 | |
| 127 | Choose checks by the changed contract. Documentation-only changes need content |
| 128 | and link checks; Go changes need owning-package tests and applicable vet/lint |
| 129 | checks. Broaden to `go test ./...` for shared behavior. Desktop Go is a separate |
| 130 | module: run its affected packages or `cd desktop && go test ./...`. |
| 131 | |
| 132 | For Go changes, format the changed files and use `make lint` (golangci-lint at |
| 133 | `.golangci-version` plus repolint). `go vet` does not cover all lint rules. |
| 134 | Install the pinned linter with `make lint-install` if needed. Repeat successful |
| 135 | checks only when new changes or unresolved risks justify it. |
| 136 | |
| 137 | When adding an internal import, check the target package's test imports for a |
| 138 | reverse dependency and run the target package tests to catch setup cycles. |
| 139 | |
| 140 | Desktop transcript scroll and history changes follow the |
| 141 | [scroll and history contract](docs/TRANSCRIPT_SCROLL_CONTRACT.md), including the |
| 142 | single writer, the bounded reading window, and deterministic regression cases via |
| 143 | `pnpm test:transcript` in `desktop/frontend/`. |
| 144 | |
| 145 | Keep correctness gates deterministic. Prove concurrency and lifecycle ordering |
| 146 | with channels, injected clocks, state transitions, or emitted events instead of |
| 147 | asserting that an operation finishes within a small wall-clock interval on a |
| 148 | shared CI runner. Use a generous timeout only as a liveness watchdog. Performance |
| 149 | limits belong in an explicit benchmark that records evidence and uses the |
| 150 | benchmark's documented sampling rule; host integration probes may report an |
| 151 | advisory result when the runner cannot provide a controlled environment. |
| 152 | |
| 153 | ### Code style |
| 154 | |
| 155 | - `gofmt` is enforced by CI — format before committing |
| 156 | - Follow existing patterns: wrap errors with `fmt.Errorf("...: %w", err)` |
| 157 | - Library code never calls `os.Exit` or prints to stdout/stderr |
| 158 | - Only `cli/` and `main/` decide exit codes and user-facing messages |
| 159 | - Exported identifiers need useful doc comments; explain non-obvious constraints |
| 160 | rather than restating code. Mechanical limits live in |
| 161 | `tools/repolint/comments.go` and `tools/repolint/main.go`. |
| 162 | - `TODO(#nnn):` and `HACK(#nnn):` need an issue anchor; `FIXME` is rejected. |
| 163 | - Keep one responsibility per file. Repolint ratchets existing debt: an edit |
| 164 | cannot silently increase a file's recorded debt. Prefer removing redundancy |
| 165 | or extracting a coherent owner. |
| 166 | - A narrow baseline adjustment may carry existing debt through a rename or |
| 167 | extraction, or cover a small measured capacity increase whose design is clearer. |
| 168 | Explain the before/after values and rationale. Longer comments are justified |
| 169 | for non-obvious protocol, concurrency, compatibility, or recovery invariants. |
| 170 | Do not add broad slack, disable checks, or weaken correctness/security tests. |
| 171 | |
| 172 | ### Commit messages |
| 173 | |
| 174 | Follow [Conventional Commits](https://www.conventionalcommits.org/): |
| 175 | |
| 176 | ``` |
| 177 | feat(glob): add ** recursive pattern support |
| 178 | fix: replace silent error discards with structured logging |
| 179 | test(event): add comprehensive unit tests for event package |
| 180 | docs: add CONTRIBUTING.md |
| 181 | ci: add golangci-lint and govulncheck |
| 182 | ``` |
| 183 | |
| 184 | ### Review updates and PR metadata |
| 185 | |
| 186 | Use ordinary follow-up commits and fast-forward pushes. Amend or force-push only |
| 187 | when explicitly authorized, after verifying the remote head. Keep unrelated |
| 188 | changes out of the PR. |
| 189 | |
| 190 | Two CI guards read the PR body. The scripts are the source of truth and both |
| 191 | run locally: `scripts/check-cache-impact.sh`, `scripts/check-docs-impact.sh`. |
| 192 | Separators must be an ASCII `-` or `:` — an em dash fails the docs guard. |
| 193 | |
| 194 | Cache-sensitive diffs (`internal/tool/`, `internal/provider/`, |
| 195 | `internal/boot/`, `internal/agent/agent.go`, and the rest of the list in the |
| 196 | script) require: |
| 197 | |
| 198 | ``` |
| 199 | Cache-impact: <none|low|medium|high> - <reason> |
| 200 | Cache-guard: <focused guard test/command or existing guard rationale> |
| 201 | ``` |
| 202 | |
| 203 | `none` is a legitimate impact when the provider-visible prefix stays |
| 204 | byte-identical; only an empty value, `todo`, or `tbd` is rejected. If the diff |
| 205 | also touches `internal/config/`, `internal/memory/`, `internal/outputstyle/`, |
| 206 | `internal/skill/`, or `internal/boot/`, add `System-prompt-review: <note>` — |
| 207 | that field additionally rejects `none` and `n/a`, so it must name the explicit |
| 208 | prompt reviewer or approval. |
| 209 | |
| 210 | User-visible diffs (`cmd/reasonix/`, `desktop/`, `npm/`, and most of |
| 211 | `internal/`; tests and lockfiles are exempt) require one of these, chosen by |
| 212 | whether the same PR edited `docs/*.md`: |
| 213 | |
| 214 | ``` |
| 215 | Documentation-impact: updated - <what changed> # docs/*.md edited |
| 216 | Documentation-impact: none - <why the docs stay correct> # not edited |
| 217 | ``` |
| 218 | |
| 219 | ## Adding a new built-in tool |
| 220 | |
| 221 | 1. Create `internal/tool/builtin/mytool.go` |
| 222 | 2. Implement the `tool.Tool` interface: `Name()`, `Description()`, `Schema()`, `ReadOnly()`, `Execute()` |
| 223 | 3. Register via `func init() { tool.RegisterBuiltin(myTool{}) }` |
| 224 | 4. Add tests in `internal/tool/builtin/builtin_test.go` or a separate `mytool_test.go` |
| 225 | 5. The tool is automatically available — `main` blank-imports `builtin` |
| 226 | |
| 227 | ## Adding a new model provider |
| 228 | |
| 229 | (For MCP tool servers see `internal/plugin` instead — that's a different layer.) |
| 230 | |
| 231 | 1. Create `internal/provider/myprovider/` |
| 232 | 2. Implement `provider.Provider`: `Name()`, `Stream()` |
| 233 | 3. Register via `func init() { provider.Register("mykind", New) }` |
| 234 | 4. The provider is available from config with `kind = "mykind"` |
| 235 | |
| 236 | ## Adding i18n strings |
| 237 | |
| 238 | 1. Add the field to `internal/i18n/i18n.go` (`Messages` struct) |
| 239 | 2. Add the value in `internal/i18n/messages_en.go` and `messages_zh.go` |
| 240 | 3. The `TestCatalogsComplete` test will fail if you miss a locale |
| 241 | |
| 242 | ## Submitting changes |
| 243 | |
| 244 | 1. Fork the repository |
| 245 | 2. Create a feature branch from `main-v2` |
| 246 | 3. Make the change and add regression coverage for affected behavior |
| 247 | 4. Run the relevant checks above and satisfy required CI checks |
| 248 | 5. Ensure changed Go files are formatted |
| 249 | 6. Submit a pull request to `main-v2` |
| 250 | |
| 251 | ## Reporting issues |
| 252 | |
| 253 | Open an issue on GitHub with: |
| 254 | - Steps to reproduce |
| 255 | - Expected vs actual behavior |
| 256 | - Go version and OS |
| 257 | - Relevant logs or error messages |
| 258 | |
| 259 | ## License |
| 260 | |
| 261 | By contributing, you agree that your contributions will be licensed under the |
| 262 | same license as the project. |
| 263 |