diff --git a/.github/workflows/nix-desktop.yml b/.github/workflows/nix-desktop.yml index 54ce55be069..b7919c062cc 100644 --- a/.github/workflows/nix-desktop.yml +++ b/.github/workflows/nix-desktop.yml @@ -9,6 +9,13 @@ on: - "nix/**" - "packages/app/**" - "packages/desktop/**" + pull_request: + paths: + - "flake.nix" + - "flake.lock" + - "nix/**" + - "packages/app/**" + - "packages/desktop/**" workflow_dispatch: jobs: diff --git a/.opencode/command/openspec-apply.md b/.opencode/command/openspec-apply.md new file mode 100644 index 00000000000..2686863870d --- /dev/null +++ b/.opencode/command/openspec-apply.md @@ -0,0 +1,26 @@ +--- +agent: build +description: Implement an approved OpenSpec change and keep tasks in sync. +--- + + + +**Guardrails** + +- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required. +- Keep changes tightly scoped to the requested outcome. +- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications. + +**Steps** +Track these steps as TODOs and complete them one by one. + +1. Read `changes//proposal.md`, `design.md` (if present), and `tasks.md` to confirm scope and acceptance criteria. +2. Work through tasks sequentially, keeping edits minimal and focused on the requested change. +3. Confirm completion before updating statuses—make sure every item in `tasks.md` is finished. +4. Update the checklist after all work is done so each task is marked `- [x]` and reflects reality. +5. Reference `openspec list` or `openspec show ` when additional context is required. + +**Reference** + +- Use `openspec show --json --deltas-only` if you need additional context from the proposal while implementing. + diff --git a/.opencode/command/openspec-archive.md b/.opencode/command/openspec-archive.md new file mode 100644 index 00000000000..7273292db4a --- /dev/null +++ b/.opencode/command/openspec-archive.md @@ -0,0 +1,31 @@ +--- +agent: build +description: Archive a deployed OpenSpec change and update specs. +--- + + + $ARGUMENTS + + +**Guardrails** +- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required. +- Keep changes tightly scoped to the requested outcome. +- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications. + +**Steps** + +1. Determine the change ID to archive: + - If this prompt already includes a specific change ID (for example inside a `` block populated by slash-command arguments), use that value after trimming whitespace. + - If the conversation references a change loosely (for example by title or summary), run `openspec list` to surface likely IDs, share the relevant candidates, and confirm which one the user intends. + - Otherwise, review the conversation, run `openspec list`, and ask the user which change to archive; wait for a confirmed change ID before proceeding. + - If you still cannot identify a single change ID, stop and tell the user you cannot archive anything yet. +2. Validate the change ID by running `openspec list` (or `openspec show `) and stop if the change is missing, already archived, or otherwise not ready to archive. +3. Run `openspec archive --yes` so the CLI moves the change and applies spec updates without prompts (use `--skip-specs` only for tooling-only work). +4. Review the command output to confirm the target specs were updated and the change landed in `changes/archive/`. +5. Validate with `openspec validate --strict` and inspect with `openspec show ` if anything looks off. + +**Reference** + +- Use `openspec list` to confirm change IDs before archiving. +- Inspect refreshed specs with `openspec list --specs` and address any validation issues before handing off. + diff --git a/.opencode/command/openspec-proposal.md b/.opencode/command/openspec-proposal.md new file mode 100644 index 00000000000..9282a4fd20e --- /dev/null +++ b/.opencode/command/openspec-proposal.md @@ -0,0 +1,36 @@ +--- +agent: build +description: Scaffold a new OpenSpec change and validate strictly. +--- + +The user has requested the following change proposal. Use the openspec instructions to create their change proposal. + +$ARGUMENTS + + + + +**Guardrails** + +- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required. +- Keep changes tightly scoped to the requested outcome. +- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications. +- Identify any vague or ambiguous details and ask the necessary follow-up questions before editing files. +- Do not write any code during the proposal stage. Only create design documents (proposal.md, tasks.md, design.md, and spec deltas). Implementation happens in the apply stage after approval. + +**Steps** + +1. Review `openspec/project.md`, run `openspec list` and `openspec list --specs`, and inspect related code or docs (e.g., via `rg`/`ls`) to ground the proposal in current behaviour; note any gaps that require clarification. +2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, and `design.md` (when needed) under `openspec/changes//`. +3. Map the change into concrete capabilities or requirements, breaking multi-scope efforts into distinct spec deltas with clear relationships and sequencing. +4. Capture architectural reasoning in `design.md` when the solution spans multiple systems, introduces new patterns, or demands trade-off discussion before committing to specs. +5. Draft spec deltas in `changes//specs//spec.md` (one folder per capability) using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement and cross-reference related capabilities when relevant. +6. Draft `tasks.md` as an ordered list of small, verifiable work items that deliver user-visible progress, include validation (tests, tooling), and highlight dependencies or parallelizable work. +7. Validate with `openspec validate --strict` and resolve every issue before sharing the proposal. + +**Reference** + +- Use `openspec show --json --deltas-only` or `openspec show --type spec` to inspect details when validation fails. +- Search existing requirements with `rg -n "Requirement:|Scenario:" openspec/specs` before writing new ones. +- Explore the codebase with `rg `, `ls`, or direct file reads so proposals align with current implementation realities. + diff --git a/openspec/AGENTS.md b/openspec/AGENTS.md new file mode 100644 index 00000000000..45ef8a6b43d --- /dev/null +++ b/openspec/AGENTS.md @@ -0,0 +1,522 @@ +# OpenSpec Instructions + +Instructions for AI coding assistants using OpenSpec for spec-driven development. + +## TL;DR Quick Checklist + +- Search existing work: `openspec spec list --long`, `openspec list` (use `rg` only for full-text search) +- Decide scope: new capability vs modify existing capability +- Pick a unique `change-id`: kebab-case, verb-led (`add-`, `update-`, `remove-`, `refactor-`) +- Scaffold: `proposal.md`, `tasks.md`, `design.md` (only if needed), and delta specs per affected capability +- Write deltas: use `## ADDED|MODIFIED|REMOVED|RENAMED Requirements`; include at least one `#### Scenario:` per requirement +- Validate: `openspec validate [change-id] --strict` and fix issues +- Request approval: Do not start implementation until proposal is approved + +## Three-Stage Workflow + +### Stage 1: Creating Changes + +Create proposal when you need to: + +- Add features or functionality +- Make breaking changes (API, schema) +- Change architecture or patterns +- Optimize performance (changes behavior) +- Update security patterns + +Triggers (examples): + +- "Help me create a change proposal" +- "Help me plan a change" +- "Help me create a proposal" +- "I want to create a spec proposal" +- "I want to create a spec" + +Loose matching guidance: + +- Contains one of: `proposal`, `change`, `spec` +- With one of: `create`, `plan`, `make`, `start`, `help` + +Skip proposal for: + +- Bug fixes (restore intended behavior) +- Typos, formatting, comments +- Dependency updates (non-breaking) +- Configuration changes +- Tests for existing behavior + +**Workflow** + +1. Review `openspec/project.md`, `openspec list`, and `openspec list --specs` to understand current context. +2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, optional `design.md`, and spec deltas under `openspec/changes//`. +3. Draft spec deltas using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement. +4. Run `openspec validate --strict` and resolve any issues before sharing the proposal. + +### Stage 2: Implementing Changes + +Track these steps as TODOs and complete them one by one. + +1. **Read proposal.md** - Understand what's being built +2. **Read design.md** (if exists) - Review technical decisions +3. **Read tasks.md** - Get implementation checklist +4. **Implement tasks sequentially** - Complete in order +5. **Confirm completion** - Ensure every item in `tasks.md` is finished before updating statuses +6. **Update checklist** - After all work is done, set every task to `- [x]` so the list reflects reality +7. **Approval gate** - Do not start implementation until the proposal is reviewed and approved + +### Stage 3: Archiving Changes + +After deployment, create separate PR to: + +- Move `changes/[name]/` → `changes/archive/YYYY-MM-DD-[name]/` +- Update `specs/` if capabilities changed +- Use `openspec archive --skip-specs --yes` for tooling-only changes (always pass the change ID explicitly) +- Run `openspec validate --strict` to confirm the archived change passes checks + +## Before Any Task + +**Context Checklist:** + +- [ ] Read relevant specs in `specs/[capability]/spec.md` +- [ ] Check pending changes in `changes/` for conflicts +- [ ] Read `openspec/project.md` for conventions +- [ ] Run `openspec list` to see active changes +- [ ] Run `openspec list --specs` to see existing capabilities + +**Before Creating Specs:** + +- Always check if capability already exists +- Prefer modifying existing specs over creating duplicates +- Use `openspec show [spec]` to review current state +- If request is ambiguous, ask 1–2 clarifying questions before scaffolding + +### Search Guidance + +- Enumerate specs: `openspec spec list --long` (or `--json` for scripts) +- Enumerate changes: `openspec list` (or `openspec change list --json` - deprecated but available) +- Show details: + - Spec: `openspec show --type spec` (use `--json` for filters) + - Change: `openspec show --json --deltas-only` +- Full-text search (use ripgrep): `rg -n "Requirement:|Scenario:" openspec/specs` + +## Quick Start + +### CLI Commands + +```bash +# Essential commands +openspec list # List active changes +openspec list --specs # List specifications +openspec show [item] # Display change or spec +openspec validate [item] # Validate changes or specs +openspec archive [--yes|-y] # Archive after deployment (add --yes for non-interactive runs) + +# Project management +openspec init [path] # Initialize OpenSpec +openspec update [path] # Update instruction files + +# Interactive mode +openspec show # Prompts for selection +openspec validate # Bulk validation mode + +# Debugging +openspec show [change] --json --deltas-only +openspec validate [change] --strict +``` + +### Command Flags + +- `--json` - Machine-readable output +- `--type change|spec` - Disambiguate items +- `--strict` - Comprehensive validation +- `--no-interactive` - Disable prompts +- `--skip-specs` - Archive without spec updates +- `--yes`/`-y` - Skip confirmation prompts (non-interactive archive) + +## Directory Structure + +``` +openspec/ +├── project.md # Project conventions +├── specs/ # Current truth - what IS built +│ └── [capability]/ # Single focused capability +│ ├── spec.md # Requirements and scenarios +│ └── design.md # Technical patterns +├── changes/ # Proposals - what SHOULD change +│ ├── [change-name]/ +│ │ ├── proposal.md # Why, what, impact +│ │ ├── tasks.md # Implementation checklist +│ │ ├── design.md # Technical decisions (optional; see criteria) +│ │ └── specs/ # Delta changes +│ │ └── [capability]/ +│ │ └── spec.md # ADDED/MODIFIED/REMOVED +│ └── archive/ # Completed changes +``` + +## Creating Change Proposals + +### Decision Tree + +``` +New request? +├─ Bug fix restoring spec behavior? → Fix directly +├─ Typo/format/comment? → Fix directly +├─ New feature/capability? → Create proposal +├─ Breaking change? → Create proposal +├─ Architecture change? → Create proposal +└─ Unclear? → Create proposal (safer) +``` + +### Proposal Structure + +1. **Create directory:** `changes/[change-id]/` (kebab-case, verb-led, unique) + +2. **Write proposal.md:** + +```markdown +# Change: [Brief description of change] + +## Why + +[1-2 sentences on problem/opportunity] + +## What Changes + +- [Bullet list of changes] +- [Mark breaking changes with **BREAKING**] + +## Impact + +- Affected specs: [list capabilities] +- Affected code: [key files/systems] +``` + +3. **Create spec deltas:** `specs/[capability]/spec.md` + +```markdown +## ADDED Requirements + +### Requirement: New Feature + +The system SHALL provide... + +#### Scenario: Success case + +- **WHEN** user performs action +- **THEN** expected result + +## MODIFIED Requirements + +### Requirement: Existing Feature + +[Complete modified requirement] + +## REMOVED Requirements + +### Requirement: Old Feature + +**Reason**: [Why removing] +**Migration**: [How to handle] +``` + +If multiple capabilities are affected, create multiple delta files under `changes/[change-id]/specs//spec.md`—one per capability. + +4. **Create tasks.md:** + +```markdown +## 1. Implementation + +- [ ] 1.1 Create database schema +- [ ] 1.2 Implement API endpoint +- [ ] 1.3 Add frontend component +- [ ] 1.4 Write tests +``` + +5. **Create design.md when needed:** + Create `design.md` if any of the following apply; otherwise omit it: + +- Cross-cutting change (multiple services/modules) or a new architectural pattern +- New external dependency or significant data model changes +- Security, performance, or migration complexity +- Ambiguity that benefits from technical decisions before coding + +Minimal `design.md` skeleton: + +```markdown +## Context + +[Background, constraints, stakeholders] + +## Goals / Non-Goals + +- Goals: [...] +- Non-Goals: [...] + +## Decisions + +- Decision: [What and why] +- Alternatives considered: [Options + rationale] + +## Risks / Trade-offs + +- [Risk] → Mitigation + +## Migration Plan + +[Steps, rollback] + +## Open Questions + +- [...] +``` + +## Spec File Format + +### Critical: Scenario Formatting + +**CORRECT** (use #### headers): + +```markdown +#### Scenario: User login success + +- **WHEN** valid credentials provided +- **THEN** return JWT token +``` + +**WRONG** (don't use bullets or bold): + +```markdown +- **Scenario: User login** ❌ + **Scenario**: User login ❌ + +### Scenario: User login ❌ +``` + +Every requirement MUST have at least one scenario. + +### Requirement Wording + +- Use SHALL/MUST for normative requirements (avoid should/may unless intentionally non-normative) + +### Delta Operations + +- `## ADDED Requirements` - New capabilities +- `## MODIFIED Requirements` - Changed behavior +- `## REMOVED Requirements` - Deprecated features +- `## RENAMED Requirements` - Name changes + +Headers matched with `trim(header)` - whitespace ignored. + +#### When to use ADDED vs MODIFIED + +- ADDED: Introduces a new capability or sub-capability that can stand alone as a requirement. Prefer ADDED when the change is orthogonal (e.g., adding "Slash Command Configuration") rather than altering the semantics of an existing requirement. +- MODIFIED: Changes the behavior, scope, or acceptance criteria of an existing requirement. Always paste the full, updated requirement content (header + all scenarios). The archiver will replace the entire requirement with what you provide here; partial deltas will drop previous details. +- RENAMED: Use when only the name changes. If you also change behavior, use RENAMED (name) plus MODIFIED (content) referencing the new name. + +Common pitfall: Using MODIFIED to add a new concern without including the previous text. This causes loss of detail at archive time. If you aren’t explicitly changing the existing requirement, add a new requirement under ADDED instead. + +Authoring a MODIFIED requirement correctly: + +1. Locate the existing requirement in `openspec/specs//spec.md`. +2. Copy the entire requirement block (from `### Requirement: ...` through its scenarios). +3. Paste it under `## MODIFIED Requirements` and edit to reflect the new behavior. +4. Ensure the header text matches exactly (whitespace-insensitive) and keep at least one `#### Scenario:`. + +Example for RENAMED: + +```markdown +## RENAMED Requirements + +- FROM: `### Requirement: Login` +- TO: `### Requirement: User Authentication` +``` + +## Troubleshooting + +### Common Errors + +**"Change must have at least one delta"** + +- Check `changes/[name]/specs/` exists with .md files +- Verify files have operation prefixes (## ADDED Requirements) + +**"Requirement must have at least one scenario"** + +- Check scenarios use `#### Scenario:` format (4 hashtags) +- Don't use bullet points or bold for scenario headers + +**Silent scenario parsing failures** + +- Exact format required: `#### Scenario: Name` +- Debug with: `openspec show [change] --json --deltas-only` + +### Validation Tips + +```bash +# Always use strict mode for comprehensive checks +openspec validate [change] --strict + +# Debug delta parsing +openspec show [change] --json | jq '.deltas' + +# Check specific requirement +openspec show [spec] --json -r 1 +``` + +## Happy Path Script + +```bash +# 1) Explore current state +openspec spec list --long +openspec list +# Optional full-text search: +# rg -n "Requirement:|Scenario:" openspec/specs +# rg -n "^#|Requirement:" openspec/changes + +# 2) Choose change id and scaffold +CHANGE=add-two-factor-auth +mkdir -p openspec/changes/$CHANGE/{specs/auth} +printf "## Why\n...\n\n## What Changes\n- ...\n\n## Impact\n- ...\n" > openspec/changes/$CHANGE/proposal.md +printf "## 1. Implementation\n- [ ] 1.1 ...\n" > openspec/changes/$CHANGE/tasks.md + +# 3) Add deltas (example) +cat > openspec/changes/$CHANGE/specs/auth/spec.md << 'EOF' +## ADDED Requirements +### Requirement: Two-Factor Authentication +Users MUST provide a second factor during login. + +#### Scenario: OTP required +- **WHEN** valid credentials are provided +- **THEN** an OTP challenge is required +EOF + +# 4) Validate +openspec validate $CHANGE --strict +``` + +## Multi-Capability Example + +``` +openspec/changes/add-2fa-notify/ +├── proposal.md +├── tasks.md +└── specs/ + ├── auth/ + │ └── spec.md # ADDED: Two-Factor Authentication + └── notifications/ + └── spec.md # ADDED: OTP email notification +``` + +auth/spec.md + +```markdown +## ADDED Requirements + +### Requirement: Two-Factor Authentication + +... +``` + +notifications/spec.md + +```markdown +## ADDED Requirements + +### Requirement: OTP Email Notification + +... +``` + +## Best Practices + +### Simplicity First + +- Default to <100 lines of new code +- Single-file implementations until proven insufficient +- Avoid frameworks without clear justification +- Choose boring, proven patterns + +### Complexity Triggers + +Only add complexity with: + +- Performance data showing current solution too slow +- Concrete scale requirements (>1000 users, >100MB data) +- Multiple proven use cases requiring abstraction + +### Clear References + +- Use `file.ts:42` format for code locations +- Reference specs as `specs/auth/spec.md` +- Link related changes and PRs + +### Capability Naming + +- Use verb-noun: `user-auth`, `payment-capture` +- Single purpose per capability +- 10-minute understandability rule +- Split if description needs "AND" + +### Change ID Naming + +- Use kebab-case, short and descriptive: `add-two-factor-auth` +- Prefer verb-led prefixes: `add-`, `update-`, `remove-`, `refactor-` +- Ensure uniqueness; if taken, append `-2`, `-3`, etc. + +## Tool Selection Guide + +| Task | Tool | Why | +| --------------------- | ---- | ------------------------ | +| Find files by pattern | Glob | Fast pattern matching | +| Search code content | Grep | Optimized regex search | +| Read specific files | Read | Direct file access | +| Explore unknown scope | Task | Multi-step investigation | + +## Error Recovery + +### Change Conflicts + +1. Run `openspec list` to see active changes +2. Check for overlapping specs +3. Coordinate with change owners +4. Consider combining proposals + +### Validation Failures + +1. Run with `--strict` flag +2. Check JSON output for details +3. Verify spec file format +4. Ensure scenarios properly formatted + +### Missing Context + +1. Read project.md first +2. Check related specs +3. Review recent archives +4. Ask for clarification + +## Quick Reference + +### Stage Indicators + +- `changes/` - Proposed, not yet built +- `specs/` - Built and deployed +- `archive/` - Completed changes + +### File Purposes + +- `proposal.md` - Why and what +- `tasks.md` - Implementation steps +- `design.md` - Technical decisions +- `spec.md` - Requirements and behavior + +### CLI Essentials + +```bash +openspec list # What's in progress? +openspec show [item] # View details +openspec validate --strict # Is it correct? +openspec archive [--yes|-y] # Mark complete (add --yes for automation) +``` + +Remember: Specs are truth. Changes are proposals. Keep them in sync. diff --git a/openspec/project.md b/openspec/project.md new file mode 100644 index 00000000000..4e02f5e8cdc --- /dev/null +++ b/openspec/project.md @@ -0,0 +1,348 @@ +# Project Context + +## Purpose + +Opencode is an open-source, spec-driven, agentic coding platform enabling AI assistants to reliably modify, extend, and manage multi-language monorepos. It provides a unified workflow (OpenSpec) for proposing, validating, implementing, and archiving changes across CLI tooling, UI surfaces, SDKs, integrations, and automation scripts while enforcing simplicity-first engineering and predictable behavior. + +## Vision + +The system SHALL enable repeatable, auditable code evolution where specs are authoritative truth, changes are explicit proposals, and assistants reduce ambiguity through tightly-scoped operations. Multi-surface distribution (CLI, TUI, VSCode, Web, Slack) SHALL remain unified by the same spec corpus. + +## Core Principles + +- Simplicity first (prefer <100 LOC initial implementations) +- Spec before code divergence +- Provider agnostic (LLM + infrastructure) +- Deterministic tooling (avoid magic/global state) +- Composition over abstraction layering +- Small capability boundaries (10-minute understandability rule) + +--- + +## Tech Stack + +### Runtimes / Languages + +- Bun (primary JS/TS runtime) — MUST be used for new JS/TS code unless strong justification +- Go (TUI and SDK components) +- TypeScript (dominant logic + tooling) +- JavaScript (lightweight scripts where types not required) +- Markdown (specs, proposals, docs) +- Astro (marketing/docs site) +- SolidJS + React (frontend surfaces) +- Shell (install / bootstrap scripts) + +### Frameworks / Tooling + +- SST (infrastructure orchestration on AWS + Cloudflare home) +- Turbo (monorepo task pipelining) +- Vite (frontend build) +- Astro (static site) +- OpenSpec (change/spec lifecycle) +- GitHub Actions (CI/CD) +- Husky (git hooks) +- Goreleaser (Go binary distribution) +- Stainless (SDK generation) +- Tailwind (styling) +- Zod for validation, Hono for lightweight APIs +- Stripe & PlanetScale providers via SST config + +### Surfaces + +| Surface | Path | Stack | Purpose | +| ---------------- | --------------------------- | ----------------- | ------------------------------------------ | +| CLI | `packages/opencode` | Bun + TS | Core agent execution / orchestration | +| TUI | `packages/tui` | Go | Terminal UI (migration planned to opentui) | +| Web Console | `packages/console` | SolidJS + Vite | Admin / interactive interface | +| Public Site | `web/` | Astro | Landing + docs | +| Desktop | `packages/desktop` | Web tech packaged | Local desktop experience | +| VSCode Extension | `sdks/vscode` | TS | Editor integration | +| Slack App | `packages/slack` | TS | Chat/task integration | +| SDKs | `packages/sdk/js`, `sdk/go` | TS / Go | Programmatic access | + +### External Integrations + +- AWS (via SST abstractions) +- Cloudflare (home target) +- Stripe (payments / billing) +- PlanetScale (database provider) +- Slack API +- VSCode Extension API +- Stainless (codegen) +- Email provider (TBD in `packages/mail`) +- LLM Providers (Anthropic recommended; OpenAI, Google, local — MUST remain pluggable) + +--- + +## Architecture Patterns + +- Monorepo organized by capability-focused packages (`packages/`, `sdk/`, `sdks/`, `tui/`, `web/`) +- Specification-driven flow: `specs/` (truth) ← `changes/` (proposals) → archive +- Capability naming: verb-noun or concise domain noun — MUST avoid “AND” in description +- Cross-cutting concerns (auth, logging, telemetry) added ONLY after ≥2 concrete use cases +- Pure functions favored; side effects isolated at IO boundaries +- No hidden globals; configuration MUST be injected or read explicitly +- Go code SHALL use small interfaces; prefer composition over inheritance + +--- + +## Directory Reference + +``` +openspec/ + project.md # Conventions (this file) + specs/ # Live capabilities (deployed behavior) + changes/ # Proposed modifications + [change-id]/ # Active proposal + archive/ # Completed changes +packages/ # JS/TS packages (console, plugin, mail, etc.) +sdk/ # Multi-language SDK tooling (Go/JS) +sdks/ # VSCode and other surface-specific distributions +web/ # Public site +tui/ # Terminal UI (Go) +infra/ # Infra definition modules loaded by SST +script/ # Automation scripts +``` + +--- + +## Project Conventions + +### Code Style + +- Prefer one focused function/file for initial implementations (<100 new lines) +- Use `const`; avoid `let` unless mutation unavoidable +- Avoid `any`; choose precise types or `unknown` +- Early returns — minimize `else` +- Avoid unnecessary destructuring +- Keep variable names single-word descriptive +- Use Bun APIs (`Bun.file()`, `fetch`, native `serve`) +- MINIMIZE `try/catch`; prefer promise chains or localized error handling +- Inline comments sparingly — specs + naming SHOULD convey intent +- Go: small exported surface; limit package-level variables + +### Error Handling + +- Boundary-only: file IO, network, external service calls +- Wrap external failures with contextual messages (no raw exception leakage) +- No silent catches — MUST surface actionable messages to caller or log system + +### Logging / Telemetry (Future Placeholder) + +- SHALL centralize structured logs once ≥2 surfaces require cross-correlation +- Metrics/trace integration added ONLY after performance or observability trigger + +### Data & Persistence + +- PlanetScale (MySQL) via SST provider — schema evolution MUST be gated by spec changes +- Ephemeral caches allowed (in-memory) if rebuild safe and <10ms target read path +- No implicit persistence outside defined infra modules + +### Security + +- Secrets MUST NOT be committed (enforced by tooling + review) +- Stripe key via `process.env.STRIPE_SECRET_KEY` +- Least privilege AWS/IAM via SST constructs +- Authentication capabilities MUST specify scenarios before implementation +- Security-affecting changes REQUIRE proposals (even if code small) + +### Performance + +- Optimize ONLY after measurement (profiling or metrics) +- Add complexity triggers: >1000 concurrent sessions, >100MB dataset, >p95 >500ms +- Snapshot / golden output tests allowed for stable textual outputs (help screens, spec listings) + +--- + +## Testing Strategy + +- Behavior-first: tests reflect spec scenarios +- Unit tests colocated with logic (same directory) +- Bun test runner for TS; Go standard testing for Go code +- SHALL mock external network dependencies except explicit integration tests +- Add/modify tests when implementing ADDED or MODIFIED requirements +- REMOVED requirements generally do NOT require tests unless migration complexity +- `openspec validate --strict` MUST accompany test execution for requirement coverage +- Fast deterministic tests favored; no flaky timing dependence + +### Test Layers + +| Layer | Purpose | Example | +| ----------- | --------------------- | ---------------------------- | +| Unit | Pure function logic | parsing, formatting | +| Integration | Cross-module behavior | CLI command uses spec loader | +| Contract | Spec alignment | scenario enforcement tests | +| Golden | Stable render output | CLI help banner snapshot | + +--- + +## Git Workflow + +- Branch per approved proposal (`change-id` → branch name) +- Commits: small, focused, verb-led explaining WHY +- No implementation BEFORE proposal approval +- Archive after deployment using OpenSpec tool then merge archival PR +- Pre-push hooks (format + typecheck) MUST pass +- Commit verbs: `add`, `update`, `remove`, `refactor`, `fix`, `docs` + +### Commit Message Examples + +- `add 2fa challenge flow` +- `refactor spec parsing for speed` +- `fix provider selection race` + +--- + +## Change Management (OpenSpec) + +- New capability / breaking / architecture / performance / security => proposal REQUIRED +- Bug fixes / typo / formatting / existing behavior tests => direct change allowed +- Each requirement MUST include ≥1 `#### Scenario:` header +- MODIFIED MUST copy full prior requirement block before editing +- RENAMED used ONLY for name changes (behavior changes must also MODIFIED) + +### Scenario Formatting (Critical) + +Correct: + +```markdown +#### Scenario: User login success + +- **WHEN** valid credentials provided +- **THEN** return JWT token +``` + +Wrong: + +``` +- **Scenario: Login** ✗ +**Scenario**: Login ✗ +### Scenario: Login ✗ +``` + +--- + +## Capability Naming + +- Kebab-case +- Verb-led or domain noun (`user-auth`, `project-stats`) +- If description requires “AND” → split capability +- SHALL remain discoverable via `openspec list --specs` within minutes + +--- + +## Contribution Guardrails + +- Net-new UI / product feature MUST undergo design conversation first +- Acceptable PR types: bug fixes, LSP additions, provider support, missing standard behaviors, doc improvements +- Large multi-surface changes REQUIRE proposal +- Regenerate Stainless SDK after touching `packages/opencode/src/server/server.ts` BEFORE merging client updates + +--- + +## Release & Distribution + +- CLI install script supports prioritized env vars (`OPENCODE_INSTALL_DIR`, `XDG_BIN_DIR`) +- Multi-channel distribution: Homebrew, npm, chocolatey, scoop, Arch +- Go binaries via Goreleaser (tag-driven) +- Patched dependencies tracked under `patchedDependencies` in root `package.json` +- VSCode extension packaged from `sdks/vscode` + +--- + +## Tasks & Automation + +- Turborepo orchestrates shared tasks (`build`, `typecheck`, `opencode#test`) +- CI via GitHub Actions: publish, test, typecheck, formatting, deploy +- Husky hooks enforce local hygiene pre-push + +--- + +## Runtime Policy + +- Bun MUST be default for JS/TS execution +- Node-specific APIs added only with explicit justification (compatibility or ecosystem library) +- Go remains for TUI + performance-critical areas (subject to future migration) +- Desktop packaging SHALL reuse existing web build artifacts where possible + +--- + +## LLM & Provider Agnosticism + +- Anthropic recommended baseline; MUST not hard-code provider assumptions +- Pluggable model selection strategy +- Behavior surfaces rely on spec parsing to reduce hallucination risk + +--- + +## External Dependencies (Detail) + +| Dependency | Purpose | Notes | +| ----------- | ------------------------- | ----------------------------- | +| Bun | Runtime + package manager | Primary execution environment | +| SST | Infra provisioning | AWS + Cloudflare integration | +| Stripe | Billing | API key via env | +| PlanetScale | DB | Managed MySQL | +| Slack API | Chat integration | Bot actions | +| VSCode API | Editor integration | Extension surface | +| Stainless | SDK gen | Sync after server changes | +| Turbo | Task pipeline | Build/test graph | +| Husky | Git hooks | Pre-push enforcement | +| Goreleaser | Go release | Binary distribution | +| Tailwind | Styling | Used in frontends | +| Zod | Validation | Runtime schema checks | +| Hono | Lightweight HTTP | API layer | + +--- + +## Implementation Constraints + +- New features initial pass <100 added LOC +- Avoid premature abstraction (extract ONLY after repetition) +- External dependency additions MUST justify performance / integration need +- All normative behavior SHALL be documented before divergence +- Performance tuning AFTER measurement (avoid speculative complexity) + +--- + +## Validation & Tooling + +- `openspec validate --strict` MUST pass before sharing proposal +- Spec parsing reliability is critical — scenario headers MUST be exact +- Use ripgrep for spec search: `rg -n "Requirement:|Scenario:" openspec/specs` +- Provide file references using `path/to/file.ts:42` format in discussions + +--- + +## Open Questions (Placeholders) + +- Telemetry collection approach (metrics + tracing) — TBD after first cross-surface need +- Secret management standardization (SSM vs custom vault) — pending scale trigger +- Unified plugin architecture across CLI + VSCode — exploration ongoing +- 2FA rollout sequence (auth capability expansion) — future proposal + +--- + +## Quick Reference + +| Artifact | Purpose | +| ------------- | --------------------------------- | +| `specs/` | Current truth | +| `changes/` | Proposed modifications | +| `archive/` | Completed changes | +| `proposal.md` | Why + What | +| `tasks.md` | Implementation checklist | +| `design.md` | Technical decisions (conditional) | +| `spec.md` | Requirements + scenarios | + +### Essential CLI + +```bash +openspec list # Active changes +openspec list --specs # Capabilities +openspec show [item] # Display spec/change +openspec validate --strict # Validate formatting + scenarios +openspec archive --yes # Archive post-deploy +``` + +Remember: Specs are truth. Changes are proposals. Keep them in sync. diff --git a/packages/desktop/src-tauri/src/cli.rs b/packages/desktop/src-tauri/src/cli.rs index 6b86cbcd2c3..8b76d1a7f8a 100644 --- a/packages/desktop/src-tauri/src/cli.rs +++ b/packages/desktop/src-tauri/src/cli.rs @@ -1,3 +1,5 @@ +use tauri::Manager; + const CLI_INSTALL_DIR: &str = ".opencode/bin"; const CLI_BINARY_NAME: &str = "opencode"; @@ -9,9 +11,10 @@ fn get_cli_install_path() -> Option { }) } -pub fn get_sidecar_path() -> std::path::PathBuf { - tauri::utils::platform::current_exe() - .expect("Failed to get current exe") +pub fn get_sidecar_path(app: &tauri::AppHandle) -> std::path::PathBuf { + // Get binary with symlinks support + tauri::process::current_binary(&app.env()) + .expect("Failed to get current binary") .parent() .expect("Failed to get parent dir") .join("opencode-cli") @@ -26,12 +29,12 @@ fn is_cli_installed() -> bool { const INSTALL_SCRIPT: &str = include_str!("../../../../install"); #[tauri::command] -pub fn install_cli() -> Result { +pub fn install_cli(app: tauri::AppHandle) -> Result { if cfg!(not(unix)) { return Err("CLI installation is only supported on macOS & Linux".to_string()); } - let sidecar = get_sidecar_path(); + let sidecar = get_sidecar_path(&app); if !sidecar.exists() { return Err("Sidecar binary not found".to_string()); } @@ -108,7 +111,7 @@ pub fn sync_cli(app: tauri::AppHandle) -> Result<(), String> { cli_version, app_version ); - install_cli()?; + install_cli(app)?; println!("Synced installed CLI"); diff --git a/packages/desktop/src-tauri/src/lib.rs b/packages/desktop/src-tauri/src/lib.rs index 5d1610fa3e6..612797fd099 100644 --- a/packages/desktop/src-tauri/src/lib.rs +++ b/packages/desktop/src-tauri/src/lib.rs @@ -120,7 +120,7 @@ fn spawn_sidecar(app: &AppHandle, port: u32) -> CommandChild { #[cfg(not(target_os = "windows"))] let (mut rx, child) = { - let sidecar = get_sidecar_path(); + let sidecar = get_sidecar_path(app); let shell = get_user_shell(); app.shell() .command(&shell)