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]>
This commit is contained in:
PouiiroandSoulForge committed 2026-05-10 23:50:11 +02:00
1 parent ce313edf41
commit 350670c10b
20 files changed
+391 -28

No files matched your search

+30
View File
@@ -0,0 +1,30 @@
# SoulForge code owners.
#
# Architecture and core IP paths require maintainer review on every PR.
# Other paths (providers, components, docs) accept community contributions
# with regular review. See CONTRIBUTING.md for the full governance policy.
# Default — every file requires maintainer review unless overridden below.
* @proxysoul
# Architecture / core IP — locked. Discuss in an issue before any PR.
/src/core/intelligence/ @proxysoul
/src/core/agents/ @proxysoul
/src/core/context/manager.ts @proxysoul
/src/core/prompts/ @proxysoul
/src/core/memory/recall.ts @proxysoul
/src/core/compaction/ @proxysoul
/src/hearth/ @proxysoul
# Build / publish — sensitive to source-map / identifier leaks.
/scripts/build.ts @proxysoul
/scripts/bundle.sh @proxysoul
/.github/workflows/ @proxysoul
/package.json @proxysoul
# License / governance docs.
/LICENSE @proxysoul
/COMMERCIAL_LICENSE.md @proxysoul
/GOVERNANCE.md @proxysoul
/CONTRIBUTING.md @proxysoul
/CODEOWNERS @proxysoul
+5
View File
@@ -0,0 +1,5 @@
# GitHub Sponsors button + alternative funding links.
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository
github: [proxysoul]
custom: ["https://paypal.me/waeru"]
+31
View File
@@ -0,0 +1,31 @@
<!--
Read CONTRIBUTING.md before opening this PR.
Out-of-scope PRs are closed without review. Out of scope means:
- src/core/intelligence/, src/core/agents/, src/core/context/manager.ts
- src/core/prompts/, src/core/memory/recall.ts, src/core/compaction/
- src/hearth/ server internals
- New tools, new agents, new commands
For architecture proposals: open a Discussion, not a PR.
-->
## What
<!-- One sentence: what does this change? -->
## Why
<!-- One paragraph: what problem does it solve, what was the prior behavior? -->
## Scope check
- [ ] I read `CONTRIBUTING.md` and this PR is in the **In scope** table.
- [ ] This PR does not touch any path in the **Out of scope** list.
- [ ] One feature or fix per PR (no drive-by refactors).
- [ ] `bun run lint:fix && bun run format && bun run typecheck` passes.
- [ ] If behavior changed, a test was added or updated.
## Verification
<!-- How did you verify this works? Steps, commands, or screenshots. -->
+42 -1
View File
@@ -158,7 +158,48 @@ src/
└── ... # other hooks
```
## Rules
## 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.
+68
View File
@@ -0,0 +1,68 @@
# Governance
SoulForge is a single-maintainer open-source project. This document explains how decisions get made.
## Maintainer
@proxysoul owns:
- Product direction and roadmap
- Architecture decisions (agent design, intelligence layer, prompts, context strategy)
- Release cadence and version numbers
- Final say on every merged PR
- License interpretation and commercial license issuance
## Decision-making
| Type of change | How it lands |
|---|---|
| Bug fix | Open a PR; maintainer reviews and merges |
| Provider adapter | Open a PR; maintainer reviews and merges |
| UI / theme / docs | Open a PR; maintainer reviews and merges |
| Tool improvement (no architecture change) | Open a PR; maintainer reviews and merges |
| New feature touching only "open" paths | Open an issue first to align; PR after |
| Architecture change | Open an issue **first**. Maintainer responds yes / no / "let's discuss". Skip this and the PR gets closed. |
| Public API / SDK change | Open an issue first; semver-stable contracts are not changed casually |
| Build / publish pipeline | Open an issue first; sensitive to source-map and identifier leaks |
The "open an issue first" rule exists to save your time. The maintainer would rather say "no, here's why" in 5 minutes than have you spend a weekend on a PR that won't land.
## Why strict architectural governance
SoulForge's value is in how the pieces fit together — agent loop, intelligence, context, prompts, memory. The maintainer needs to keep that vision coherent across many releases. Open-source contributions to architecture would fragment that vision and slow the project down.
This is the same model used by SQLite, Postgres, FreeBSD subsystems, the Linux kernel, aider, and most successful single-maintainer open-source projects.
CODEOWNERS enforces this on GitHub: PRs touching IP-sensitive paths require maintainer review and cannot self-merge.
## What "open source" means here
- **Visibility:** every line of source is on GitHub. You can read, audit, and learn from it.
- **Forkability:** you can fork the repo and patch it for yourself. BUSL-1.1 limits commercial redistribution for 4 years; converts to Apache 2.0 on March 15, 2030.
- **Contribution:** PRs welcome in the areas listed in [CONTRIBUTING.md](CONTRIBUTING.md). Architecture is single-maintainer.
- **Privacy:** SoulForge runs entirely on your machine. Your code is sent only to the LLM providers you choose. There is no SoulForge backend. See [PRIVACY.md](PRIVACY.md).
## License gates
| License | Use case |
|---|---|
| BUSL-1.1 (default) | Personal use, internal company use, non-redistribution. Free forever. |
| [Commercial license](COMMERCIAL_LICENSE.md) | Repackaging or reselling SoulForge as a commercial product or service during the BUSL period. Email proxysoul to inquire. |
| Apache 2.0 (auto-conversion) | All uses, after March 15, 2030. |
## Communication
- **Bug reports / feature requests:** [GitHub issues](https://github.com/proxysoul/soulforge/issues)
- **Security disclosures:** see [SECURITY.md](SECURITY.md)
- **Commercial license inquiries:** open a GitHub issue or contact via the email listed in COMMERCIAL_LICENSE.md
- **Discussion:** [GitHub Discussions](https://github.com/proxysoul/soulforge/discussions) (when enabled)
## Forking
You're free to fork. If your fork ships under a different name and respects BUSL-1.1, that's fine. If you want to maintain a permanent fork, that's also fine — but understand that upstream changes won't merge automatically and you take on the maintenance burden yourself.
If you're forking because a PR was rejected: this governance model means architecture rejections are intentional, not arbitrary. A fork that re-applies rejected architecture changes is welcome to exist; it's just not what SoulForge is.
## Future
This is a single-maintainer project today. If at some point a sustainable funding model materializes (sponsorships, commercial licenses, acquisition), governance may evolve — additional maintainers, a steering committee, or a non-profit foundation. Any such change will be announced clearly in advance.
+114
View File
@@ -0,0 +1,114 @@
# Privacy
SoulForge is a **local-first** AI coding tool. This document describes exactly what data flows where, so you can verify the privacy model directly against the source.
## TL;DR
- SoulForge runs entirely on your machine.
- There is no SoulForge backend. No telemetry. No analytics. No usage tracking.
- Code from your project is sent **only** to the LLM providers you choose to enable.
- API keys live in `~/.soulforge/secrets.json` (file mode `0600`) or your OS keychain.
- Sessions, repo map, and memory are SQLite databases under `~/.soulforge/` and `<project>/.soulforge/`.
## What gets sent over the network
Three categories, all on actions you initiate:
### 1. LLM provider API calls
Every chat turn, tool call result, and dispatched subagent talks to the LLM provider you've selected. The request payload contains:
- The system prompt (varies by mode — see [`src/core/prompts/`](src/core/prompts/))
- The conversation history for the current session
- The current Soul Map / repo map injection (file list, exported symbol names)
- File contents you asked the agent to read
- Tool definitions and tool call results
This is the **same as every other AI coding tool**. Cursor, Claude Code, Aider, Continue all do this. The provider you pick (Anthropic, OpenAI, Google, etc.) is the destination.
You can verify the exact payload at runtime — set `BUN_DEBUG=1` and look at the network requests, or read [`src/core/agents/forge.ts`](src/core/agents/forge.ts) and the provider adapters in [`src/core/llm/providers/`](src/core/llm/providers/).
### 2. Web search (opt-in, with approval prompt)
When the agent decides to search the web (`web_search` tool), SoulForge prompts you for approval before each search. The search query goes to the configured search backend (Brave, DuckDuckGo, or Tavily) — never to SoulForge servers (there are none).
Disable: set `webSearch: false` in `~/.soulforge/config.json` or run with `--no-web-search`.
### 3. Page fetches (opt-in, with approval prompt)
When the agent decides to fetch a URL (`fetch_page` tool), it prompts you. The fetch goes to the URL you approve, optionally through Jina Reader for HTML cleanup.
## What does NOT leave your machine
- Your file system contents, except files the agent explicitly reads and includes in an LLM request you initiated.
- Repo map, memory database, sessions — all in SQLite under `~/.soulforge/`.
- API keys — stored locally in `~/.soulforge/secrets.json` (mode `0600`) or in your OS keychain when available.
- Bug reports, error traces, performance metrics — none of this is collected. SoulForge has no telemetry.
- Your usage patterns — when you type, what you ask, which models you use. Nobody is watching.
## What's in `~/.soulforge/`
```
~/.soulforge/
├── secrets.json # API keys, mode 0600 (you only)
├── config.json # global config
├── memory.db # global persistent memory (SQLite)
├── sessions/ # session JSONL files (per project)
├── lsp-servers/ # auto-installed LSP servers
└── installs/ # auto-installed tooling (nvim, etc.)
```
Per-project data lives in `<your-project>/.soulforge/`:
```
<project>/.soulforge/
├── config.json # project-specific config
├── memory.db # project-scoped memory
└── repomap.db # SQLite repo map (FTS5, PageRank scores)
```
## Sensitive file blocking
By default, SoulForge refuses to read files matching these patterns (the agent gets an empty result, even if it asks):
- `.env`, `.env.*`
- `*.pem`, `*.key`, `id_rsa*`, `id_dsa*`, `id_ecdsa*`, `id_ed25519*`
- `credentials`, `credentials.json`
- `.npmrc`, `.netrc`, `.pgpass`
- Anything matching the user's `/privacy add` patterns
The list is in [`src/core/security/forbidden.ts`](src/core/security/forbidden.ts). Add your own patterns with `/privacy add <glob>` in the TUI or via the `forbidden` field in `~/.soulforge/config.json`.
## Hearth (remote control)
The optional Hearth daemon exposes SoulForge to a paired Telegram or Discord bot you set up on your own account. When enabled:
- Messages you send from your phone go through Telegram's / Discord's servers (their privacy policies apply).
- The Hearth daemon runs locally on your machine.
- Your code never leaves your host.
Disable: don't pair Hearth, or run `/hearth unpair`.
## License-key validation (future)
The codebase has scaffolding for a future "Pro" tier with license-key validation. **Today, no license check happens.** When/if a Pro tier ships:
- Validation will be a single HTTP call to a license server on startup.
- The check will not transmit any project content — only the license key.
- The Pro features will run locally; no cloud execution.
## Verifying the privacy story
You don't have to take this document's word for it. SoulForge is open source. Audit paths:
1. Read [`src/core/llm/`](src/core/llm/) for every outbound network call.
2. Read [`src/core/agents/forge.ts`](src/core/agents/forge.ts) for what gets sent to LLMs.
3. Read [`src/core/security/forbidden.ts`](src/core/security/forbidden.ts) for sensitive-file blocking.
4. Run with `BUN_DEBUG=1` to see every HTTP request live.
5. Use any local network monitor (Little Snitch, Wireshark) to confirm.
If you find an unintended data flow, please report via [SECURITY.md](SECURITY.md).
## Data deletion
Everything SoulForge stores is in `~/.soulforge/` and `<project>/.soulforge/`. Delete those directories and SoulForge has nothing on you.
-7
View File
@@ -42,10 +42,3 @@ Use `/privacy` to add or remove forbidden file patterns:
- **Project scope** — patterns in `.soulforge/forbidden` apply to current project
- **Global scope** — patterns in `~/.soulforge/forbidden` apply everywhere
- Built-in patterns cover `.env`, `.pem`, `credentials`, `private_key`, `id_rsa`, `.npmrc`, `.netrc`, `shadow`, `passwd`, and more
## Supported Versions
| Version | Supported |
|---------|-----------|
| 3.x | Yes |
| < 3.0 | No |
+1 -1
View File
@@ -23,7 +23,7 @@ Configure which model each one uses in [the task router](/recipes/task-router).
## What the agents share
When Forge dispatches multiple sub-agents in parallel, they coordinate through the AgentBus:
When Forge dispatches multiple sub-agents in parallel, they coordinate through a shared bus:
- **File cache** — first reader caches; others reuse.
- **Tool cache** — read-only tool results shared across agents and dispatches.
+1 -1
View File
@@ -15,7 +15,7 @@ You don't configure this. It picks the best available backend for every operatio
| Operation | Examples |
|-----------|----------|
| **Go to definition** | "where is `parseConfig` defined?" |
| **Find references** | "who calls `AgentBus`?" |
| **Find references** | "who calls `parseConfig`?" |
| **Rename** | "rename `useChat` to `useConversation` everywhere" |
| **Type info** | "what's the return type of `fetchUser`?" |
| **Diagnostics** | "any errors in this file?" |
+3 -3
View File
@@ -13,9 +13,9 @@ The agent never wastes a turn grepping for "where does auth live" — it already
The map appears in every system prompt, looking like this:
```
src/core/agents/agent-bus.ts (→12)
+AgentBus Shared coordination bus for parallel subagent communication
+SharedCache
src/services/auth/session.ts (→12)
+SessionManager Tracks active sessions, refresh tokens, expiry
+SessionStore
```
- `+` — exported symbol
+1 -1
View File
@@ -66,7 +66,7 @@ Privacy patterns (`/privacy add`) block sensitive files from the agent entirely.
## Can I use it for work?
Yes. Use it anywhere, anytime, for free — personal projects, startups, enterprise, client work, whatever. The [BSL 1.1 license](https://github.com/proxysoul/soulforge/blob/main/LICENSE) only kicks in if you want to *redistribute SoulForge as a commercial product* (repackage it, resell it, host it as a paid service). Converts to Apache 2.0 on March 15, 2030.
Yes. Use it anywhere, anytime, for free — personal projects, startups, enterprise, client work, whatever. The [Business Source License 1.1](https://github.com/proxysoul/soulforge/blob/main/LICENSE) only kicks in if you want to *redistribute SoulForge as a commercial product* (repackage it, resell it, host it as a paid service). Converts to Apache 2.0 on March 15, 2030.
## What languages does code intelligence cover?
+3 -3
View File
@@ -20,7 +20,7 @@ That's it. macOS and Linux. Neovim and a Nerd Font are auto-installed on first l
<Tabs>
<Tab title="Prebuilt binary">
```bash
# download from https://github.com/ProxySoul/soulforge/releases/latest
# download from https://github.com/proxysoul/soulforge/releases/latest
tar xzf soulforge-*.tar.gz && cd soulforge-*/ && ./install.sh
```
</Tab>
@@ -32,7 +32,7 @@ That's it. macOS and Linux. Neovim and a Nerd Font are auto-installed on first l
</Tab>
<Tab title="Source">
```bash
git clone https://github.com/ProxySoul/soulforge.git
git clone https://github.com/proxysoul/soulforge.git
cd soulforge && bun install && bun run dev
```
</Tab>
@@ -83,7 +83,7 @@ You need at least one LLM provider key. Pick one path:
Keys are stored in your OS keychain (macOS Keychain, Linux secret-tool). You can also `export ANTHROPIC_API_KEY=...` in your shell profile.
[See all 20+ providers →](/providers)
[See all 20+ providers →](/providers/overview)
## Launch
+2 -2
View File
@@ -10,7 +10,7 @@ Most AI coding tools treat your codebase as text. They grep, they paste, they ma
<CardGroup cols={3}>
<Card title="The first AST editor for AI" icon="scalpel" href="/tools/ast-edit">
Symbol-addressed TypeScript and JavaScript edits via ts-morph. Born from my own 2022 Master's thesis on programmatic JS to TS conversion. Changing a return type costs a handful of tokens.
Symbol-addressed TypeScript and JavaScript edits via ts-morph. Roots in a 2022 Master's thesis on programmatic JS to TS conversion. Changing a return type costs a handful of tokens.
</Card>
<Card title="Full LSP, your way" icon="brain" href="/tools/lsp">
Workspace rename, definitions, references, call and type hierarchy, diagnostics, code actions, format. Mason installer for 576+ servers from the TUI.
@@ -65,7 +65,7 @@ Neovim is optional. A Nerd Font is optional. SoulForge prompts to install either
## Plain-English prompts, real tool calls
```
> rename AgentBus to CoordinationBus
> rename UserService to AccountService
> move parseConfig from utils.ts to config/parser.ts
> change fetchUser to return Promise<User>
> run tests, fix lint, commit
+1 -1
View File
@@ -23,7 +23,7 @@ icon: "rocket"
<Step title="Make a change">
Describe the change in plain English.
```
> rename AgentBus to CoordinationBus across the project
> rename UserService to AccountService across the project
```
Watch the agent run `rename_symbol` — one tool call updates every file.
</Step>
+2 -2
View File
@@ -9,7 +9,7 @@ icon: "pen"
Just ask:
```
> rename AgentBus to CoordinationBus
> rename UserService to AccountService
```
The agent runs `rename_symbol` — one LSP-backed call updates every file and verifies there are no dangling references.
@@ -27,7 +27,7 @@ Moves the symbol, updates every import across the project, fixes re-exports. Ful
## Rename a file
```
> rename src/old-name.ts to src/components/new-name.ts
> rename src/old-name.ts to src/utils/new-name.ts
```
Moves the file, updates every importer.
+1 -1
View File
@@ -85,4 +85,4 @@ Each tab has its own independent checkpoint history. Git tags are namespaced by
## Steering
Steering messages (sent while the agent is working via `Ctrl+V` or the message queue) do not create new checkpoints. They fold into the current running checkpoint.
Steering messages (typed into the input while the agent is working) do not create new checkpoints. They fold into the current running checkpoint.
+1 -1
View File
@@ -115,7 +115,7 @@ Both backends support: definitions, references, hover, diagnostics, rename, code
### Mason auto-install
On first editor launch, SoulForge detects project languages and installs appropriate LSP servers via Mason. Servers are stored in `~/.local/share/soulforge/mason/bin/` and discovered automatically.
On first editor launch, SoulForge detects project languages and installs appropriate LSP servers via Mason. Servers are stored in `~/.soulforge/lsp-servers/` and discovered automatically.
## Vim hints
+13 -1
View File
@@ -1,6 +1,6 @@
---
title: "Themes"
description: "24 builtin themes, custom themes with hot reload — pick your flavor or add yours."
description: "36 builtin themes, custom themes with hot reload — pick your flavor or add yours."
icon: "palette"
---
@@ -34,6 +34,18 @@ Press `/theme` or `Ctrl+K` → search "theme" to open the theme picker. Themes p
| `proxysoul-main` | proxySoul — deep purple with hot pink |
| `proxysoul-coffee` | proxySoul Coffee — warm amber & burnt orange |
| `proxysoul-water` | proxySoul Water — ocean blue & teal |
| `cyberdream` | Cyberdream |
| `oxocarbon` | Oxocarbon |
| `sonokai` | Sonokai |
| `moonfly` | Moonfly |
| `melange` | Melange |
| `solarized-osaka` | Solarized Osaka |
| `bamboo` | Bamboo |
| `nordic` | Nordic |
| `synthwave` | Synthwave |
| `iceberg` | Iceberg |
| `ember` | Ember |
| `vesper` | Vesper |
### Light
+1 -1
View File
@@ -51,7 +51,7 @@ Open `/lsp status`. If your language server isn't attached:
/lsp install
```
Picks from the Mason registry and installs into `~/.local/share/soulforge/mason/`. Restart SoulForge after installing.
Picks from the Mason registry and installs into `~/.soulforge/lsp-servers/`. Restart SoulForge after installing.
## Neovim not found
+71 -2
View File
@@ -109,6 +109,24 @@ const reactCompilerPlugin: BunPlugin = {
},
};
// ── Strip absolute filesystem paths from the bundle ────────────────────
// Bun's bundler may inline absolute paths (cwd, $HOME) into the bundle as
// __dirname / __filename strings or comment markers. Replace them so the
// dev machine's filesystem doesn't ship with the binary.
const stripAbsPathsPlugin: BunPlugin = {
name: "strip-abs-paths",
setup(build) {
const cwd = process.cwd();
const home = process.env.HOME ?? "";
build.onLoad({ filter: /\.(ts|tsx|js|jsx)$/ }, async ({ path, loader }) => {
let src = await Bun.file(path).text();
if (cwd && src.includes(cwd)) src = src.split(cwd).join("");
if (home && src.includes(home)) src = src.split(home).join("~");
return { contents: src, loader };
});
},
};
// ── Parse args ───────────────────────────────────────────────────────
const isCompile = process.argv.includes("--compile");
@@ -220,12 +238,25 @@ if (isCompile) {
const finalPath = outfile ? resolve(outfile) : defaultBinary;
console.log(`✓ Compiled binary with React Compiler in ${elapsed}ms → ${finalPath}`);
} else {
// Production hardening: when NODE_ENV=production or --prod is passed, the
// npm `dist/` build is minified, sourcemap-free, identifier-mangled.
// This is what protects the published binary from `strings`-based source
// recovery and casual reverse-engineering.
const isProd = process.env.NODE_ENV === "production" || process.argv.includes("--prod");
const minify = isProd ? { whitespace: true, identifiers: true, syntax: true } : false;
const result = await Bun.build({
entrypoints: ["src/boot.tsx"],
outdir: "dist",
target: "bun",
naming: "[dir]/index.[ext]",
plugins: [reactCompilerPlugin, devtoolsStubPlugin],
plugins: [
stripAbsPathsPlugin,
reactCompilerPlugin,
devtoolsStubPlugin,
],
minify,
sourcemap: "none",
});
if (!result.success) {
@@ -244,6 +275,8 @@ if (isCompile) {
target: "bun",
naming: "[name].[ext]",
plugins: [reactCompilerPlugin],
minify,
sourcemap: "none",
});
if (!workerResult.success) {
@@ -285,9 +318,45 @@ if (isCompile) {
);
chmodSync("dist/bin.sh", 0o755);
// Production guards: assert no inline sourcemaps and no source-form sentinel
// class declarations leak into the bundle. Bun preserves export NAMES even
// with identifier mangling — the *class body* is what we protect.
if (isProd) {
const bundle = await Bun.file("dist/index.js").text();
const sentinels = [
/\bclass\s+ContextManager\b/,
/\bclass\s+AgentBus\b/,
/\bclass\s+RepoMap\b/,
/\bclass\s+WorkspaceCoordinator\b/,
/\bfunction\s+createForgeAgent\b/,
];
const leaks = sentinels.filter((re) => re.test(bundle)).map((re) => re.source);
if (leaks.length > 0) {
console.error(`✗ unmangled class/function declarations leaked: ${leaks.join(", ")}`);
process.exit(1);
}
if (bundle.includes("sourceMappingURL=data:")) {
console.error("✗ inline sourcemap detected in bundle");
process.exit(1);
}
// Post-build path scrub. The onLoad strip-paths plugin only sees our
// source; pre-bundled CJS deps inline absolute __dirname/__filename paths
// that survive into dist/index.js. Replace them.
const cwd = process.cwd();
const home = process.env.HOME ?? "";
let raw = bundle;
for (const needle of [cwd, home].filter(Boolean)) {
if (raw.includes(needle)) raw = raw.split(needle).join("");
}
if (raw !== bundle) {
await Bun.write("dist/index.js", raw);
}
console.log("✓ verified: sentinels absent, no inline sourcemap, paths stripped");
}
const elapsed = (performance.now() - start).toFixed(0);
const count = result.outputs.length + workerResult.outputs.length;
console.log(
`✓ Built ${count} artifact${count === 1 ? "" : "s"} with React Compiler in ${elapsed}ms`,
`✓ Built ${count} artifact${count === 1 ? "" : "s"} with React Compiler in ${elapsed}ms (minified=${!!minify})`,
);
}