Files
Empryo/CONTRIBUTING.md
PouiiroandSoulForge 350670c10b chore: governance, build hardening, mintlify sweep
- governance: CODEOWNERS, GOVERNANCE.md, PRIVACY.md, FUNDING.yml, PR template
- CONTRIBUTING.md: in-scope/out-of-scope contribution policy
- SECURITY.md: drop stale supported-versions table
- scripts/build.ts: minify + sourcemap:none + abs-path scrub + sentinel guard under NODE_ENV=production
- mintlify: rename-refactor sample, broken paths, theme list (36), casing, license name

Co-Authored-By: SoulForge <[email protected]>
2026-05-10 23:50:11 +02:00

17 KiB

Contributing

Thanks for your interest in SoulForge. This document tells you everything you need to know.

Setup

git clone https://github.com/proxysoul/soulforge
cd soulforge
bun install
bun run dev

Requirements: Bun >= 1.0, Neovim >= 0.9, at least one LLM API key (see README).

Scripts:

bun run dev          # start soulforge
bun run typecheck    # tsc --noEmit
bun run lint         # biome check
bun run lint:fix     # biome auto-fix
bun run format       # biome format

Run all three checks before submitting a PR:

bun run lint:fix && bun run format && bun run typecheck

Project Structure

src/
├── index.tsx                 # entry point
├── boot.tsx                  # bootstrap / startup sequence
├── types/index.ts            # shared types
├── config/index.ts           # scoped config (session/project/global)
│
├── stores/                   # Zustand state stores
│   ├── ui.ts                 # global UI state
│   ├── errors.ts             # error log
│   ├── repomap.ts            # repo map status
│   └── statusbar.ts          # status bar state
│
├── components/               # UI (OpenTUI — React for terminals)
│   ├── App.tsx               # root — state, keybindings, layout
│   ├── commands.ts           # slash command dispatch (50+ commands)
│   ├── shared.tsx            # Spinner, PopupRow, color constants
│   ├── syntax.ts             # syntax highlighting
│   ├── EditorPanel.tsx       # neovim display
│   ├── InputBox.tsx          # chat input (paste collapse, history)
│   ├── MessageList.tsx       # chat history
│   ├── ToolCallDisplay.tsx   # tool call progress
│   ├── DiffView.tsx          # inline/side-by-side diffs
│   ├── PlanView.tsx          # plan mode sidebar
│   ├── StructuredPlanView.tsx # step-by-step plan progress
│   ├── PlanProgress.tsx      # plan execution tracking
│   ├── LlmSelector.tsx       # Ctrl+L model picker
│   ├── RouterSettings.tsx    # per-task model assignment
│   ├── ProviderSettings.tsx  # thinking, effort, speed settings
│   ├── WebSearchSettings.tsx # web search configuration
│   ├── SkillSearch.tsx       # Ctrl+S skills browser
│   ├── GitMenu.tsx           # Ctrl+G git operations
│   ├── GitCommitModal.tsx    # AI commit message
│   ├── SessionPicker.tsx     # session restore
│   ├── ContextBar.tsx        # context budget inspector
│   ├── ChangedFiles.tsx      # changed files tree
│   ├── RepoMapIndicator.tsx  # repo map status display
│   ├── MemoryIndicator.tsx   # memory status
│   ├── EditorSettings.tsx    # editor/LSP toggles
│   └── ...                   # other popups and views
│
├── core/
│   ├── agents/               # forge + subagent definitions
│   │   ├── forge.ts          # main agent (factory — new per turn)
│   │   ├── explore.ts        # read-only subagent
│   │   ├── code.ts           # full-access subagent
│   │   ├── web-search.ts     # web search agent (multi-step research)
│   │   ├── agent-bus.ts      # shared cache, edit mutex, findings
│   │   ├── bus-tools.ts      # bus-aware tool wrappers
│   │   ├── subagent-tools.ts # exposes subagents as tool calls
│   │   ├── subagent-events.ts # dispatch event system
│   │   └── step-utils.ts     # step counting, budget tracking
│   │
│   ├── context/              # system prompt builder
│   │   └── manager.ts        # repo map, memory, git, toolchain detection
│   │
│   ├── intelligence/         # multi-backend code intelligence
│   │   ├── router.ts         # LSP → ts-morph → tree-sitter → regex
│   │   ├── instance.ts       # singleton intelligence instance
│   │   ├── cache.ts          # result caching
│   │   ├── repo-map.ts       # SQLite graph, PageRank, co-change, semantic summaries
│   │   ├── post-edit.ts      # post-edit diagnostics (before/after diff)
│   │   ├── types.ts          # shared intelligence types
│   │   └── backends/
│   │       ├── lsp/          # LSP client, server registry, standalone servers
│   │       ├── tree-sitter.ts # 20+ language grammars via WASM
│   │       └── ts-morph.ts   # TypeScript AST analysis
│   │
│   ├── editor/               # neovim spawn, screen, RPC
│   │
│   ├── llm/
│   │   ├── models.ts         # model fetching, context windows
│   │   ├── task-router.ts    # per-task model assignment
│   │   ├── provider-options.ts # thinking, effort, speed
│   │   └── providers/        # one file per provider
│   │       ├── types.ts      # ProviderDefinition interface
│   │       ├── anthropic.ts
│   │       ├── openai.ts
│   │       ├── google.ts
│   │       ├── xai.ts
│   │       ├── ollama.ts
│   │       ├── gateway.ts    # AI Gateway
│   │       └── proxy.ts      # CLIProxyAPI relay
│   │
│   ├── tools/                # 30+ tool definitions
│   │   ├── index.ts          # tool registry
│   │   ├── edit-file.ts      # file editing
│   │   ├── read-file.ts      # file reading
│   │   ├── read-code.ts      # symbol-level extraction
│   │   ├── grep.ts           # content search
│   │   ├── glob.ts           # file pattern matching
│   │   ├── shell.ts          # shell execution
│   │   ├── git.ts            # git operations
│   │   ├── navigate.ts       # LSP go-to-def, references, call hierarchy
│   │   ├── analyze.ts        # diagnostics, type info, outlines
│   │   ├── refactor.ts       # extract function/variable
│   │   ├── rename-symbol.ts  # workspace rename + grep verification
│   │   ├── move-symbol.ts    # cross-file symbol move + import update
│   │   ├── project.ts        # auto-detect toolchain, test/build/lint
│   │   ├── discover-pattern.ts # recurring code pattern detection
│   │   ├── web-search.ts     # multi-backend web search
│   │   ├── web-search-scraper.ts # Brave/DDG backends
│   │   ├── fetch-page.ts     # Jina/Readability page fetcher
│   │   ├── editor.ts         # neovim integration tools
│   │   ├── interactive.ts    # user prompts
│   │   ├── memory.ts         # persistent memory tools
│   │   └── file-events.ts    # edit/read event emission
│   │
│   ├── memory/               # SQLite-backed persistent memory
│   ├── sessions/             # session save/restore
│   ├── security/             # forbidden file patterns
│   ├── history/              # command history
│   ├── proxy/                # CLIProxyAPI lifecycle
│   ├── setup/                # prerequisite checks
│   └── secrets.ts            # secret detection
│
└── hooks/                    # React hooks
    ├── useChat.ts            # main chat logic
    ├── useNeovim.ts          # neovim lifecycle
    ├── useEditorFocus.ts     # focus management
    ├── useEditorInput.ts     # keystroke forwarding
    ├── useTabs.ts            # multi-tab management
    ├── useSessionBuilder.ts  # session construction
    ├── useLspStatus.ts       # LSP server status
    └── ...                   # other hooks

Contribution scope

SoulForge is open source under BUSL-1.1 with strict architectural governance. The maintainer (proxysoul) owns product direction. To respect everyone's time, the scope of acceptable contributions is explicit and enforced.

Read this section before writing code. PRs outside scope will be closed without review. This is not personal — it's how the project stays coherent with one maintainer.

In scope — open a PR directly

Area Path
Bug fixes anywhere
LLM providers src/core/llm/providers/*.ts
UI polish, themes, keybindings, accessibility src/components/, src/hooks/, src/stores/
Tool fixes (perf, edge cases, error messages — no behavior changes) src/core/tools/ (excluding new tools)
Documentation mintlify-docs/, README.md, recipes
Tests tests/
Build, CI, packaging scripts/, .github/workflows/

Out of scope — PRs will be closed unread

These paths define product direction. Do not open PRs against them. They are protected by CODEOWNERS and maintainer review is mandatory; drive-by changes are closed without discussion.

  • src/core/intelligence/ — repo map, ranking, indexing, backend router
  • src/core/agents/ — agent loop, dispatch, bus, subagent orchestration
  • src/core/context/manager.ts — context assembly
  • src/core/prompts/ — system prompts, family detection, mode overlays
  • src/core/memory/recall.ts — memory recall scoring
  • src/core/compaction/ — compaction strategies, working state extraction
  • src/hearth/ server internals — pairing protocol, permission engine
  • New tools, new agents, new commands

If you have an idea touching these paths, open a Discussion, not an issue or PR. The maintainer will respond yes, no, or let's design it together before any code is written. This is the only path forward for architecture proposals — it protects your time more than it protects the project.

The Discussions board is the right place for: "what if SoulForge did X", "I'd like to add Y", "have you considered Z architecture". Issues are for bugs and concrete feature requests in the in-scope areas above.

Why this is strict

Single maintainer. No co-maintainers, no committee. Every architecture PR is hours of design review the maintainer cannot afford to spend on speculative direction. The scope above is the contract: anything in-scope gets a fair review, anything out-of-scope is a closed-without-review fast path so contributors don't waste time waiting.

Same governance model as SQLite, Postgres, FreeBSD, Linux subsystems, aider. Open code, controlled direction.

Code rules

These are non-negotiable. PRs that break them will be asked to fix before merge.

  • Bun only. Never node, npm, or npx.
  • No any. TypeScript strict mode. Use proper types or Zod inference.
  • No unused variables. The compiler catches these.
  • No import React. JSX transform handles it.
  • Biome, not ESLint/Prettier. One toolchain for linting and formatting.
  • The AI is named Forge. Never "AI", "assistant", or "bot" in UI strings or prompts.

Architecture

For the full technical reference, see soulforge.proxysoul.com/concepts/architecture.

Agent System

SoulForge uses the Vercel AI SDK's ToolLoopAgent. Each chat turn creates a new agent instance (not a singleton) so the user can switch models mid-session with Ctrl+L.

The main agent (Forge) has 30+ direct tools and three subagent types:

  • Explore — read-only tools, fresh context window, for researching a codebase
  • Code — full tool access, fresh context window, for implementing changes
  • WebSearch — multi-step web research with search + page fetch in an agent loop

Subagents are exposed to Forge as regular tool calls via buildSubagentTools(). Each invocation gets its own context window. Multiple subagents run in parallel, coordinated through the AgentBus:

  • Shared file cache — deduplicates reads across agents (waiter pattern)
  • Tool result cache — LRU 200, cross-agent reuse
  • Edit mutex — serializes concurrent writes to the same file
  • Real-time findings — agents post discoveries visible to peers within 1-2 steps
  • Cache persistence — warm starts across dispatch calls

See soulforge.proxysoul.com/agents/dispatch for the full coordination reference.

Task Router

The task router assigns different LLM models to different task types (planning, coding, exploration, web search, semantic summaries). Configure via /router in the UI. See README.

Code Intelligence

A tiered router tries the best available backend for every code operation:

  1. LSP — definitions, references, rename, diagnostics, call hierarchy, formatting
  2. ts-morph — TypeScript/JavaScript AST analysis, tree-sitter — 20+ languages via WASM
  3. regex — universal fallback

The intelligence layer also powers:

  • Repo map — SQLite graph with PageRank ranking, git co-change analysis, LLM semantic summaries
  • Post-edit diagnostics — snapshots LSP errors before/after edits, reports only new errors

See soulforge.proxysoul.com/concepts/repo-map for the ranking system.

Compound Tools

Tools that do the complete job in a single call — no agent guessing:

  • rename_symbol — LSP rename + grep verification, finds the symbol itself
  • move_symbol — cross-file move with per-language import updates (TS/JS, Python, Rust)
  • project — auto-detects toolchain across 20+ ecosystems, runs test/build/lint/typecheck
  • navigate — LSP-backed go-to-definition, references, call hierarchy
  • read_code — extracts a single symbol's source instead of the whole file

See soulforge.proxysoul.com/concepts/compound-tools for design principles and benchmarks.

How Neovim Works

Neovim runs in a real PTY (via Bun.spawn with terminal) and uses --listen <socket> for a separate RPC channel. The PTY output is rendered natively by ghostty-terminal (ghostty's Zig VT parser), which handles cursor shapes, escape sequences, and scroll regions. RPC over the Unix socket provides API access for file operations, cursor polling, and LSP integration. When the editor panel is focused, raw stdin bytes are forwarded directly to the PTY.

How Providers Work

Each LLM provider is a self-contained file in src/core/llm/providers/ that implements the ProviderDefinition interface. The provider registry handles everything else — model resolution, icon display, API fetching, context window lookup.

Adding a New LLM Provider

One file, two lines in the registry.

1. Create the provider file. Copy any existing one as a template:

cp src/core/llm/providers/anthropic.ts src/core/llm/providers/mistral.ts

Implement the ProviderDefinition interface:

export const mistral: ProviderDefinition = {
  id: "mistral",
  name: "Mistral",
  envVar: "MISTRAL_API_KEY",
  icon: "▲",
  createModel(modelId) { /* ... */ },
  fetchModels() { /* ... */ },
  fallbackModels: [ /* ... */ ],
  contextWindows: [ ["mistral-large", 128_000] ],
};

2. Register it. In src/core/llm/providers/index.ts:

import { mistral } from "./mistral.js";

const ALL_PROVIDERS: ProviderDefinition[] = [
  gatewayProvider, anthropic, openai, xai, google, ollama,
  mistral,  // add here
];

3. Verify. bun run typecheck will catch any missing fields.

That's it. The model picker, provider status checks, context window lookup, and icon display all work automatically.

Adding a New Tool

Tools live in src/core/tools/. Each file exports a builder function that returns a Vercel AI SDK tool definition.

1. Create the tool file:

// src/core/tools/my-tool.ts
import { tool } from "ai";
import { z } from "zod";

export function buildMyTool(ctx: ToolContext) {
  return tool({
    description: "What this tool does — be specific",
    parameters: z.object({
      param: z.string().describe("What this param is"),
    }),
    execute: async ({ param }) => {
      // Tool output should state facts confidently
      // Never say "verify" or "check" — it triggers agent spirals
      return `Done. Result: ${param}`;
    },
  });
}

2. Register it in src/core/tools/index.ts.

3. Design principles (from compound tools):

  • Tool finds things itself — don't make the agent locate/explore first
  • Confident output — state facts, never hedge
  • One call = complete job
  • Accept flexible input — symbol name over file path + line number
  • Large files (>50MB) auto-truncate with a helpful message — never refuse or hang

Submitting a PR

  1. Fork and branch off main
  2. Make your changes
  3. Run bun run lint:fix && bun run format && bun run typecheck
  4. Open a PR with a clear description of what and why
  5. One feature or fix per PR

If you're unsure whether something is in scope, open an issue first.

License

Business Source License 1.1. By contributing, you agree your code is licensed under the same terms. See LICENSE and COMMERCIAL_LICENSE.md for details.