ctx/docs/DECISIONS.md
Ricardo Carneiro 69cadb4ea6 chore: initial scaffold with plugin system and placeholders
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 13:43:10 -03:00

159 lines
5.5 KiB
Markdown

# Architecture Decision Records — ctx
Decisions are recorded here as they are made. Each entry has context (why we
needed to decide), decision (what we chose), and consequences (what it implies).
---
## 1. Language: Go (not C# or TypeScript)
**Context:** ctx is a CLI tool that runs locally on developer machines. It
needs to start fast (< 100ms cold start), produce a single distributable
binary, and work on macOS, Linux, and Windows without asking the user to
install a runtime.
**Decision:** Go.
**Consequences:**
- Single static binary, trivial cross-compilation (`GOOS=linux go build`).
- No runtime install required on target machines.
- Go's stdlib covers file I/O, JSON, HTTP, process spawning no heavy deps.
- Cannot use Roslyn directly for C# analysis (Go has no decent C# parser).
Handled by a separate helper process (see decision 4).
---
## 2. CLI framework: Cobra
**Context:** Need subcommands (`ctx git`, `ctx csharp outline`), flags, help
text, and shell completion. Rolling our own is wasteful.
**Decision:** `github.com/spf13/cobra`. Optionally `viper` for config in the
future, but not added yet.
**Consequences:**
- Cobra is the de facto standard (`kubectl`, `gh`, `hugo`, `helm` all use it).
- Well-understood by contributors. Good docs. Stable API.
- Shell completion (bash/zsh/fish/PowerShell) comes for free.
---
## 3. Plugin system: compile-time in MVP, subprocess later
**Context:** ctx needs to support multiple stacks. Plugins must be modular.
Two valid approaches: (a) compile all plugins into one binary, (b) dispatch
to external binaries (`ctx-csharp`, `ctx-react`) like kubectl.
**Decision:** MVP uses compile-time plugins registered via `init()`. The
`core.Plugin` interface is designed so subprocess plugins can implement it
later without changing callers.
**Consequences:**
- Simpler to build and ship in early stages.
- All plugins must be written in Go (or wrap external tools).
- Subprocess migration is planned but deferred until we need third-party
plugins. At that point: scan PATH for `ctx-*` binaries, wrap them with a
shim that implements `core.Plugin`.
---
## 4. C# analysis: separate Roslyn helper process
**Context:** Semantic C# analysis (types, method signatures, usages, errors)
requires a compiler-level parser. Roslyn is the only production-quality option.
Go has no usable C# parser.
**Decision:** A separate C# program (`tools/roslyn-helper/`) that loads
projects with Roslyn and answers queries sent as JSON-RPC messages over
stdin/stdout. ctx spawns it as a subprocess and communicates via pipes.
**Consequences:**
- Requires .NET SDK on the machine for C# commands (acceptable users of
`ctx csharp` are already .NET developers).
- Clean separation: Go handles CLI, orchestration, output formatting; C#
handles semantic analysis.
- JSON-RPC over stdin/stdout is trivial to test in isolation.
- Performance: subprocess is spawned once per ctx invocation, amortized if we
batch queries (future optimization).
---
## 5. Output format: UTF-8 markdown without BOM on stdout
**Context:** ctx output is consumed by Claude Code (via pipe or CLAUDE.md
instructions). Claude understands markdown well. BOM causes issues in some
contexts.
**Decision:** All plugin output is plain UTF-8 markdown, no BOM, on stdout.
Errors go to stderr. No ANSI colors (markdown already has structure).
**Consequences:**
- Output is directly pasteable into Claude conversations.
- Easy to redirect to file: `ctx csharp project > context.md`.
- Plugins must use `ctx.Stdout` / `ctx.Stderr`, never `fmt.Println` directly,
so output destination can be overridden in tests.
---
## 6. Subcommand structure: ctx <stack> <command>
**Context:** Multiple stacks, each with multiple commands. Need a consistent,
scalable interface.
**Decision:** `ctx <stack> <command> [args] [flags]`. Examples:
- `ctx csharp project`
- `ctx csharp outline src/Foo.cs`
- `ctx git`
- `ctx auto project`
**Consequences:**
- Adding a new stack = adding a new top-level subcommand.
- Commands within a stack are subcommands of the stack command.
- Consistent with kubectl, gh, and other modern CLIs.
---
## 7. Module path: github.com/ricarneiro/ctx
**Context:** Source is hosted on a private Gitea instance
(`git.carneiro.ddnsfree.com`). Go module paths do not need to match the actual
git host. If we want `go install` from the public GitHub mirror later, the
module path must match.
**Decision:** `github.com/ricarneiro/ctx` from day one.
**Consequences:**
- Developers cloning from Gitea still use this module path no friction.
- `go install github.com/ricarneiro/ctx/cmd/ctx@latest` works once we push
to GitHub. No rename needed.
- If we never publish to GitHub, the module path is just an identifier and
causes no problems.
---
## 8. License: MIT
**Context:** Want maximum adoption. No patent clause complexity.
**Decision:** MIT, copyright Ricardo Carneiro.
**Consequences:**
- Anyone can use, fork, embed, sell without restrictions.
- No copyleft. If this matters later, we can dual-license, but that's a
future problem.
---
## 9. Commit convention: Conventional Commits
**Context:** Want automated CHANGELOG generation in the future. Need a
consistent commit format from day one.
**Decision:** Conventional Commits (`feat:`, `fix:`, `chore:`, `docs:`,
`refactor:`, `test:`). Enforced by convention, not by hook (yet).
**Consequences:**
- `git log --oneline` is readable.
- Can run `git-cliff` or `conventional-changelog` later to generate CHANGELOG.
- PRs should squash to a single conventional commit on merge.