npm package: @soulbatical/stella
Description: Learning Agent for MCP servers - knowledge base, reflections, bug reports, chat engine
Author: Albert Barth
Latest version: 1.8.20

# Stella - Learning Agent for MCP Servers

**Ralph bouwt, Stella navigeert** — Stella beheert kennis, reflectie, bugreports en permissions voor MCP servers.

## Stella & Ralph: Two Repos, One System

Stella and Ralph Manager are separate repositories with a deliberate architectural relationship:

| | Stella (`@soulbatical/stella`) | Ralph Manager |
|--|--------------------------|---------------|
| **Type** | NPM library (TypeScript package) | Full-stack application |
| **Role** | Middleware layer — gates, permissions, knowledge, reflections | Platform layer — dashboard, Telegram bot, autonomous loops, agents |
| **Imported by** | All MCP servers (web-mcp, snelstart-mcp, agentrook, sparkbuddy-live, ralph-manager, etc.) | Nothing — it's the top-level platform |
| **Database** | Uses Ralph Manager's Supabase (all `stella_*` tables) | Owns the Supabase project |
| **Approval flow** | Requests approvals via Telegram | Hosts the Telegram bot that handles approvals |

**Why separate repos?**
- **Blast radius** — A Stella change affects all consumers. Separate repos force conscious builds and testing per consumer.
- **Versioning** — Each consumer decides when to update Stella. No accidental HEAD changes.
- **Focus** — Stella = middleware concerns. Ralph Manager = platform/operational concerns.

**The relationship is like Stripe SDK vs Stripe Dashboard:** Stella is the library every project imports. Ralph Manager is the platform that hosts the infrastructure Stella depends on (database, approval flow, monitoring). Other consumers (agentrook, web-mcp, etc.) are pure consumers of the library — they use Stella but don't host its infrastructure.

## Changelog

### 2026-03-17: session_task — verplichte taakbeschrijving bij identity confirmation

**Probleem:** Bij sessiestart bevestigde de LLM wie hij was (identity), maar niet *wat* hij ging doen. Hierdoor was er geen expliciete internalisering van de taak, en was achteraf niet zichtbaar welke taak een sessie had.

**Oplossing:** `stella_confirm_identity` en `stella_session_init` vereisen nu een `session_task` parameter (min 10 tekens) naast de bestaande `summary`.

**Wijzigingen:**
1. **Tool definition** (`reflections.ts`) — `session_task` toegevoegd als required field in `confirmIdentityDef`
2. **Handler** (`reflections.ts`) — `handleConfirmIdentity` valideert `session_task` (min 10 tekens), retourneert het in de response
3. **Session init** (`howtos.ts`) — `sessionInitDef` schema en handler uitgebreid met `session_task`, blokkeert identity confirmation zonder
4. **Identity gate** (`reflections.ts`) — gate prompt vraagt nu om identity + taak met voorbeeld
5. **Akkoord response** (`howtos.ts`) — hint na akkoord toont `session_task` in het voorbeeld
6. **Stella hook** (`~/.claude/hooks/stella-hook.sh`) — stap 2 vermeldt expliciet dat `summary` en `session_task` beide verplicht zijn

**Voorbeeld:**
```
stella_confirm_identity
  summary="I am the Practical Executor. I focus on efficiency..."
  session_task="Fix the 401 authentication error in the live widget"
```

---

### 2026-02-28: Predictors — time-windowed tool health monitoring + Telegram alerting

**Probleem:** Stille tool failures bleven weken ongedetecteerd. `cost_summary` retourneerde 6+ weken lang lege resultaten (200 OK, geen error) door een niet-bestaande kolom (`project_name` i.p.v. `project_id` join). Geen enkel monitoring systeem pikte dit op.

**Oplossing:** Outcome-gebaseerde tool health monitoring via de neural predictors:

1. **`trainPredictors()`** — Analyseert howto_usage scores per tool met tijdsvensters (24h, 7d, 30d, all-time). Berekent per tool:
   - Success rate per venster (score >= 4 = success)
   - Trend: `improving` | `declining` | `stable` | `insufficient_data`
   - Lift boven/onder baseline (overall success rate)
   - Confidence gewogen op basis van lift × sample size

2. **Stale edge cleanup** — Oude `predicts` edges worden verwijderd vóór elke train-run, zodat tools die terug naar baseline gaan niet als "failure" blijven staan.

3. **Periodieke training** — Daemon traint elke 6 uur (+ 30s na startup). Geen handmatige actie nodig.

4. **Telegram alerting** — Bij declining tools met >10% drop en >=3 recente samples stuurt de daemon een alert via `POST /api/internal/notifications/agent` naar ralph-manager → Telegram. 24h cooldown per tool voorkomt spam.

**Voorbeeld alert:**
```
⚠️ Tool Health Alert

📉 stella_plan_update declining
   All-time: 73% (n=26)
   7d: 50% (n=4)
   Drop: -23pp
```

**Root cause cost_summary:** `scripts.ts` en `staff-reports.ts` selecteerden `project_name` van `ralph_sessions`, maar die kolom bestaat niet — de tabel heeft `project_id` (UUID FK). Fix: `.select("project:projects(name), ...")` join via FK relatie.

---

### 2026-02-23: Gate blokkade-tekst herschreven voor betere AI compliance

**Probleem:** Na elke MCP server reload reset `akkoordGiven` naar `false` (in-memory boolean). Wanneer de AI daarna een tool aanroept (bijv. `task_list`), retourneert de gate een blokkade-response. De AI interpreteerde dit consequent als "de tool gaf lege resultaten" of "de tools zijn kapot" en ging debuggen/omheen werken in plaats van gewoon `stella_howto_akkoord` te callen. Dit patroon herhaalde zich sessie na sessie.

**Root cause:** De blokkade-tekst stond ONDERAAN een muur van basis-instructies, bugs en identity context (~100+ regels). De AI las die muur als "informatie" en miste de actie-verplichting. Bovendien was niet expliciet dat de tool NIETS retourneerde — de AI dacht dat het resultaat van de oorspronkelijke tool was.

**Fix 1 — `gates.ts` blokkade-tekst herschreven:**
- Blokkade staat nu BOVENAAN (vóór basis-instructies, niet erna)
- `⛔ TOOL CALL BLOCKED — "{toolName}" returned NOTHING` — meteen duidelijk
- `"The result you see is NOT from {toolName}"` — voorkomt misinterpretatie
- `"This happens after every MCP reload"` — verklaart waarom het opnieuw moet
- Drie expliciete anti-patronen: "DO NOT work around this", "DO NOT interpret as empty results", "DO NOT debug why tools seem broken"

**Fix 2 — `stella-gate.sh` hook tekst uitgebreid:**
- Na de 3 onboarding-stappen staat nu een `⚠️ BELANGRIJK` blok
- Legt preventief uit dat "TOOL CALL BLOCKED" NIET betekent dat de tool kapot is
- Instrueert om NOOIT te debuggen maar gewoon de 3 stappen te doen
- Geldt bij ELKE sessiestart EN na elke MCP server reload

---

### 2026-02-21: Cross-scope howto editing fix (findHowtoForEdit)

**Probleem:** `howto_upsert`, `howto_patch`, `howto_delete` gebruikten strict scope matching (`eq(scope_id)`), terwijl `howto_get`, `howto_rate`, `howto_publish` cross-scope lookup gebruikten (`applyScopeFilter` + `maybeSingle()`). Dit veroorzaakte drie bugs:

1. **Patch/upsert konden publieke howto's niet vinden vanuit een project scope.** Een publieke howto (scope_id: null) was onvindbaar vanuit bijv. sparkbuddy-live scope. `howto_patch` gaf "not found in current scope", `howto_upsert` maakte een **scoped duplicate** aan i.p.v. de bestaande te updaten.

2. **Duplicates veroorzaakten crashes in alle cross-scope handlers.** Zodra er twee rijen bestonden met dezelfde slug (public + scoped), faalde `maybeSingle()` met "multiple rows returned" — waardoor `howto_get`, `howto_rate` en `howto_publish` ook kapot gingen.

3. **Delete/publish cyclus.** `howto_delete` vond alleen de scoped versie (strict match), niet de publieke. Na delete probeerde de agent opnieuw upsert → nieuwe scoped duplicate → zelfde deadlock.

**Fix:** Nieuwe helper `findHowtoForEdit(slug, columns)` vervangt alle 7 lookup-patronen:

```typescript
// Oud: 2 incompatibele patronen
// Pattern A (upsert/patch/delete): eq("scope_id", scopeId) → mist publieke howtos
// Pattern B (get/rate/publish): applyScopeFilter() + maybeSingle() → crasht bij duplicates

// Nieuw: 1 consistent patroon
const { data, error } = await findHowtoForEdit(slug);
// → Fetcht ALLE matches (own scope + public/null)
// → Disambigueert: prefereert eigen scope > public (null) > eerste match
// → Geen maybeSingle() meer → nooit crash bij duplicates
```

Aangepaste handlers: `handleHowtoGet`, `handleHowtoUpsert`, `handleHowtoPatch`, `handleHowtoRate`, `handleHowtoPublish`, `handleHowtoDelete`, `processInlineReview`.

---

### 2026-02-21: Duplicate howto prevention bij upsert

**Probleem:** Agents maakten regelmatig howto's aan die qua inhoud sterk overlappen met bestaande howto's. Voorbeeld: `systematic-browser-crud-testing` werd aangemaakt terwijl `saasaway-client-test-script` al hetzelfde onderwerp dekte. Dit leidde tot fragmentatie in de kennisbank.

**Fix:** `stella_howto_upsert` doet nu een embedding similarity check bij het aanmaken van een **nieuwe** howto (slug bestaat nog niet). Als er bestaande howto's zijn met >= 50% similarity, wordt de creatie geblokkeerd:

```
Similar howtos already exist:

  - "saasaway-client-test-script" (72% similar ★4, 2x) — Complete test procedure...

Options:
  1. Retry with force=true to create anyway
  2. Use stella_howto_patch slug="..." to add content to existing
  3. Use stella_howto_upsert slug="..." to fully rewrite existing
```

- **Alleen bij nieuwe slugs** — updates van bestaande howto's worden niet gecheckt (dat is bewust herschrijven)
- **Bypass:** `force=true` parameter om toch aan te maken na review van de matches
- **Geen extra stappen bij geen matches** — check is transparant en voegt geen latentie toe als er geen duplicaten zijn

---

### 2026-02-21: Gate hook optimalisatie + howto kwaliteitsverbeteringen

**Probleem 1: Gate hook injecteerde volledige instructies bij ELKE prompt.**
130+ regels context werd bij elk user prompt herhaald — enorme token-verspilling.

**Fix:** State file per CLI sessie (`~/.claude/state/stella-gate-$PPID`). Eerste prompt krijgt volledige instructie, daarna niets. Stale state files worden automatisch opgeruimd (PID check). Reflection keys worden ook maar 1x geïnjecteerd (eerste keer dat ze beschikbaar zijn).

**Probleem 2: Howto search te breed/plat.**
Agents zochten op het symptoom ("agentrook 401 authentication widget chat API") zonder werktype of context mee te geven.

**Fix:** Zoektermen moeten nu drie dimensies combineren:
- ONDERWERP — wat is het? (bijv. "401", "RLS")
- WERKTYPE — wat ga je doen? (bijv. "debug", "deploy", "refactor")
- CONTEXT — welke stack/laag? (bijv. "express", "supabase", "doppler")

**Probleem 3: Bij geen howto match ging agent blind bouwen.**
Geen kennis verzamelen, direct aan het werk.

**Fix:** Stap 1b instrueert nu expliciet om EERST kennis te verzamelen:
- Optie A: eigen kennis (Grep/Read bestaande code)
- Optie B: externe kennis (Context7, WebSearch, skills)
- Optie C: combinatie (vaak het beste)

**Probleem 4: Howto's werden te context-specifiek geschreven.**
"Fix 401 in agentrook widget" i.p.v. "Debug Express authentication errors".

**Fix:**
- Gate hook bevat nu stap 1c met generiek-schrijven regels + bad/good voorbeelden
- Upsert tool description bevat zelfde instructie als extra reminder
- `is_public` default gewijzigd van `false` naar `true` — howto's zijn standaard publiek

**Probleem 5: Agent begon inhoudelijk te antwoorden zonder onboarding.**
Bij "hoi, maak een advertentie" ging de agent vragen stellen zonder eerst howto's te laden.

**Fix:** Gate hook bevat nu een hard blok "EERSTE ACTIE — voordat je IETS anders doet":
1. stella_howto_akkoord
2. stella_confirm_identity
3. stella_howto_search
Pas NA stap 3 mag inhoudelijk geantwoord worden.

---

### 2026-02-21: Akkoord gate enforcement hersteld

**Probleem:** Op 19 feb werd de daemon geïntroduceerd met `setAkkoordForPid` in de hook. De hook signaleerde automatisch akkoord aan de daemon bij elk user prompt, waardoor de MCP akkoord gate altijd overgeslagen werd. Hierdoor was de hele learning workflow (howto search → plan → execute → summarize → rate) niet meer afgedwongen — Claude kon direct tools gebruiken zonder de Stella flow te doorlopen.

**Oorzaak:** De hook deed `setAkkoordForPid` → daemon zette `akkoordGiven=true` → MCP's checkten `isAkkoordConfirmedShared()` → `true` → gate overgeslagen. Zonder akkoord gate werd er geen howto geladen, zonder howto geen plan gate, en zonder plan gate geen enforcement.

**Fix:**
- `stella-gate.sh`: `setAkkoordForPid` vervangen door `registerCliPid` — registreert alleen de PID, geeft geen akkoord
- `daemon.ts`: nieuw `registerCliPid` method toegevoegd (lightweight, geen akkoord)
- `stella-howto-hooks.sh`: logging verplaatst naar `~/.claude/logs/stella/hooks-YYYY-MM-DD.log` (dagelijkse rotatie, alleen matches gelogd)
- Daemon stderr gaat nu naar `~/.claude/logs/stella/daemon-YYYY-MM-DD.log` (was `/dev/null`)

**Resultaat:** De akkoord gate blokkeert weer hard. Claude moet `stella_howto_akkoord` callen om tools te unlocken, wat de volledige learning workflow afdwingt.

---

## Documentation

| Document | Beschrijving |
|----------|-------------|
| [docs/RUVECTOR.md](docs/RUVECTOR.md) | RuVector local store: architectuur, daemon protocol, neural graph, data layout, troubleshooting |
| [docs/PLAN-neural-graph.md](docs/PLAN-neural-graph.md) | Neural Graph bouwplan (M1-M4): schema's, channel keys, co-activatie, policy engine. Status: gebouwd |
| [docs/SECURITY.md](docs/SECURITY.md) | Security model: HMAC-signed tokens, network isolation, threat model |
| [docs/PRD-stella-store-ruvector.md](docs/PRD-stella-store-ruvector.md) | Origineel ontwerp StellaStore + RuVector dual-write (historisch) |

## Features

- **How-To Knowledge Base** — Semantische zoekfunctie (pgvector embeddings), versioning, scoring. Basis how-to's laden alleen de eigen scope (~800 tokens i.p.v. ~5000)
- **Plans & Summaries** — Gestructureerd plan vooraf, samenvatting achteraf met semantic search op eerdere samenvattingen
- **Stella Daemon** — Centraal state-proces via Unix socket. Alle MCP servers delen onboarding, plan en howto state real-time
- **Hook-based Onboarding** — `stella-gate.sh` UserPromptSubmit hook levert instructies (alleen eerste prompt, daarna niets), start daemon en registreert CLI PID. Instrueert agent om onboarding (akkoord → identity → search) te doen als EERSTE ACTIE voordat inhoudelijk geantwoord wordt
- **Reflection System** — Identiteits-gebaseerde reflecties met 10 lagen + empathy questions
- **Bug Reports** — Gecentraliseerd in ralph-manager MCP (andere MCPs verwijzen hiernaar via gates)
- **Neural Graph** — Adaptief lerend netwerk (Hebbian co-activatie, bundle detectie, policy engine). Zie [docs/RUVECTOR.md](docs/RUVECTOR.md) voor operationele details en [docs/PLAN-neural-graph.md](docs/PLAN-neural-graph.md) voor het architectuurplan
- **Tool Health Monitoring** — Outcome-gebaseerde predictors detecteren stille tool failures via tijdsvensters (24h/7d/30d). Daemon traint elke 6 uur en stuurt Telegram alerts bij declining trends. Detecteert problemen die traditionele health checks missen (tools die 200 OK retourneren maar slechte resultaten opleveren)
- **AI User Sentiment** — PostToolUse hook analyseert het laatste user bericht met Claude Haiku voor sentiment (frustrated/urgent/positive/confused/correcting/neutral) + 7 indices. Fallback naar grep-heuristics als API onbeschikbaar
- **Gate Middleware** — Enforceert learning workflows (akkoord, evaluatie, summary, plan, identiteit, reflectie, project context)
- **Project Context Gate** — Write tools geblokkeerd tot project geselecteerd, deploy_config auto-update
- **Howto Hooks** — How-to's kunnen Claude Code lifecycle hooks bevatten (PreToolUse, PostToolUse, Stop, etc.) die automatisch actief worden bij `howto_get` en verwijderd bij `howto_rate`
- **Inline Parameters** — `_reflection`, `_review`, `_status` voor efficiente roundtrips
- **Permission System** — Universeel toegangsbeleid per tool (`free`, `confirm`, `approve`, `block`)
- **Agent Profiles** — Per-sessie permission overrides via geheime codes

---

## Architecture

```
┌──────────────────────────────────────────────────────────────┐
│                     Claude Code Session                       │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │  UserPromptSubmit Hook (stella-gate.sh)                │  │
│  │  - Levert instructies (alleen 1e prompt, daarna niets) │  │
│  │  - Start daemon als die er niet is                     │  │
│  │  - Registreert CLI PID (geen auto-akkoord)            │  │
│  │  - Injecteert reflection keys (1x, zodra beschikbaar)  │  │
│  └────────────────────────────────────────────────────────┘  │
│  ┌────────────────────────────────────────────────────────┐  │
│  │  Pre/PostToolUse + Stop Hook (stella-howto-hooks.sh)   │  │
│  │  - Query daemon voor actieve howto hooks               │  │
│  │  - Voert command/prompt/agent hooks uit                │  │
│  │  - Output via JSON (systemMessage + additionalContext) │  │
│  └────────────────────────────────────────────────────────┘  │
│                                                              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐    │
│  │snelstart │  │agentrook │  │  stella-  │  │  ralph-  │    │
│  │   MCP    │  │   MCP    │  │  proxy    │  │ manager  │    │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬─────┘    │
│       │              │              │              │          │
│       └──────────────┼──────────────┼──────────────┘          │
│                      │              │                         │
│              ┌───────▼──────────────▼───────┐                │
│              │     Stella Daemon Process     │                │
│              │  /tmp/stella-daemon.sock      │                │
│              │                               │                │
│              │  Shared State (in-memory):    │                │
│              │  - akkoordGiven               │                │
│              │  - identityConfirmed          │                │
│              │  - activePlan                 │                │
│              │  - openSessions              │                │
│              │  - activeHooks               │                │
│              │  - stepCounter               │                │
│              │  - reflectionKeys            │                │
│              │  - neuralPolicy (STATE_OUT)   │                │
│              │                               │                │
│              │  RuVectorStore (embedded):     │                │
│              │  - EmbeddingCache (cosine)    │                │
│              │  - MetadataStore (JSON/disk)  │                │
│              │  - Neural Graph (nodes/edges) │                │
│              │  - PolicyEngine (Thompson)    │                │
│              │  - Decay Runner (10 min)      │                │
│              │  - Predictor Trainer (6 hr)   │                │
│              │    → Telegram alerts           │                │
│              └───────────────────────────────┘                │
│                                                              │
│              ┌───────────────────────────────┐                │
│              │         Supabase              │                │
│              │  howtos, plans, reflections,  │                │
│              │  permissions, episodes        │                │
│              └───────────────────────────────┘                │
└──────────────────────────────────────────────────────────────┘
```

---

## Installation

### Optie 1: npm install from local path (aanbevolen)

```bash
npm install ../stella
```

### Optie 2: npm link (lokaal development)

```bash
# In stella project:
cd ~/projecten/stella
npm run build
npm link

# In je MCP project:
cd ~/projecten/web-mcp
npm link @soulbatical/stella
```

### Optie 3: npm install from git

```bash
npm install git+https://github.com/ralph-group/stella.git
```

---

## Environment Variables

Voeg toe aan je `.env`:

```env
# Ralph Manager Supabase (gedeeld tussen alle projecten)
LEARNING_SUPABASE_URL=https://xxx.supabase.co
LEARNING_SUPABASE_SERVICE_KEY=eyJ...

# Project scope (uniek per project)
LEARNING_SCOPE_ID=web-mcp          # of 'sparkbuddy', 'snelstart', etc.

# OpenAI voor semantic search
OPENAI_API_KEY=sk-...
```

---

## Quick Start

### Met withGates() (aanbevolen)

```typescript
import {
  withGates,
  initDaemonClient,
  configurePermissions,
  configureStellaClient,
  decorateToolDefinitions,
} from '@soulbatical/stella';

// Bij startup:
configureStellaClient(supabase, 'web-mcp');
configurePermissions({
  scopeId: 'web-mcp',
  approvalService: 'web-mcp',
  approvalUrl: 'http://localhost:3005',
});

// Initialize daemon connection (auto-starts daemon if not running)
await initDaemonClient('web-mcp');

// ListTools handler:
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return { tools: await decorateToolDefinitions(myToolDefs) };
});

// CallTool handler:
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
  return withGates(name, args, async () => {
    return myHandler(args);
  });
});
```

`withGates()` handelt automatisch af:
1. Inline parameter processing (`_reflection`, `_review`, `_status`)
2. Gate checks (akkoord, evaluatie, summary, plan, identiteit, reflectie, project context)
3. **Permission checks** (free/confirm/approve/block)
4. Tool execution met error handling
5. **Auto-update deploy_config** (na succesvolle write tools)
6. Post-enrichment (tips, reflection prompts, batch suggestions)

`decorateToolDefinitions()` doet twee dingen:
1. **Permission prefixes** — `[CONFIRM]`, `[APPROVE]`, `[BLOCKED]` in tool descriptions
2. **Inline parameter schemas** — Injecteert `_reflection` in de `inputSchema.properties` van elke tool, zodat LLMs het als formele parameter zien en inline gebruiken

### Handmatig (voor custom flows)

```typescript
import {
  checkGates,
  processInlineParams,
  getPostEnrichment,
  checkPermission,
  requestApproval,
} from '@soulbatical/stella';

const inlineResult = await processInlineParams(name, args);
if (inlineResult) return inlineResult;

const gate = await checkGates(name);
if (gate) return gate;

// Permission check
const perm = await checkPermission(name, args);
if (perm.blocked) return errorResponse('Tool blocked');
if (perm.level === 'approve') {
  const approval = await requestApproval(name, args);
  if (!approval.approved) return errorResponse(approval.message);
}

const result = await handler(args);

const { additions, blocked } = await getPostEnrichment(name, result);
if (blocked) return blocked;
result.content.push(...additions);
```

---

## Hook Setup (stella-gate.sh)

De hook is de eerste stap in elke sessie. Hij draait als `UserPromptSubmit` hook in Claude Code bij elk user prompt.

### Installatie

```bash
# Symlink (bron = stella repo, actief via Claude Code hooks)
ln -s ~/projecten/stella/hooks/stella-gate.sh ~/.claude/hooks/stella-gate.sh
```

### Wat doet de hook?

1. **Levert instructies (alleen eerste prompt)** — stack, naming, env, ports, onboarding flow, howto-schrijfregels. Gebruikt state file (`~/.claude/state/stella-gate-$PPID`) om herhaling te voorkomen. Stale state files worden automatisch opgeruimd (PID check).
2. **Start daemon** als die er niet is (stderr → `~/.claude/logs/stella/daemon-YYYY-MM-DD.log`)
3. **Registreert CLI PID** via `registerCliPid` — daemon kent de sessie maar geeft **geen** akkoord (akkoord moet via MCP gate `stella_howto_akkoord`)
4. **Injecteert reflection keys (1x)** — query `getReflectionKeysForPid`, inject bij eerste keer dat ze beschikbaar zijn (na MCP loaded identity), daarna nooit meer

### Howto Hooks Dispatcher (stella-howto-hooks.sh)

How-to's kunnen Claude Code lifecycle hooks bevatten. Wanneer een howto geladen wordt via `stella_howto_get`, worden de hooks opgeslagen in de daemon. De dispatcher (`stella-howto-hooks.sh`) draait bij elk PreToolUse, PostToolUse en Stop event.

#### Installatie

```bash
ln -s ~/projecten/stella/hooks/stella-howto-hooks.sh ~/.claude/hooks/stella-howto-hooks.sh
```

Registreer in `~/.claude/settings.json` onder `hooks`:

```json
"PreToolUse": [{ "hooks": [{ "type": "command", "command": "~/.claude/hooks/stella-howto-hooks.sh", "timeout": 30 }] }],
"PostToolUse": [{ "hooks": [{ "type": "command", "command": "~/.claude/hooks/stella-howto-hooks.sh", "timeout": 30 }] }],
"Stop": [{ "hooks": [{ "type": "command", "command": "~/.claude/hooks/stella-howto-hooks.sh", "timeout": 30 }] }]
```

#### Flow

```
howto_get → hooks uit DB → daemon.setActiveHooks(slug, hooks, cwd)
                                     ↓
Claude Code event (PreToolUse/PostToolUse/Stop)
  → stella-howto-hooks.sh
  → leest hook_event_name + cwd uit stdin JSON
  → skip als stop_hook_active=true (voorkomt infinite loops)
  → query daemon: getActiveHooksForEvent(cliPid, event, cwd)
  → daemon retourneert hooks met cwd per hook
  → script filtert: skip hook als hook.cwd niet matcht met sessie cwd
  → voert command hooks uit, stuurt prompt/agent hooks als context
  → JSON output: { systemMessage, additionalContext }
                                     ↓
howto_rate → daemon.removeActiveHooks(slug) → hooks weg
```

#### cwd Scoping

Hooks worden alleen uitgevoerd in de juiste project-sessie. Dit voorkomt dat hooks geladen in `~/projecten/stella` vuren in een sessie die in `~/projecten/web-mcp` werkt.

**Hoe het werkt:**
1. Bij `setActiveHooks` slaat de daemon de `cwd` op per hook set
2. Bij `getActiveHooksForEvent` stuurt de daemon de `cwd` mee per hook
3. Het hook script vergelijkt de `cwd` uit Claude Code stdin met `hook.cwd`
4. Fallback: als geen explicit `cwd`, parse het uit `cd <pad>` in het command

**Stop hook specifiek:** `stop_hook_active` wordt gecheckt om infinite loops te voorkomen — als Claude al in een stop-hook continuation zit, wordt de hook overgeslagen.

#### Hook types in howto's

```json
{
  "hooks": {
    "PostToolUse": [{
      "type": "command",
      "command": "npx tsc --noEmit",
      "description": "TypeScript check na elke edit",
      "matcher": "Edit|Write"
    }],
    "Stop": [{
      "type": "prompt",
      "command": "Check of alle bestanden opgeslagen zijn",
      "description": "Save reminder"
    }]
  }
}
```

| Type | Werking |
|------|---------|
| `command` | Shell command uitvoeren, stdout als context |
| `prompt` | Tekst als context naar het model sturen |
| `agent` | Subagent instructie als context sturen |

#### Output

Het script output JSON zodat Claude Code het correct verwerkt:
- `systemMessage` — zichtbaar voor de gebruiker in de CLI
- `additionalContext` — zichtbaar voor het model

### PID Resolution

MCP servers draaien via `doppler run -- node`, dus hun `process.ppid` = doppler PID, niet Claude CLI PID. Dit creëert twee gescheiden PID chains:

```
Hook: $PPID = Claude CLI PID (40973) → registerCliPid(40973) → state aangemaakt (geen akkoord)

MCP server: process.ppid = doppler PID (41105) → register(41105) →
  getOrCreateState(41105) → akkoordGiven=false → gate blokkeert

LLM callt stella_howto_akkoord → setAkkoordGiven() → broadcast naar alle clients
```

**Akkoord**: gaat via `stella_howto_akkoord` tool call (hard enforcement). De `setAkkoordForPid` method bestaat nog voor backward compat maar wordt niet meer door de hook gecalld.

**Active Hooks**: ancestor walk faalt als doppler reparents naar ppid=1. Oplossing: `getActiveHooksForEvent` scant alle cliStates globaal en filtert op `cwd` (project directory) in plaats van PID matching. Het hook script vergelijkt de CLI sessie cwd met de hook's project cwd.

---

## Stella Daemon (Cross-MCP Shared State)

In een Claude Code sessie draaien meerdere MCP servers tegelijk. Zonder sharing zou elke MCP de volledige onboarding afdwingen en plan/howto state niet kunnen zien — dit veroorzaakte deadlocks (bijv. howto_get op adgency + plan_create op stella-proxy → plan gate permanent geblokkeerd).

### Architectuur

De daemon is een detached Node.js process dat communiceert via **JSON-RPC 2.0 over Unix domain socket** (`/tmp/stella-daemon.sock`).

```
MCP Server A ──┐
MCP Server B ──┼──► Unix Socket ──► Stella Daemon (in-memory state)
MCP Server C ──┘         │
                         └──► Broadcast state changes to all clients
```

**Drie componenten:**

| Component | Bestand | Rol |
|-----------|---------|-----|
| `daemon.ts` | Server process | Houdt state in geheugen, handelt RPC requests af, broadcast wijzigingen |
| `daemon-client.ts` | Client library | Verbindt met daemon, local cache + broadcast sync |
| `daemon-spawn.ts` | Auto-start | Controleert of daemon draait, start indien nodig, cleanup stale PID/socket |

### Hoe het werkt

1. **Eerste MCP server start** → `initDaemonClient(scopeId)` → `ensureDaemon()` fork't daemon process
2. **Client registreert** → `register` RPC → ontvangt huidige state snapshot → local cache gesync
3. **State wijzigt** → client stuurt RPC (bijv. `setAkkoordGiven`) → daemon update + broadcast naar alle clients
4. **Broadcasts** → `stateChanged` notifications updaten de local cache van elke client real-time
5. **State lezen** → local cache (geen RPC nodig, instant)

### Gedeelde State

| State | Daemon method | Beschrijving |
|-------|--------------|-------------|
| Akkoord | `setAkkoordGiven` / `isAkkoordGiven` | Basis instructies bevestigd |
| CLI PID (hook) | `registerCliPid` / `getReflectionKeysForPid` | Hook registreert PID (geen akkoord) + query keys per CLI PID |
| Identity | `setIdentityConfirmed` / `isIdentityConfirmed` | Identiteit bevestigd (impliceert akkoord) |
| Plan | `setActivePlan` / `getActivePlan` | Actief plan met stappen |
| Summary | `setSummaryRequired` / `isSummaryRequired` | Summary vereist na plan completion |
| Howto Sessions | `setHowtoSession` / `removeHowtoSession` | Open howto sessies |
| Active Hooks | `setActiveHooks` / `removeActiveHooks` / `getActiveHooksForEvent` | Howto lifecycle hooks (per event, global scan + cwd filter) |
| Step Counter | `incrementStepCounter` / `getStepCounter` | Totaal tool calls (voor evaluatie gate) |
| Reflection | `setPendingReflection` | Reflectie vereist |
| Reflection Keys | `setReflectionKeys` / `getReflectionKeysForPid` | Reflection question keys (pushed door MCP, queried door hook) |
| Bug/Howto Actions | `setPendingBugAction` / `setPendingHowtoAction` | Actie gates |
| Basis Loaded | `setBasisLoaded` / `isBasisLoaded` | Basis howtos geladen |

### Cross-MCP Gate Resolution

| Gate | Logica |
|------|--------|
| Akkoord | `localState.akkoordGiven` (gesync via daemon) → 1x akkoord geldt voor alle MCP's |
| Identity | `localState.identityConfirmed` → 1x identity geldt voor alle MCP's |
| Plan | `localState.activePlan` → plan aangemaakt in 1 MCP is zichtbaar in alle MCP's |
| Summary | `localState.summaryRequired` → blokkeert howto_rate in alle MCP's |
| Howto sessions | `localState.openSessions` → sessie geopend in 1 MCP is zichtbaar overal |

### Flow bij meerdere MCP servers

```
User prompt (1e prompt):
  → stella-gate.sh hook draait
  → Levert volledige instructies (onboarding, stack, naming, howto-schrijfregels)
  → Start daemon als die er niet is (stderr → ~/.claude/logs/stella/)
  → registerCliPid(cliPid) → daemon kent de sessie (GEEN akkoord)
  → Maakt state file aan (~/.claude/state/stella-gate-$PPID)

User prompt (2e+):
  → stella-gate.sh hook draait
  → State file bestaat → geen instructies output
  → registerCliPid(cliPid)
  → Reflection keys: inject 1x zodra beschikbaar, daarna nooit meer

1e MCP tool call (bijv. op snelstart-mcp):
  → initDaemonClient() → daemon al gestart door hook
  → register(dopplerPid) → getOrCreateState() → akkoordGiven=false
  → checkGates() → 🔒 TOOLS BLOCKED → LLM moet stella_howto_akkoord callen
  → stella_howto_akkoord → setAkkoordGiven → broadcast naar alle clients

2e MCP server (bijv. agentrook-mcp):
  → initDaemonClient() → verbindt met bestaande daemon
  → register → ontvangt state met akkoordGiven=true (via broadcast)
  → howto_get → opent sessie → plan gate activeert

3e MCP server (bijv. stella-proxy):
  → plan_create → broadcast naar alle clients
  → agentrook ziet plan via local cache → plan gate passeert (geen deadlock)
```

### Technische details

- **Protocol:** JSON-RPC 2.0, newline-delimited, over Unix domain socket
- **Socket:** `/tmp/stella-daemon.sock`
- **PID file:** `/tmp/stella-daemon.pid`
- **Auto-start:** Eerste `initDaemonClient()` call start daemon via `fork()` (detached)
- **Predictor training:** Elke 6 uur (+ 30s na startup) traint `trainPredictors()`. Bij declining tools (>10% drop, >=3 samples in 7d) stuurt alert via ralph-manager Telegram endpoint. 24h cooldown per tool
- **Auto-exit:** 5 minuten zonder connecties → daemon stopt zichzelf
- **Stale cleanup:** `daemon-spawn.ts` valideert PID file tegen levend process, ruimt stale socket/pid op
- **Retry:** Exponential backoff (50ms, 100ms, 200ms, 400ms) bij wachten op daemon startup
- **RPC timeout:** 5 seconden per call
- **Fallback:** Als daemon onbereikbaar → local-only mode (zelfde gedrag als voor daemon bestond)
- **Backward compat:** `getSharedSession()` retourneert session-achtig object voor code die de oude file-based API gebruikte

### Logging

Alle lokale Stella logs staan in `~/.claude/logs/stella/` (dagelijkse rotatie):

| Bestand | Wie schrijft | Wat |
|---------|-------------|-----|
| `daemon-YYYY-MM-DD.log` | `stella-gate.sh` (daemon stderr) | Registraties, akkoord events, hook changes, errors |
| `hooks-YYYY-MM-DD.log` | `stella-howto-hooks.sh` | Alleen gefirde hooks (lege results worden niet gelogd) |

MCP tool call data (episodes, token usage, plans, summaries) gaat naar Supabase.

### Diagnostics

```bash
# Check of daemon draait
ls -la /tmp/stella-daemon.sock
cat /tmp/stella-daemon.pid
ps aux | grep stella.*daemon

# Ping daemon
echo '{"jsonrpc":"2.0","method":"ping","params":{},"id":1}' | nc -U /tmp/stella-daemon.sock

# Get full state (check akkoordGiven, activePlan, etc.)
echo '{"jsonrpc":"2.0","method":"getState","params":{},"id":1}' | nc -U /tmp/stella-daemon.sock

# Get reflection keys for a CLI PID
printf '{"jsonrpc":"2.0","id":1,"method":"getReflectionKeysForPid","params":{"cliPid":"%s"}}\n' $PPID \
  | nc -U /tmp/stella-daemon.sock -w1

# Bekijk daemon logs van vandaag
cat ~/.claude/logs/stella/daemon-$(date +%Y-%m-%d).log

# Bekijk hook fires van vandaag
cat ~/.claude/logs/stella/hooks-$(date +%Y-%m-%d).log

# Kill daemon (wordt automatisch herstart door hook of MCP call)
kill $(cat /tmp/stella-daemon.pid)
```

---

## Permission System

### Levels

| Level | Gedrag | Menselijke actie |
|-------|--------|------------------|
| `free` | Direct uitvoeren | Geen |
| `confirm` | LLM moet bevestigen (`confirmed=true`) | Geen |
| `approve` | Telegram goedkeuring vereist | Klik op knop |
| `block` | Tool geblokkeerd | - |

### Resolutie-volgorde

```
1. Agent profiel override (als profiel actief + tool match)
2. Supabase stella_tool_permissions tabel
3. Local overrides (hardcoded fallback via configurePermissions)
4. Default: 'free'
```

### Tool Description Decoratie

Bij ListTools worden descriptions automatisch verrijkt:

```
[CONFIRM] Boek inkoopfactuur...
[APPROVE — Telegram] DNS record toevoegen...
[BLOCKED] Gevaarlijke actie...
```

### Agent Profiles

Een agent profiel is een set permission overrides gekoppeld aan een geheime code:

```typescript
// Agent stuurt code mee bij akkoord of via unlockAgentProfile()
const result = await unlockAgentProfile('mijn-geheime-code');
// → Laadt profiel permissions voor de rest van de sessie
```

### Tijdelijke Permissions

Permissions kunnen tijdelijk ingesteld worden met een `expires_at` timestamp. Na afloop valt de tool automatisch terug naar het volgende niveau in de resolutie-volgorde.

```sql
-- Via Ralph Manager MCP:
permission_set scope_id="agentrook" tool_name="*" level="free" duration_hours=4
-- → Na 4 uur verloopt de override en gelden weer de defaults
```

Expired rows worden genegeerd bij resolutie — geen cleanup nodig.

### Database Tabellen

```sql
-- Standaard permission levels per scope
stella_tool_permissions (scope_id, tool_name, level, confirm_message, approve_label, expires_at)
-- expires_at: NULL = permanent, timestamptz = auto-expire

-- Agent profielen
stella_agent_profiles (name, secret_code, scope_id, is_active)

-- Per-profiel permission overrides
stella_agent_profile_permissions (profile_id, scope_id, tool_name, level)

-- Audit trail
stella_permission_audit (scope_id, tool_name, level, agent_profile, result, ...)
```

Migratie: `ralph-manager/supabase/migrations/20260207_stella_permissions.sql`

---

## Inline Parameters

LLMs kunnen reflections, reviews en status updates meesturen bij reguliere tool calls.
Dit bespaart 2 roundtrips per reflectie (geen apart `reflect` → tool opnieuw).

**Belangrijk:** `_reflection` wordt automatisch geïnjecteerd in de `inputSchema` van elke tool via `decorateToolDefinitions()`. LLMs zien het als formele parameter en gebruiken het inline — geen aparte reflect-calls meer nodig.

```typescript
// Reflectie meesturen (AANBEVOLEN — bespaart 2 roundtrips):
tool_name(..., _reflection: {
  answers: [{key: "safety", score: 4, text: "..."}, ...],
  last_step: "wat ik net deed",
  next_step: "wat ik ga doen"
})

// Review meesturen:
tool_name(..., _review: {
  slug: "mijn-howto",
  score: 4,
  outcome: "success",
  review: "werkte goed"
})

// Status meesturen:
tool_name(..., _status: {
  slug: "mijn-howto",
  status: "in_progress",
  remaining_steps: 5
})
```

### Reflection Prompt Optimalisatie

De reflectie-prompt wordt slim gecomprimeerd:

| Moment | Wat wordt getoond | ~Tokens |
|--------|-------------------|---------|
| Step 1 (eerste keer) | Volledige vragen met keys en scores | ~600 |
| Step 2+ (daarna) | `🪞 Analytische Denker \| tool_name \| Step N` | ~15 |
| Hook (2e+ prompt) | Required keys lijst (via daemon query) | ~30 |

De identity context toont nu expliciete key names bij elke vraag:
```
Required keys for _reflection.answers: efficiency, reusable, result, blockers, ...
  1. [key="efficiency"] Was this the fastest route?
  2. [key="reusable"] Can I reuse this?
```

Dit voorkomt dat de AI de verkeerde keys raadt (bijv. "reusability" i.p.v. "reusable").

---

## Tools

### How-To Knowledge

| Tool | Description |
|------|-------------|
| `stella_howto_search` | Semantic search in knowledge base |
| `stella_howto_get` | Get specific how-to by slug |
| `stella_howto_upsert` | Create new how-to or full rewrite. Duplicate check blocks creation if similar howtos exist (bypass with `force=true`) |
| `stella_howto_patch` | Incremental edits: old_text→new_text patches + optional metadata. Saves tokens on large how-tos |
| `stella_howto_rate` | Rate een how-to na gebruik |
| `stella_howto_stats` | Usage en score statistieken |
| `stella_howto_publish` | Toggle public/private |
| `stella_howto_akkoord` | Bevestig basis instructies gelezen |
| `stella_howto_status` | Rapporteer voortgang op actieve sessie |
| `stella_howto_delete` | Permanent verwijderen van howto + versies + usage history |

### Reflection System

| Tool | Description |
|------|-------------|
| `stella_reflect` | Submit een reflectie (met empathy questions) |
| `stella_confirm_identity` | Bevestig identiteit bij sessie start |
| `stella_set_identity` | Wissel actieve identiteit mid-sessie |

### Bug Reports (centralized in ralph-manager)

Bug tools are only mapped in ralph-manager MCP. Other MCPs don't expose these tools — instead, the bug action gate redirects to ralph-manager and offers `howto_status dismiss_bug=true` to unblock.

| Tool | Description |
|------|-------------|
| `stella_report_bug` | Meld een bug of feature request |
| `stella_list_bugs` | Lijst bugs met filters |
| `stella_get_bug` | Bug details ophalen |
| `stella_update_bug` | Bug status updaten |

### Plans & Summaries

| Tool | Description |
|------|-------------|
| `stella_plan_create` | Maak een plan met stappen, gelinkt aan howto sessie(s) |
| `stella_plan_update` | Update stap status (in_progress/completed/skipped) |
| `stella_plan_get` | Haal actief plan op met voortgang |
| `stella_summarize` | Dien samenvatting in na plan completion (met embedding voor semantic search) |

### Project Context

| Tool | Description |
|------|-------------|
| `stella_set_project_context` | Selecteer project, laadt deploy_config, ontgrendelt write tools |

### Batch

| Tool | Description |
|------|-------------|
| `stella_batch_tools` | Meerdere tools in 1 call (telt als 1 reflectie-stap) |

---

## Gate Flow

```
Session Start
  1. Akkoord gate: BLOKKEERT tot stella_howto_akkoord wordt aangeroepen
     Hook levert instructies maar geeft GEEN auto-akkoord — dit forceert
     de volledige learning workflow (search → plan → execute → summarize → rate)
  2. Identity gate: auto-loaded via daemon (eerste MCP call laadt identity)
     Fallback: blokkeert tot stella_confirm_identity

During Work
  3. Evaluation gate: na X tool calls, vereist status report
  4. Summary gate: blokkeert howto_rate tot summary ingediend (na plan completion)
  5. Plan gate: blokkeert non-stella tools tot plan aangemaakt (bij open howto sessie)
  6. Reflection gate: periodiek reflectie vereist
  7. Project context gate: write tools vereisen actief project
  -> Permission gate: free/confirm/approve/block per tool
  -> Auto-update: deploy_config bijwerken na succesvolle writes
  -> Bug action gate: bij probleem, blokkeert tot stella_report_bug (of dismiss_bug)
  -> Howto action gate: bij inzicht, blokkeert tot stella_howto_patch (kleine edits) of stella_howto_upsert (nieuw/rewrite)

After Task
  -> Summary vereist (stella_summarize)
  -> Review vereist (stella_howto_rate of _review)
```

### Plan & Summary Workflow

```
howto_search → howto_get → plan_create → [tools + plan_update] → summarize → howto_rate
```

- **Plan gate**: zodra een howto sessie open is, worden non-stella tools geblokkeerd tot een plan is aangemaakt
- **Summary gate**: zodra een plan completed is, wordt howto_rate geblokkeerd tot een summary is ingediend
- **Disable**: `STELLA_PLAN_GATE=false` in environment, of `configurePlanGate(false)` in code

---

## Howto Visibility: Scope Isolation + Public/Private

Alle projecten delen dezelfde Supabase. Elke howto heeft twee velden die bepalen wie het kan zien:

### `scope_id` — "Van welk project is dit?"

Komt van `LEARNING_SCOPE_ID` env var. Bij upsert krijgt een howto de scope_id van de aanmakende server.

### `is_public` — "Mogen andere scopes dit zien?"

Nieuwe howto's starten als `is_public: true` (public by default). Alleen private maken als het echt project-specifiek is. Quality gates (score >= 4, usage >= 3) gelden voor publishen van private → public.

### Scope Filter Logica

Twee lookup strategieën:

```typescript
// 1. applyScopeFilter (search, stats, basis laden):
// → is_public = true OR scope_id IS NULL OR scope_id = myScopeId
applyScopeFilter(query, includePublic=true)

// 2. findHowtoForEdit (get, upsert, patch, rate, publish, delete):
// → Fetcht ALLE matches: scope_id = myScopeId OR scope_id IS NULL
// → Disambigueert: prefereert eigen scope > public (null) > eerste match
// → Geen maybeSingle() crash bij duplicates
const { data, error } = await findHowtoForEdit(slug);
```

### Wat elke server ziet

| Operatie | Private howto (is_public=false) | Public howto (is_public=true) |
|----------|--------------------------------|-------------------------------|
| `howto_search` | Alleen eigen scope + scope=NULL | Alle scopes |
| `howto_get` | Eigen scope + scope=NULL | Eigen scope + scope=NULL |
| `howto_rate` | Eigen scope + scope=NULL | Eigen scope + scope=NULL |
| `howto_upsert` | Eigen scope + scope=NULL (prefers own) | Eigen scope + scope=NULL (prefers own) |
| `howto_patch` | Eigen scope + scope=NULL (prefers own) | Eigen scope + scope=NULL (prefers own) |
| `howto_publish` | Eigen scope + scope=NULL | Eigen scope + scope=NULL |
| `howto_delete` | Eigen scope + scope=NULL | Eigen scope + scope=NULL |
| Basis laden | Alleen eigen scope | Alleen eigen scope |

### Voorbeeld

```
snelstart-mcp maakt howto "btw-controle" → scope_id="snelstart-mcp", is_public=false
  → adgency kan het NIET zien

howto_publish slug="btw-controle" is_public=true
  → adgency kan het WEL zien via search/get/rate

stella_howto_stats toont: "btw-controle (v3) — ★4.5 (7x) 🌐"  (🌐 = public, 🔒 = private)
```

### Semantic Search (`stella_match_howtos` RPC)

De Supabase RPC functie filtert op `filter_scope_id`:
- Retourneert howto's met `scope_id = myScopeId` OF `scope_id IS NULL` OF `is_public = true`
- Resultaten zijn gerankt op cosine similarity (pgvector embeddings)

---

## Actieve MCP Servers

| Server | Scope ID | Bug tools | Bijzonderheden |
|--------|----------|-----------|----------------|
| ralph-manager | `ralph-manager` | **Ja (centraal)** | `ralph_control` op `approve`, permission tools op `confirm` |
| agentrook | `agentrook` | Nee | Brand management, logo generatie, ad campaigns |
| web-mcp | `web-mcp` | Nee | 15 write tools op `approve` (DNS, domains, Railway, Netlify) + project context gate |
| snelstart-mcp | `<administratie-id>` | Nee | Boekjaar validatie als pre-gate hook |
| sparkbuddy-live | `sparkbuddy` | Nee | Eigen AccessLevel auth (user/admin/superadmin) naast Stella permissions |
| vincifox | `vincifox` | Nee | Howto + reflectie tools |
| stella-proxy | `stella-proxy` | Nee | Howto + reflectie tools + DataForSEO + Supabase |
| herald-mcp | `herald` | Nee | Howto + reflectie tools + CRM/email |

---

## Project Structure

```
stella/
  hooks/
    stella-gate.sh        → UserPromptSubmit hook (symlinked from ~/.claude/hooks/)
                            Levert instructies (1e prompt only), start daemon, registreert CLI PID, injecteert reflection keys (1x)
    stella-howto-hooks.sh → Pre/PostToolUse + Stop hook (symlinked from ~/.claude/hooks/)
                            Query daemon voor actieve howto hooks, voert ze uit, logt naar ~/.claude/logs/stella/
  src/
    index.ts              → Exports, stellaTools, getStellaHandler
    gates.ts              → withGates(), checkGates(), gate orchestration (7+ gates)
    daemon.ts             → Daemon server process (JSON-RPC over Unix socket)
    daemon-client.ts      → Daemon client (connect, local cache, broadcast sync)
    daemon-spawn.ts       → Auto-start daemon (fork, PID check, stale cleanup)
    plans.ts              → plan_create, plan_update, plan_get, summarize
    howtos.ts             → howto tools + session management + evaluation gate + hook registration
    claude-hooks.ts       → Hook normalisation, Claude Code config generation, summarize
    chat-sessions.ts      → Chat session tracking
    reflections.ts        → reflectie systeem + identity gates + reflection key push
    permissions.ts        → configurePermissions(), checkPermission(), resolvePermission()
    project-context.ts    → Project context gate, deploy_config auto-update
    bugreports.ts         → Bug report tools (centralized in ralph-manager)
    projects.ts           → Project management tools
    tasks.ts              → Task management tools
    batch.ts              → stella_batch_tools (meerdere tools in 1 call)
    session.ts            → MCP session ID management
    embedding.ts          → OpenAI embedding generation (text-embedding-3-small)
    token-tracker.ts      → Token usage tracking per session
    token-stats.ts        → stella_token_stats, stella_token_top_sessions/tools
    howto-analytics.ts    → stella_howto_analytics (usage stats)
    date-params.ts        → Date range parameter parsing
    tool-names.ts         → Tool name resolution (stella_* ↔ host names)
    supabase-client.ts    → Supabase connection, scope filtering, requireLearning
    types.ts              → TypeScript types, helpers, constants
    constants.ts          → Tool names, default config values
  src/neural/
    index.ts              → Re-exports for all neural modules
    window-extractor.ts   → EpisodeWindow types, channel key extraction, top-K, novelty/mismatch
    coactivation.ts       → Hebbian co-activation processor (pairs that fire together → edge strengthening)
    neighborhood.ts       → BFS graph traversal, Jaccard similarity, decision tracing
    bundle-detector.ts    → Clique detection + promotion to bundle nodes
    policy-engine.ts      → PolicyEngine class, heuristic + Thompson Sampling, StateOut
    predictors.ts         → Tool health predictors: time-windowed trends (24h/7d/30d), outcome-based success rates, trend detection, PredictorStats/ToolTrend exports
  src/store/
    daemon-store-client.ts → DaemonStoreClient — socket proxy to daemon + graph proxy
  tests/
    daemon-e2e.mjs        → 50 tests: lifecycle, state, multi-client, plans, howtos
    daemon-client-e2e.mjs → 24 tests: auto-spawn, fallback, API functions
  dist/                   → Compiled JavaScript (npm package output)
  package.json            → @soulbatical/stella
```

---

## Build & Test

```bash
# Build
npm run build

# Run daemon tests (74 tests)
node tests/daemon-e2e.mjs
node tests/daemon-client-e2e.mjs

# After stella changes: rebuild consumers
cd ~/projecten/web-mcp && npm run build
cd ~/projecten/snelstart-mcp/mcp && npm run build
cd ~/projecten/ralph-manager/backend-mcp && npm run build
cd ~/projecten/adgency/backend-mcp && npm run build
```

---

## License

MIT