# SymForge Implementation Plans — Compact-3 Trust + Usability + Onboarding

Generated by the `improve` skill on 2026-06-18, base commit `1998760` (`main`).

**Goal (operator-set):** drive SymForge to the keystone — **real token saving +
complete LLM trust** — by closing the gap between "an LLM can *trust* it"
(feature 010, shipped in 8.2.0) and "an LLM can actually *use* it end-to-end
without falling back to native tools or hitting dead-ends."

Every finding below is **code-verified** against live `main` (anchors confirmed,
not subagent leads) and corroborated by three independent dogfood agents driving
8.2.0 over real repos (SymForge itself + the AAP Rust workspace, ~1421 files).
This is a **living index** — new dogfood findings fold in here.

## Spine rules (non-negotiable, carried from 010 + the constitution)

1. Do **NOT** revert compact-3 or re-expose the 35-tool wire surface to "fix"
   anything. Broaden the `symforge` / `symforge_edit` **facades** and route
   intents to the existing internal tools.
2. **Honesty contract:** no label/field/count claims a capability or
   completeness the code does not deliver. Relabel != validate.
3. **Constitution:** embed isolation (`cargo check --no-default-features
   --features embed` stays green), transport parity, determinism/recovery,
   verification-before-done.

## Verification gates (every plan, this repo)

```
cargo fmt --check
cargo check --all-targets
cargo clippy --all-targets -- -D warnings
cargo test --all-targets -- --test-threads=1
cargo build --release
cargo check --no-default-features --features embed
```

## Execution order & status

| Plan | Title | Priority | Effort | Depends on | Status |
|------|-------|----------|--------|------------|--------|
| 001 | Add Cursor (home-cwd harnesses) to `symforge init` + write `SYMFORGE_WORKSPACE_ROOT` | P0 | M | — | DONE |
| 002 | cwd-independent workspace resolution via MCP `roots` capability | P0 | L | — | DONE |
| 003 | Honest empty-index + no-repo-root recovery hints (no LLM-unactionable "re-launch") | P0 | S | — | DONE |
| 003b | LLM-callable index/reindex trigger on the compact surface (folded into 002 index-loading) | P0 | M | 002 | TODO |
| 004 | `search_text`/`search_files` completeness honesty — disclose hidden/dotdir exclusion | P1 | S | — | DONE |
| 004b | Index `.github` (coverage; reverses the tested `.hidden(true)` determinism invariant) + same disclosure on `find_references`/`what_changed`/coupling labels | P1 | M | — | TODO |
| 005 | Compact-edit completeness — insert + within-symbol on the `symforge_edit` facade | P1 | M | — | DONE |
| 006 | Edit `body` contract docs + dedent-normalize the splice (kills compounding indent) | P1 | S/M | — | DONE |
| 007 | Read-facade routing — NL-`find` OR-explosion (stopword filter on fusion terms) | P1 | S | — | DONE |
| 007b | Read-facade routing — `symbol=` routing, prose-in-symbol corrective error, `find_references` N-defs disclosure | P1 | M | — | DONE |
| 008a | Operator port range 8000-8999/5000-5999, never ephemeral (corporate networks) | P2 | S | — | DONE |
| 008b | Persistent shared-server lifecycle (start-or-attach, last-client shutdown) + `/symforge-admin` command file install | P2 | L | — | TODO |
| 009b | Edit economics grounding (body bytes; preview echoes body, apply footer-only) | P2 | M | — | DONE |
| 009a | Opt-in `SYMFORGE_STEL_COMPACT` one-line envelope (cut per-call ledger noise) | P2 | S | — | DONE |
| 009a-default | Make compact the *default* envelope (ripples ~10 honesty-surface test files + schema) | P2 | M | 009a | TODO |
| 009c | Wire the `CalibrationState` auto-tune loop (self-correct predictions from the ledger) | P2 | L | 009b | TODO |

Status values: TODO | IN PROGRESS | DONE | BLOCKED (reason) | REJECTED (rationale).

**Plan files 002–009 are pending** (being written in the next installment); their
verified anchors are captured in "Findings detail" below so no analysis is lost
if context compacts. **001 is written and ready to execute.**

## Dependency notes

- **001, 002, 003 are all P0 and independent of each other**, but the index must
  load (any one of them) before P1/P2 deliver user-visible value. Ship 001 first
  (smallest diff, fixes the real Cursor failure using machinery that already
  works); 002 is the structural fix; 003 makes a cold compact surface
  self-recoverable.
- **013 and 012 are MOOT until the index loads.** 004–007 (the compact read/edit
  surface) and 008–009 (server/economics) assume a populated index.
- 006 depends on 005 (both touch the `symforge_edit` facade / `body` contract;
  land the capability, then the contract docs + splice fix on top).

## Findings detail (verified anchors)

### P0 — Onboarding / index resolution (TOTAL-FAILURE: index won't load)

**Symptom:** in Cursor, every real query fails with `index_ready: false,
index_files: 0`, while the *same* 8.2.0 binary works in Claude Code.

**Root cause (verified):**
- `src/discovery/mod.rs:501-537` `find_project_root()` resolves the project from
  the launch CWD; `is_forbidden_root()` (`:572-657`, home guard at `:603-608`)
  returns `true` for a home dir, so walking up from `C:\Users\<name>` yields
  `None` → empty index (`src/main.rs:57-63`, `:279-328`, `LiveIndex::empty()`).
- The override env is `SYMFORGE_WORKSPACE_ROOT` (`mod.rs:490` `WORKSPACE_ROOT_ENV`),
  checked **first** in `find_project_root` via `workspace_root_env_override()`
  (`:546-568`) — it already works; it just isn't set for Cursor.
- `symforge init` (`src/cli/init.rs`) configures Claude Code, Claude Desktop,
  Codex, Gemini, KiloCode — **Cursor is absent** (harness branches ~`:258-351`).
  Claude Desktop gets `SYMFORGE_SURFACE=compact` + `SYMFORGE_WORKSPACE_ROOT`
  (`:777-794`); Cursor gets nothing → home-cwd disease. → **Plan 001.**
- The MCP `roots` capability is **not consumed**: no `initialize` override, no
  `roots` in `ServerCapabilities` (`src/protocol/mod.rs:919-1012`); no per-request
  workspace param. This is the structural cwd-independence fix. → **Plan 002.**
- Indexing is designed to be **LLM-triggered**, but compact-3 exposes **no index
  trigger**: `COMPACT_TOOL_NAMES = [symforge, symforge_edit, status]`
  (`src/stel/surface.rs:7-8`), gated by `enforce_compact_surface`
  (`src/protocol/surface_probe.rs:39-51`); `IntentBucket` (`src/stel/types.rs:13-22`)
  has no index intent; `index_folder` is full-surface-only. And the empty-index
  recovery hint (`src/protocol/format.rs:4789-4799`) tells the LLM to "Re-launch
  SymForge from your project root" — **unactionable by an LLM and contradicting
  the LLM-triggered model** — with a test (`src/protocol/format/tests.rs:2691-2712`)
  *locking in* that wrong text. → **Plan 003.**

### P1 — Trust & usability of the compact read/edit surface

- **`search_text` silent omission (trust/completeness, HIGH).** Default
  generated/vendor/hidden filters + a result cap drop files without naming them;
  "22 matches in 7 files" omitted `.github/workflows/release-please.yml` that
  ripgrep found (8 files). A count presented as complete that isn't — the 010
  honesty class. Fix: emit an explicit "filtered N generated/vendor/hidden:
  [list]" + "result cap hit: N more omitted" line, and/or an exhaustive mode.
  (Anchors: `src/live_index/search.rs` `classify_path` ~`:350-404`,
  `TextSearchOptions` ~`:452-470`; the count/format path in `src/protocol/format.rs`.)
  → **Plan 004.**
- **Compact `symforge_edit` is replace-only (the original 013 blocker).** Routes
  only to `replace_symbol_body` (`src/protocol/tools.rs:8582-8608`,
  `src/protocol/edit_planner.rs:96` validates only path/symbol/body). Insert /
  within-symbol / import edits force a fallback to the harness-native editor,
  which then desyncs against SymForge's out-of-band atomic write. Internal ops
  exist: `insert_symbol` (`src/protocol/edit_tools.rs:831`), `edit_within_symbol`
  (`:1204`), batch (`src/protocol/edit.rs:1800`). Fix: route insert + within-symbol
  through the facade via intent. → **Plan 005.**
- **`body` contract undocumented + splice compounds indent.** `symforge_edit`
  description is one line (`tools.rs:8584`); `body` field has zero docs
  (`src/stel/types.rs:114`); schema `body` has no description. Unstated: it is the
  full item source (signature + body), expects **unindented** input, doc-comments
  outside the range are preserved (`edit_tools.rs:684-703`), and preview is a
  byte-count dry-run not a post-splice view. AND `apply_indentation`
  (`src/protocol/edit.rs:766-783`) prepends the symbol's indent to every non-empty
  line **without stripping the body's own base indent first** — a body indented to
  12 under an 8-space symbol yields 20 (its unit test assumes a column-0 body).
  Fix: document the contract + dedent-to-minimum before re-indent; add a repro
  test (symbol at 4-space, body at 8-space lead → assert 4-space nesting).
  → **Plan 006.**
- **Read-facade routing.** (a) Multi-word/NL `find` tokenizes into per-word OR
  literals (incl. stopwords like "and") → hundreds of doc hits; should phrase/AND
  or route to symbol/semantic. (b) `symbol=` hint appears ignored — a long query
  with `symbol:"is_fresh"` ran full-text on the query and returned empty. (c)
  Prose in `symbol` returns "Symbol not found: <whole sentence>" instead of a
  corrective error. (d) `find_references` resolves by bare name repo-wide,
  ignoring a path scope — refs to `update_status` scoped to one file returned a
  different same-named trait method. (Anchors to pin in the plan: `src/stel/planner.rs`
  find/trace routing, `src/protocol/tools.rs` `symforge` facade + `find_references`
  handler, `src/live_index/query.rs`.) → **Plan 007.**

### P2 — Economics honesty + operator server lifecycle

- **Edit economics ungrounded.** Read tools were grounded on real bytes by 010
  US5 (`src/stel/controller.rs grounded_step_tokens` → `competent_manual_baseline_chars`
  `src/protocol/format.rs:4965`), but `ground_plan_economics` (`src/protocol/tools.rs:5547-5604`)
  `continue`s past anything not in `get_file_context|get_file_content|get_symbol`,
  so **edits never get grounded** and fall back to the flat planner floors
  (`src/stel/planner.rs:51-52` reads 400/800, edits 520/900). That is the 57–507%
  edit-prediction error — not a mystery, an ungrounded path. Fix: ground edit
  economics on `len(body)` + the replaced symbol span. → **Plan 009.**
- **Calibration loop unwired.** `CalibrationState` (`src/stel/types.rs:302-308`) is
  defined but never read; `calibration_auto_tune` is in `DEFERRED_ITEMS`
  (`src/stel/status.rs:19`). Operator wants it wired (estimate + self-correcting
  loop). → **Plan 009.**
- **Envelope noise.** The ~10-line `── stel ──` block (`src/stel/envelope.rs:54-96`)
  is prepended **unconditionally** to every `symforge`/`symforge_edit` response —
  no verbose/quiet flag. Fix: `SYMFORGE_STEL_VERBOSE` (default compact one-liner).
  → **Plan 009.**
- **Operator server dies on exit.** `start_operator_server` (`src/cli/admin.rs:127-187`)
  runs `serve::run` on a thread *inside* the admin process; `symforge admin`
  returns and the process exits, killing the server (port 61850 went dead). The
  daemon (`src/daemon.rs`) tracks per-project sessions and drops a project on its
  last session close (`:693-756`) but has **no** idle/last-client daemon shutdown.
  Operator's model: one shared persistent server; any harness starts-or-attaches;
  dies only when the last harness disconnects. Plus: both port probes
  (`src/server/serve.rs:179` `probe_free_listener`, `src/cli/admin.rs:200`
  `select_free_addr_std`) fall back to ephemeral `:0` (→ 61850) — must scan
  8000-8999 then 5000-5999, never ephemeral. Plus: the `/symforge-admin` Claude
  Code command file was never installed (`~/.claude/commands/symforge-admin.md`
  absent → fell back to the report-only MCP prompt). → **Plan 008.**

## Findings considered and rejected (so they aren't re-audited)

- **"`replace_symbol_body` splices the body verbatim" (the original #5):** REJECTED
  as stated. It *does* re-indent (`detect_indentation` + `apply_indentation`,
  `edit_tools.rs:704-710`). The real bug is narrower: `apply_indentation` does not
  dedent the caller's body first, so a pre-indented body *compounds*. Captured in
  Plan 006, not a splice rewrite.
- **"Re-launch from your project root" as the compact recovery hint:** this is the
  *current* behavior and is wrong for the LLM-triggered model; Plan 003 replaces
  it. The test asserting it (`format/tests.rs:2691-2712`) must be updated, not kept.
- **Re-expose the 35-tool surface / index_folder on the wire to fix indexing:**
  REJECTED by spine rule 1 — broaden the facade instead (Plan 003).

## Immediate operator stopgap (Cursor, do now — no rebuild needed)

Add `SYMFORGE_WORKSPACE_ROOT` to the `symforge` block's `env` in
`~/.cursor/mcp.json`, then reload SymForge MCP in Cursor:

```json
"env": {
  "SYMFORGE_HOME": "C:\\Users\\rakovnik\\.symforge",
  "SYMFORGE_WORKSPACE_ROOT": "E:\\project\\symforge",
  "RUST_LOG": "symforge=info,warn"
}
```

Verified: `SYMFORGE_WORKSPACE_ROOT` is checked first in `find_project_root`
(`discovery/mod.rs:502`) and validated (must be an existing, non-forbidden dir),
so this populates the index immediately regardless of launch CWD. Plan 001
makes `symforge init` write this automatically; Plan 002 removes the CWD
dependency entirely.
