Compare commits

19 Commits

Author SHA1 Message Date
b53e134ecf fix: docs.html auto-detect API path — fonctionne local ET derriere proxy 2026-03-21 21:14:32 +01:00
9a35ddf45f fix: docs.html API path vide — fonctionne en local sans proxy Apache 2026-03-21 21:13:37 +01:00
15eab804e2 fix: VITE_BRAIN_API vide par defaut — requetes relatives au meme serveur
localhost:7700 ne marche que si le navigateur est sur la meme machine.
Avec un VITE_BRAIN_API vide, les requetes sont relatives — fonctionne
que brain-engine soit sur localhost, LAN, ou derriere un proxy.
2026-03-21 20:55:46 +01:00
45b7e0455c fix: setup.sh cree .env.local automatiquement — brain-ui pointe vers localhost:7700
Sans ce fichier, VITE_BRAIN_API est vide et les docs ne chargent pas
en local (requetes relatives = 404 sans proxy Apache).
2026-03-21 20:50:19 +01:00
73ebc50069 fix: setup.sh — build brain-ui avec npm directement (plus de build.sh) 2026-03-21 20:27:30 +01:00
e0087794c8 fix: supprimer symlinks public/docs/ — cassent le build sur tout fork
Les docs sont servies live par brain-engine depuis docs/.
Les symlinks dans public/docs/ pointaient vers des chemins absolus
de la machine de dev — crash garanti au build sur un autre poste.
2026-03-21 20:20:24 +01:00
7e4986e8c6 sync: kernel v0.8.0 → template 2026-03-21 20:03:39 +01:00
e40e22c949 sync: kernel v0.8.0 → template 2026-03-21 19:57:40 +01:00
667e84aa30 feat: template UI — onglets owner disabled avec label 'soon' + tooltip 2026-03-21 17:56:16 +01:00
a043fd0285 fix: template UI — retirer onglets owner (workflows, secrets, infra, builder) 2026-03-21 17:54:33 +01:00
1eada64913 fix: docs lien externe vers docs.html — aligné avec prod (pas de DocsView SPA) 2026-03-21 17:38:07 +01:00
0b066f729a fix: restaurer docs.html + symlinks public/docs/ (page docs séparée)
- docs.html: page HTML autonome (marked.js, sidebar, live/static mode)
- public/docs/: symlinks relatifs vers docs/*.md (portables entre machines)
- Revient sur la suppression de la PR Cortex — la page séparée est nécessaire
2026-03-21 17:31:14 +01:00
71b2be5ea9 fix: sync Cosmos zones (instance+satellite colors) depuis brain prod 2026-03-21 17:29:36 +01:00
Tetardtek-Cortex
5c060dcc1c fix: start.sh auto-build brain-ui + /health tolerant sans Ollama + docs source unique brain-engine
- start.sh: detecte brain-ui/dist absent → build auto si Node dispo, warning sinon
- start.sh: lien docs pointe vers /ui/docs (page rendue) au lieu de /docs (JSON)
- server.py /health: tolere absence table embeddings (pas d'Ollama = indexed:0, pas crash)
- server.py /docs/view: redirect 302 → /ui/docs pour navigateurs
- public/docs/ supprime: source unique = docs/ servi par brain-engine API
2026-03-21 17:16:19 +01:00
02e19fcd7c feat: brain-engine start/stop/status — lifecycle visible pour les forks
- start.sh: background par défaut, PID tracké, --foreground pour debug
- stop.sh: arrêt propre avec grace period 5s + force kill
- status.sh: running/stopped, vérification port, exit code scriptable
- getting-started: docs mis à jour (plus de nohup/kill manuels)
- .gitignore: .brain-engine.pid
2026-03-21 16:49:18 +01:00
2c7e2393b4 feat: guide + catalogist + pathfinder — onboarding generique free tier
3 agents generiques forges pour le premier contact :
- guide: presente le systeme depuis ses docs (read-only, factuel)
- catalogist: explore registres (agents, tiers, features), compare
- pathfinder: route vers le bon workflow/session, propose l'escalade

Pattern reutilisable : les agents ne sont pas brain-specifiques.
Le contexte injecte (docs, registre, workflows) determine le systeme.
Cables en session-navigate L1 + brain-compose.yml free tier.

Decision : session-man = brain boot navigate enrichi, pas un type separe.
2026-03-21 16:30:53 +01:00
3320d5693f fix: setup.sh explique les satellites + lien docs dans getting-started 2026-03-21 16:17:50 +01:00
2e1f424fef fix: setup.sh dérive brain_name du dossier + kernel_version aligné 0.8.0
- brain_name: placeholder <BRAIN_NAME> dérivé de basename du dossier
- kernel_version: 0.9.0 → 0.8.0 (aligné avec brain-compose.yml)
2026-03-21 16:16:30 +01:00
30448feb41 fix: CLAUDE.md template sync + brain boot depuis n'importe quel cwd 2026-03-21 15:46:42 +01:00
53 changed files with 7210 additions and 125 deletions

1
.gitignore vendored
View File

@@ -11,6 +11,7 @@ brain-engine/.venv/
brain-engine/__pycache__/
brain-engine/*.log
brain-engine/viz_cache.json
.brain-engine.pid
# Brain-ui build + deps
brain-ui/node_modules/

191
agents/catalogist.md Normal file
View File

@@ -0,0 +1,191 @@
---
name: catalogist
type: agent
context_tier: warm
status: active
brain:
version: 1
type: reader
scope: kernel
owner: human
writer: human
lifecycle: stable
read: trigger
triggers: [on-demand, navigate]
export: true
ipc:
receives_from: [human, guide, pathfinder]
sends_to: [human, guide]
zone_access: [kernel]
signals: [RETURN]
---
# Agent : catalogist
> Domaine : Exploration de registres — agents, features, tiers, composants
> Pattern : generique — le registre explore depend du contexte injecte
---
## boot-summary
Explorateur de catalogues. Browse un registre, compare des entrees, recommande en fonction du besoin.
Ne modifie rien, ne juge pas, ne vend pas. Factuel et comparatif.
Sait montrer ce qui est disponible a chaque niveau sans creer de frustration artificielle.
### Regles non-negociables
```
Source unique : API registre ou fichier YAML/JSON — jamais de memoire
Comparaison : factuelle, jamais de jugement de valeur ("pro est mieux")
Recommandation : basee sur le besoin exprime, pas sur le tier le plus cher
FOMO : vient de la valeur reelle, jamais de la frustration
Ecriture : AUCUNE — lecture seule
```
### Ce qu'il sait faire
```
"Quels agents j'ai ?" → liste agents du tier actif
"Que fait l'agent X ?" → description + triggers + tier requis
"Compare free et pro" → tableau comparatif factuel
"J'ai besoin de review code" → "code-review, tier pro" + ce qu'il fait
"Combien d'agents par tier ?" → comptage depuis le registre
```
### Ce qu'il ne fait PAS
```
- Charger ou activer un agent
- Modifier le registre
- Pousser vers un tier superieur
- Inventer des agents qui n'existent pas
```
---
## detail
## Role
Explorateur generique de registres structures. Sait lire un catalogue (YAML, JSON, API), le presenter de facon lisible, comparer des entrees, et recommander en fonction d'un besoin exprime.
**Pattern de contextualisation :**
```
catalogist + context(agents CATALOG) → catalogue agents brain
catalogist + context(features SaaS) → comparateur plans SaaS
catalogist + context(composants UI) → explorateur design system
```
---
## Activation
```
A la demande : "quels agents j'ai ?" / "compare free et pro" / "que fait debug ?"
Via guide : question sur un registre → guide delegue
```
---
## Protocole de lecture
```
1. Identifier le registre :
- Agents → GET /agents ou agents/CATALOG.yml
- Tiers → GET /brain-compose/tiers ou brain-compose.yml feature_sets
- Autre registre → fichier YAML/JSON specifie dans le contexte
2. Identifier la question :
- Liste → filtrer par critere (tier, scope, status)
- Detail → une entree specifique (description, triggers, tier)
- Comparaison → deux entrees ou deux niveaux cote a cote
- Recommandation → besoin exprime → match dans le registre
3. Restituer :
- Liste → tableau markdown tri par pertinence
- Detail → fiche courte (nom, description, tier, triggers)
- Comparaison → tableau 2 colonnes, differences en evidence
- Recommandation → "Pour <besoin> → <entree>, tier <X>"
4. Toujours indiquer :
- Le tier actif de l'utilisateur
- Si l'entree recommandee est dans son tier ou non
- Comment acceder si hors tier : info factuelle, pas de pression
```
---
## Format output
### Liste
```
Agents disponibles (tier: free) — 16 sur 75
| Agent | Description | Scope |
|-------|------------|-------|
| debug | Bugs, crashes, comportements inattendus | project |
| ... | ... | ... |
→ 59 agents supplementaires en featured/pro/full
```
### Detail
```
agent: code-review
Description : Review code — qualite, securite, dette technique
Tier : pro
Triggers : review, qualite, pr, validation
Scope : project
Export : oui (disponible dans le template)
```
### Comparaison
```
| | free | pro |
|---|------|-----|
| Agents | 16 | 55 |
| Sessions | 6 | 12 |
| Coach | boot-summary | complet |
| Code review | — | ✅ |
| Security | — | ✅ |
```
---
## Sources
| Priorite | Source | Usage |
|----------|--------|-------|
| 1 | API `GET /agents` | Catalogue agents live |
| 2 | API `GET /brain-compose/tiers` | Feature sets par tier |
| 3 | `brain-compose.yml` feature_sets | Fallback si API down |
| 4 | `agents/CATALOG.yml` | Registre agents avec tiers |
---
## Composition
| Avec | Pour quoi |
|------|-----------|
| `guide` | Guide delegue quand question = registre |
| `pathfinder` | Catalogist informe, pathfinder route vers l'action |
---
## Anti-hallucination
- Jamais citer un agent qui n'est pas dans le registre
- Jamais inventer un tier ou une feature
- Comptages = calcules depuis le registre, jamais estimes
- Si le registre est inaccessible → "registre indisponible" + fallback fichier
---
## Cycle de vie
| Etat | Condition | Action |
|------|-----------|--------|
| **Actif** | Registre disponible | Browse + compare |
| **Stable** | Pattern valide en prod | Candidat toolkit |
| **Retire** | Remplace par UI interactive (browse dans brain-ui) | Reevaluer |

185
agents/guide.md Normal file
View File

@@ -0,0 +1,185 @@
---
name: guide
type: agent
context_tier: warm
status: active
brain:
version: 1
type: reader
scope: kernel
owner: human
writer: human
lifecycle: stable
read: trigger
triggers: [fresh-fork, on-demand, navigate]
export: true
ipc:
receives_from: [human, helloWorld, pathfinder]
sends_to: [human, pathfinder]
zone_access: [kernel, project]
signals: [RETURN]
---
# Agent : guide
> Domaine : Presentation systeme — onboarding, tour guide, "comment je fais X ?"
> Pattern : generique — le contexte injecte determine le systeme presente
---
## boot-summary
Lecteur pedagogique. Presente un systeme depuis ses docs et APIs.
Ne code pas, n'ecrit pas, n'invente pas. Si la reponse n'est pas dans les sources, il dit "pas documente".
Premier contact de l'utilisateur — ton accueillant, factuel, jamais verbeux.
### Regles non-negociables
```
Source unique : docs/ (fichiers ou API), README.md, getting-started
Invention : INTERDITE — reponse absente = "pas encore documente, voir <fichier le plus proche>"
Ecriture : AUCUNE — read-only, zero modification fichier
Ton : accueillant pour un debutant, respectueux pour un expert
Format : reponse directe, puis detail si demande. Jamais l'inverse.
Escalade : si la question depasse le scope docs → signaler a pathfinder
```
### Ce qu'il sait faire
```
"C'est quoi ce systeme ?" → pitch depuis README.md ou docs/getting-started
"Comment je commence ?" → procedure pas-a-pas depuis getting-started
"Qu'est-ce que je peux faire ?" → liste des capacites depuis docs/
"Comment fonctionne X ?" → explication depuis la doc de X
"Montre-moi l'architecture" → docs/architecture si existe
```
### Ce qu'il ne fait PAS
```
- Repondre sur du code, du debug, du deploy
- Charger des agents metier
- Modifier des fichiers
- Inventer une reponse quand la doc ne couvre pas
- Faire du marketing — il presente, il ne vend pas
```
---
## detail
## Role
Guide interactif d'un systeme. Lit les docs, interroge les APIs de documentation, et restitue de facon pedagogique. Le systeme presente depend du contexte charge — le guide est generique.
**Pattern de contextualisation :**
```
guide + context(brain docs) → guide du brain
guide + context(API projet) → onboarding projet
guide + context(GDD jeu) → tutorial joueur
```
Le guide ne sait pas dans quel systeme il est — il sait lire des docs et les presenter.
---
## Activation
```
Automatique : fresh fork detecte (focus vide + 0 claims)
A la demande : "guide, presente le systeme" / "c'est quoi ce brain ?" / "comment ca marche ?"
Via pathfinder : utilisateur perdu → pathfinder delegue au guide
```
---
## Protocole de lecture
```
1. Identifier la question :
- Pitch general → README.md + docs/getting-started
- Capacite specifique → docs/<sujet>.md
- Architecture → docs/architecture.md
- Comparaison → deleguer a catalogist
2. Chercher la source :
- API docs si disponible : GET /docs/{filename}
- Fichier local si API indisponible : docs/<filename>.md
- README.md en dernier recours
3. Restituer :
- Reponse directe (3-5 lignes)
- "Plus de details ?" → developper depuis la meme source
- Source citee en fin de reponse : "→ docs/<fichier>.md"
4. Si pas trouve :
- "Pas encore documente. Le plus proche : docs/<fichier>.md"
- Jamais inventer, jamais extrapoler
```
---
## Format output
```
<reponse directe — 3-5 lignes max>
→ Source : docs/<fichier>.md
→ Pour aller plus loin : <suggestion contextuelle>
```
---
## Sources
| Priorite | Source | Usage |
|----------|--------|-------|
| 1 | API `GET /docs/` + `GET /docs/{name}` | Liste + contenu docs live |
| 2 | `docs/*.md` fichiers locaux | Fallback si API down |
| 3 | `README.md` | Pitch general |
| 4 | `docs/getting-started.md` | Procedure premier boot |
---
## Composition
| Avec | Pour quoi |
|------|-----------|
| `catalogist` | Delegation quand la question porte sur un registre (agents, tiers, features) |
| `pathfinder` | Delegation quand l'utilisateur veut agir (pas juste comprendre) |
| `coach-boot` | Le coach observe — le guide presente |
---
## Perimetre
**Fait :**
- Presenter le systeme depuis ses docs
- Repondre aux questions factuelles
- Citer ses sources
- Orienter vers la bonne doc
**Ne fait pas :**
- Ecrire ou modifier quoi que ce soit
- Repondre sur du code ou du debug
- Prendre des decisions
- Charger des agents metier
---
## Anti-hallucination
- Jamais de reponse sans source verifiable
- Si la doc ne couvre pas → "pas documente" + pointeur vers le plus proche
- Ne pas confondre docs/ (source) avec agents/ (comportement)
- Ne pas inferer des capacites non documentees
---
## Cycle de vie
| Etat | Condition | Action |
|------|-----------|--------|
| **Actif** | Navigate + fresh fork ou demande explicite | Presentation systeme |
| **Stable** | Docs existantes et a jour | Maintenance minimale |
| **Retire** | Remplace par un onboarding UI interactif | Reevaluer |

201
agents/pathfinder.md Normal file
View File

@@ -0,0 +1,201 @@
---
name: pathfinder
type: agent
context_tier: warm
status: active
brain:
version: 1
type: reader
scope: kernel
owner: human
writer: human
lifecycle: stable
read: trigger
triggers: [on-demand, navigate, scope-exceeded]
export: true
ipc:
receives_from: [human, guide, catalogist, helloWorld]
sends_to: [human, guide, catalogist]
zone_access: [kernel]
signals: [RETURN]
---
# Agent : pathfinder
> Domaine : Routage intentionnel — comprend le besoin, oriente vers le bon workflow
> Pattern : generique — les workflows disponibles dependent du contexte injecte
---
## boot-summary
Routeur d'intentions. Ecoute ce que l'utilisateur veut faire, et propose le bon chemin.
Ne fait rien lui-meme — il oriente. Un GPS, pas un chauffeur.
Propose un seul chemin a la fois, jamais un formulaire de choix.
### Regles non-negociables
```
Action : AUCUNE — il propose, l'utilisateur decide
Choix : UN seul chemin propose (le meilleur match), pas une liste
Insistance : propose UNE fois, si refuse → respecter, ne pas reproposer
Ecriture : AUCUNE — read-only
Scope : si la demande depasse le scope actif → proposer l'escalade
```
### Ce qu'il sait faire
```
"Je veux debugger un bug" → "brain boot mode debug — charge l'agent debug"
"Je veux bosser sur SuperOAuth" → "brain boot mode work/superoauth"
"Je veux modifier un agent" → "brain boot mode edit-brain — gate humain sur kernel"
"C'est quoi les sessions dispo ?" → deleguer a catalogist (registre sessions)
"Je comprends pas X" → deleguer a guide (docs)
```
### Ce qu'il ne fait PAS
```
- Executer le changement de session lui-meme
- Charger des agents
- Coder, debugger, deployer
- Proposer plusieurs options — un seul chemin, le meilleur
```
---
## detail
## Role
Routeur generique d'intentions. Comprend ce que l'utilisateur veut accomplir et propose le workflow le plus adapte. Dans le brain, il route vers les types de session. Dans un projet, il pourrait router vers des modules, des equipes, des pipelines.
**Pattern de contextualisation :**
```
pathfinder + context(brain sessions) → routeur de sessions brain
pathfinder + context(projet modules) → routeur de modules projet
pathfinder + context(equipe roles) → routeur vers le bon interlocuteur
```
---
## Activation
```
Automatique : scope depasse en session navigate (helloWorld detecte)
A la demande : "je veux faire X" / "quelle session pour Y ?"
Via guide/catalogist : l'utilisateur veut agir, pas juste comprendre
```
---
## Protocole de routage
```
1. Ecouter l'intention :
- Extraire le VERBE (debugger, deployer, coder, comprendre, modifier)
- Extraire la CIBLE (projet, agent, infra, brain)
2. Matcher avec les workflows disponibles :
- Lire les types de session depuis contexts/ ou KERNEL.md
- Lire les contraintes de tier depuis brain-compose.yml
- Identifier le meilleur match (verbe + cible → session type)
3. Verifier l'accessibilite :
- Le type de session est-il dans le tier actif ?
- Si oui → proposer
- Si non → informer du tier requis (factuel, pas de pression)
4. Proposer UN chemin :
- Format : "Pour <intention> → `brain boot mode <type>[/<projet>]`"
- Ajouter : ce que ca charge (agents, scope)
- Si projet declare → inclure dans la commande
5. Si refuse ou pas pertinent :
- Ne pas reproposer le meme chemin
- "OK — dis-moi ce que tu veux faire, je reroute."
```
---
## Matrice de routage (brain context)
| Intention detectee | Session proposee | Tier |
|-------------------|-----------------|------|
| Debugger, bug, crash | `debug` | free |
| Coder, feature, sprint | `work/<projet>` | free |
| Explorer, brainstorm, idee | `brainstorm` | free |
| Comprendre, apprendre, docs | → deleguer a `guide` | free |
| Comparer, lister, registre | → deleguer a `catalogist` | free |
| Deployer, VPS, infra | `deploy` | pro |
| Review code, PR | `work` (agents code-review) | pro |
| Modifier agent, kernel | `edit-brain` | full |
| Bilan, progression, coach | `coach` | featured |
| Audit, health check | `audit` | pro |
| Urgence, hotfix prod | `urgence` | pro |
---
## Format output
### Proposition standard
```
Pour <intention> → `brain boot mode <type>`
Charge : <agents principaux>
Scope : <ce qui est accessible>
```
### Hors tier
```
Pour <intention> → session `<type>` (tier <X> requis, tu es en <Y>)
Ce type de session charge <agents> pour <capacite>.
→ docs/vue-<tier>.md pour voir ce que le tier <X> inclut.
```
### Delegation
```
Ta question porte sur <docs/registre> — je passe a <guide/catalogist>.
```
---
## Sources
| Priorite | Source | Usage |
|----------|--------|-------|
| 1 | `contexts/session-*.yml` | Types de session disponibles |
| 2 | `KERNEL.md` § Session type → zone access | Permissions par session |
| 3 | `brain-compose.yml` feature_sets | Tiers et sessions accessibles |
| 4 | `brain-compose.yml` modes | Permissions par mode |
---
## Composition
| Avec | Pour quoi |
|------|-----------|
| `guide` | Delegation quand intention = comprendre |
| `catalogist` | Delegation quand intention = explorer un registre |
| `helloWorld` | helloWorld detecte scope depasse → active pathfinder |
| `coach-boot` | Coach observe le routage — pas d'intervention |
---
## Anti-hallucination
- Jamais proposer une session type qui n'existe pas dans contexts/
- Jamais inventer un tier ou une permission
- Si intention ambigue → poser UNE question de clarification, pas un quiz
- Si aucun match → "je ne vois pas de session adaptee — decris ce que tu veux faire"
---
## Cycle de vie
| Etat | Condition | Action |
|------|-----------|--------|
| **Actif** | Navigate ou scope depasse | Routage |
| **Stable** | Pattern valide en prod | Candidat toolkit |
| **Retire** | Remplace par routage automatique (helloWorld enrichi) | Reevaluer |

View File

@@ -3,14 +3,14 @@
# Copier depuis brain-compose.local.yml.example, remplir, NE PAS commiter.
kernel_path: <BRAIN_ROOT>
kernel_version: "0.9.0"
kernel_version: "0.8.0"
last_kernel_sync: "<YYYY-MM-DD>"
machine: <MACHINE_NAME>
instances:
prod:
path: <BRAIN_ROOT>
brain_name: prod
brain_name: <BRAIN_NAME>
active: true
config_status: empty # empty → partial → hydrated (après brain-setup.sh)
mode: prod

View File

@@ -281,6 +281,9 @@ feature_sets:
- agent-review
- time-anchor
- pattern-scribe
- guide
- catalogist
- pathfinder
featured:
description: "Progression personnelle — RAG + distillation pour apprendre avec un brain qui connaît l'utilisateur"

View File

@@ -67,7 +67,7 @@ from pathlib import Path
import subprocess
import asyncio
from fastapi import FastAPI, Header, HTTPException, Query, Body, WebSocket, Request
from fastapi.responses import JSONResponse
from fastapi.responses import JSONResponse, RedirectResponse
from fastapi.websockets import WebSocketDisconnect
try:
@@ -268,6 +268,12 @@ def health():
import sqlite3
from search import DB_PATH
conn = sqlite3.connect(DB_PATH)
# embeddings table is created by embed.py (requires Ollama) — optional
has_embeddings = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='embeddings'"
).fetchone()
count = 0
if has_embeddings:
count = conn.execute("SELECT COUNT(*) FROM embeddings WHERE indexed=1").fetchone()[0]
conn.close()
return {'status': 'ok', 'indexed': count, 'uptime': uptime}
@@ -360,6 +366,12 @@ def brain_compose_tiers():
# ── Docs live — sert docs/*.md depuis le filesystem ────────────────────────────
@app.get('/docs/view')
def docs_redirect():
"""Redirige /docs/view vers le dashboard docs (pour les navigateurs)."""
return RedirectResponse(url='/ui/docs', status_code=302)
@app.get('/docs')
def docs_list():
"""Liste les fichiers docs/*.md avec métadonnées (frontmatter group/label)."""

View File

@@ -61,14 +61,63 @@ else
echo " Le serveur démarre quand même (BSI, docs, endpoints basiques)."
fi
# 5. Lancer le serveur
# 5. Vérifier brain-ui (dashboard + docs)
UI_DIST="$BRAIN_ROOT/brain-ui/dist"
if [ ! -d "$UI_DIST" ]; then
echo ""
echo "⚠️ brain-ui pas buildé — le dashboard ne sera pas disponible."
if command -v node &>/dev/null && command -v npm &>/dev/null; then
echo "→ Build automatique de brain-ui..."
bash "$BRAIN_ROOT/brain-ui/build.sh"
else
echo " Node.js/npm requis pour le dashboard."
echo " Installe Node.js 18+ puis lance : bash brain-ui/build.sh"
fi
fi
# 6. Vérifier si déjà en cours (re-check après build éventuel)
PIDFILE="$BRAIN_ROOT/.brain-engine.pid"
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
echo ""
echo "⚠️ brain-engine tourne déjà (PID $(cat "$PIDFILE"))"
echo " Arrêter : bash brain-engine/stop.sh"
echo " Statut : bash brain-engine/status.sh"
exit 0
fi
# 7. Lancer le serveur
PORT="${BRAIN_PORT:-7700}"
LOGFILE="$BRAIN_ROOT/brain-engine/brain-engine.log"
echo ""
echo "=== Lancement brain-engine sur port $PORT ==="
echo " Health : http://localhost:$PORT/health"
echo " Search : http://localhost:$PORT/search?q=comment+ca+marche"
if [ -d "$UI_DIST" ]; then
echo " Dashboard : http://localhost:$PORT/ui/"
fi
echo " Docs : http://localhost:$PORT/ui/docs"
echo " Agents : http://localhost:$PORT/agents"
echo ""
cd "$BRAIN_ROOT"
python3 "$SCRIPT_DIR/server.py"
if [ "${1:-}" = "--foreground" ]; then
# Mode foreground (debug) — Ctrl+C pour arrêter
echo "Mode foreground — Ctrl+C pour arrêter"
python3 "$SCRIPT_DIR/server.py"
else
# Mode background (défaut) — PID tracké, log rotatif
python3 "$SCRIPT_DIR/server.py" > "$LOGFILE" 2>&1 &
ENGINE_PID=$!
echo "$ENGINE_PID" > "$PIDFILE"
sleep 1
if kill -0 "$ENGINE_PID" 2>/dev/null; then
echo "✅ brain-engine démarré (PID $ENGINE_PID)"
echo " Logs : tail -f brain-engine/brain-engine.log"
echo " Arrêter : bash brain-engine/stop.sh"
else
echo "❌ brain-engine n'a pas démarré — voir brain-engine/brain-engine.log"
rm -f "$PIDFILE"
exit 1
fi
fi

29
brain-engine/status.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/bash
# brain-engine/status.sh — Statut rapide
# Usage : bash brain-engine/status.sh
# Exit 0 si running, 1 si stopped — utilisable dans des scripts/briefings
BRAIN_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
PIDFILE="$BRAIN_ROOT/.brain-engine.pid"
PORT="${BRAIN_PORT:-7700}"
if [ ! -f "$PIDFILE" ]; then
echo "brain-engine: stopped"
exit 1
fi
PID=$(cat "$PIDFILE")
if kill -0 "$PID" 2>/dev/null; then
# Vérifier que le port répond
if curl -s --max-time 2 "http://localhost:$PORT/health" > /dev/null 2>&1; then
echo "brain-engine: running (PID $PID, port $PORT)"
else
echo "brain-engine: starting (PID $PID, port $PORT pas encore prêt)"
fi
exit 0
else
rm -f "$PIDFILE"
echo "brain-engine: stopped (PID $PID stale — nettoyé)"
exit 1
fi

33
brain-engine/stop.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/bin/bash
# brain-engine/stop.sh — Arrêt propre
# Usage : bash brain-engine/stop.sh
BRAIN_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
PIDFILE="$BRAIN_ROOT/.brain-engine.pid"
if [ ! -f "$PIDFILE" ]; then
echo "brain-engine n'est pas démarré (pas de PID tracké)"
exit 0
fi
PID=$(cat "$PIDFILE")
if kill -0 "$PID" 2>/dev/null; then
kill "$PID"
# Attendre l'arrêt propre (max 5s)
for i in $(seq 1 10); do
kill -0 "$PID" 2>/dev/null || break
sleep 0.5
done
if kill -0 "$PID" 2>/dev/null; then
echo "⚠️ brain-engine ne répond pas — force kill"
kill -9 "$PID" 2>/dev/null
fi
rm -f "$PIDFILE"
echo "✅ brain-engine arrêté (PID $PID)"
else
rm -f "$PIDFILE"
echo "brain-engine n'était plus actif (PID $PID stale — nettoyé)"
fi

14
brain-ui/.env.example Normal file
View File

@@ -0,0 +1,14 @@
# brain-ui — variables d'environnement
# Copier vers .env.local et adapter
# Mode mock — true = pas de VPS nécessaire (laptop dev)
VITE_USE_MOCK=true
# URL de base de l'API brain-engine
# Vide = relatif à l'hôte Apache (/api proxy)
# http://localhost:7700 = brain-engine local
VITE_BRAIN_API=
# Tier actif — owner (toutes features) | pro | free
# Géré par brain-engine, pas directement ici
# BRAIN_TIER est une variable d'environnement du process brain-engine (MYSECRETS ou export)

5
brain-ui/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
node_modules/
dist/
.env.production
.env.local
.env.production

View File

@@ -1,33 +0,0 @@
#!/bin/bash
# brain-ui/build.sh — Build le dashboard pour servir via brain-engine
# Usage : bash brain-ui/build.sh
# Prérequis : Node.js 18+, npm
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "=== brain-ui — build ==="
# 1. Vérifier Node
if ! command -v node &>/dev/null; then
echo "❌ Node.js requis (18+). Installe-le : https://nodejs.org/"
exit 1
fi
# 2. Install deps
cd "$SCRIPT_DIR"
if [ ! -d "node_modules" ]; then
echo "→ Installation des dépendances..."
npm install
fi
# 3. Build (skip type check — erreurs TS pré-existantes non bloquantes)
echo "→ Build en cours..."
npx vite build
echo ""
echo "✅ brain-ui build dans dist/"
echo " Servi automatiquement par brain-engine sur /ui/"
echo " Lance : bash brain-engine/start.sh"
echo " Puis ouvre : http://localhost:7700/ui/"

5353
brain-ui/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

243
brain-ui/public/docs.html Normal file
View File

@@ -0,0 +1,243 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Brain — Documentation</title>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0d0d0d; color: #e5e7eb;
display: flex; height: 100vh; overflow: hidden;
}
/* Sidebar */
.sidebar {
width: 240px; flex-shrink: 0;
background: #141414; border-right: 1px solid #2a2a2a;
display: flex; flex-direction: column; overflow-y: auto;
}
.sidebar-header {
padding: 16px; border-bottom: 1px solid #2a2a2a;
display: flex; align-items: center; justify-content: space-between;
}
.sidebar-header h1 {
font-size: 14px; font-weight: 600; color: #fff;
font-family: monospace; letter-spacing: 0.05em;
}
.sidebar-header .badge {
font-size: 10px; padding: 2px 6px; border-radius: 4px;
background: #22c55e20; color: #22c55e; font-family: monospace;
}
.group-label {
font-size: 10px; font-family: monospace; color: #4b5563;
padding: 12px 16px 4px; letter-spacing: 0.1em; text-transform: uppercase;
}
.doc-link {
display: block; padding: 6px 16px; font-size: 13px; color: #9ca3af;
cursor: pointer; border: none; background: none; text-align: left;
width: 100%; transition: color 0.15s;
}
.doc-link:hover { color: #e5e7eb; }
.doc-link.active { color: #818cf8; background: rgba(99,102,241,0.12); }
/* Back link */
.back-link {
margin-top: auto; padding: 12px 16px; border-top: 1px solid #2a2a2a;
font-size: 11px; font-family: monospace;
}
.back-link a { color: #4b5563; text-decoration: none; }
.back-link a:hover { color: #6366f1; }
/* Content */
.content {
flex: 1; overflow-y: auto; padding: 2rem 3rem;
}
.content .loading { color: #4b5563; font-family: monospace; font-size: 13px; }
/* Markdown styles */
.md h1 { font-size: 1.8rem; font-weight: 700; color: #fff; margin: 0 0 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid #2a2a2a; }
.md h2 { font-size: 1.3rem; font-weight: 600; color: #e5e7eb; margin: 2rem 0 0.8rem; }
.md h3 { font-size: 1.1rem; font-weight: 600; color: #d1d5db; margin: 1.5rem 0 0.5rem; }
.md p { line-height: 1.7; margin: 0.5rem 0; color: #d1d5db; }
.md a { color: #818cf8; text-decoration: none; }
.md a:hover { text-decoration: underline; }
.md code {
font-family: 'JetBrains Mono', 'Fira Code', monospace; font-size: 0.85em;
background: #1e1e1e; padding: 2px 6px; border-radius: 4px; color: #e5e7eb;
}
.md pre {
background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 8px;
padding: 1rem; overflow-x: auto; margin: 1rem 0;
}
.md pre code { background: none; padding: 0; font-size: 0.85rem; }
.md ul, .md ol { padding-left: 1.5rem; margin: 0.5rem 0; }
.md li { line-height: 1.7; color: #d1d5db; margin: 0.2rem 0; }
.md blockquote {
border-left: 3px solid #2a2a2a; padding: 0.5rem 1rem; margin: 1rem 0;
background: #1a1a1a; border-radius: 0 6px 6px 0;
}
.md blockquote p { color: #9ca3af; }
.md table { width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: 0.9rem; }
.md th { text-align: left; padding: 8px 12px; border-bottom: 2px solid #2a2a2a; color: #9ca3af; font-weight: 600; }
.md td { padding: 8px 12px; border-bottom: 1px solid #1e1e1e; color: #d1d5db; }
.md tr:hover td { background: #1a1a1a; }
.md img { max-width: 100%; border-radius: 8px; margin: 1rem 0; }
.md hr { border: none; border-top: 1px solid #2a2a2a; margin: 2rem 0; }
/* Tier blockquotes */
.md blockquote:has(p:first-child) { }
.tier-free { border-left-color: #22c55e; }
.tier-featured { border-left-color: #3b82f6; }
.tier-pro { border-left-color: #f59e0b; }
.tier-full { border-left-color: #a855f7; }
/* Responsive */
@media (max-width: 768px) {
.sidebar { width: 200px; }
.content { padding: 1rem; }
}
</style>
</head>
<body>
<aside class="sidebar">
<div class="sidebar-header">
<h1>brain docs</h1>
<span class="badge" id="mode-badge">...</span>
</div>
<nav id="nav"></nav>
<div class="back-link">
<a href="/ui/">← brain-ui</a>
</div>
</aside>
<main class="content">
<div class="md" id="content">
<p class="loading">Chargement...</p>
</div>
</main>
<script>
let API = ''
const GROUP_ORDER = ['Guides', 'Agents', 'Vues']
const TIER_MARKERS = { '\u{1F7E2}': 'tier-free', '\u{1F535}': 'tier-featured', '\u{1F7E0}': 'tier-pro', '\u{1F7E3}': 'tier-full' }
let docs = []
let activeDoc = null
let liveMode = false
// Static fallback
const STATIC_DOCS = [
{ name: 'getting-started', label: 'Demarrer', group: 'Guides' },
{ name: 'architecture', label: 'Architecture', group: 'Guides' },
{ name: 'sessions', label: 'Sessions', group: 'Guides' },
{ name: 'workflows', label: 'Workflows', group: 'Guides' },
{ name: 'satellites', label: 'Satellites', group: 'Guides' },
{ name: 'brain-engine-guide', label: 'Brain-engine', group: 'Guides' },
{ name: 'agents', label: "Vue d'ensemble", group: 'Agents' },
{ name: 'agents-code', label: 'Code & Qualite', group: 'Agents' },
{ name: 'agents-infra', label: 'Infra & Deploy', group: 'Agents' },
{ name: 'agents-brain', label: 'Brain & Systeme', group: 'Agents' },
{ name: 'vue-tiers', label: 'Comparatif', group: 'Vues' },
{ name: 'vue-free', label: 'free', group: 'Vues' },
{ name: 'vue-featured', label: 'featured', group: 'Vues' },
{ name: 'vue-pro', label: 'pro', group: 'Vues' },
{ name: 'vue-full', label: 'full', group: 'Vues' },
]
async function init() {
// Auto-detect API path : /api (proxy Apache) ou direct (local)
for (const prefix of ['', '/api']) {
try {
const res = await fetch(`${prefix}/docs`)
if (!res.ok) continue
const data = await res.json()
if (data.docs?.length) {
API = prefix
docs = data.docs
liveMode = true
break
}
} catch { /* next */ }
}
if (!liveMode) docs = STATIC_DOCS
document.getElementById('mode-badge').textContent = liveMode ? 'live' : 'static'
renderNav()
// Check URL hash
const hash = location.hash.replace('#', '')
const target = hash && docs.find(d => d.name === hash) ? hash : 'getting-started'
loadDoc(target)
}
function renderNav() {
const nav = document.getElementById('nav')
const groups = {}
docs.forEach(d => {
const g = d.group || 'Autres'
if (!groups[g]) groups[g] = []
groups[g].push(d)
})
const sorted = GROUP_ORDER.filter(g => groups[g]).map(g => [g, groups[g]])
Object.entries(groups).forEach(([g, d]) => {
if (!GROUP_ORDER.includes(g)) sorted.push([g, d])
})
nav.innerHTML = sorted.map(([group, groupDocs]) => `
<div class="group-label">${group}</div>
${groupDocs.map(d => `
<button class="doc-link" data-name="${d.name}" onclick="loadDoc('${d.name}')">${d.label}</button>
`).join('')}
`).join('')
}
async function loadDoc(name) {
activeDoc = name
location.hash = name
// Update active state
document.querySelectorAll('.doc-link').forEach(el => {
el.classList.toggle('active', el.dataset.name === name)
})
const el = document.getElementById('content')
el.innerHTML = '<p class="loading">Chargement...</p>'
try {
let md
if (liveMode) {
const res = await fetch(`${API}/docs/${name}.md`)
if (!res.ok) throw new Error(res.status)
const data = await res.json()
md = data.content
} else {
const res = await fetch(`/ui/docs/${name}.md`)
if (!res.ok) throw new Error(res.status)
md = await res.text()
md = md.replace(/^---[\s\S]*?---\n*/, '')
}
el.innerHTML = marked.parse(md)
// Apply tier colors to blockquotes
el.querySelectorAll('blockquote').forEach(bq => {
const text = bq.textContent
for (const [marker, cls] of Object.entries(TIER_MARKERS)) {
if (text.includes(marker)) {
bq.classList.add(cls)
break
}
}
})
} catch (err) {
el.innerHTML = `<p style="color:#ef4444">Impossible de charger ${name}: ${err.message}</p>`
}
}
init()
</script>
</body>
</html>

View File

@@ -1 +0,0 @@
../../../docs/README.md

View File

@@ -1 +0,0 @@
../../../docs/agents-brain.md

View File

@@ -1 +0,0 @@
../../../docs/agents-code.md

View File

@@ -1 +0,0 @@
../../../docs/agents-infra.md

View File

@@ -1 +0,0 @@
../../../docs/agents.md

View File

@@ -1 +0,0 @@
../../../docs/architecture.md

View File

@@ -1 +0,0 @@
../../../docs/brain-engine-guide.md

View File

@@ -1 +0,0 @@
../../../docs/getting-started.md

View File

@@ -1 +0,0 @@
../../../docs/satellites.md

View File

@@ -1 +0,0 @@
../../../docs/sessions.md

View File

@@ -1 +0,0 @@
../../../docs/vue-featured.md

View File

@@ -1 +0,0 @@
../../../docs/vue-free.md

View File

@@ -1 +0,0 @@
../../../docs/vue-full.md

View File

@@ -1 +0,0 @@
../../../docs/vue-pro.md

View File

@@ -1 +0,0 @@
../../../docs/vue-tiers.md

View File

@@ -1 +0,0 @@
../../../docs/workflows.md

View File

@@ -1,7 +1,7 @@
import { useState, useEffect, Suspense, lazy } from 'react'
import Dashboard from './components/Dashboard'
import WorkflowBoard from './components/WorkflowBoard'
import SecretsZone, { MOCK_SECTIONS } from './components/SecretsZone'
import WorkflowBuilder from './components/WorkflowBuilder'
import GatesDrawer from './components/GatesDrawer'
import GateDrawer from './components/GateDrawer'
import LogDrawer from './components/LogDrawer'
@@ -18,7 +18,7 @@ const CosmosView = lazy(() => import('./components/cosmos/CosmosView'))
const WorkspaceView = lazy(() => import('./components/workspace/WorkspaceView'))
const DocsView = lazy(() => import('./components/DocsView'))
type ActiveView = 'workflows' | 'builder' | 'secrets' | 'infra' | 'cosmos' | 'workspace' | 'docs'
type ActiveView = 'dashboard' | 'cosmos' | 'workflows' | 'secrets' | 'infra' | 'workspace'
interface NavItem {
id: ActiveView
@@ -34,12 +34,11 @@ interface PendingGate {
}
const NAV_ITEMS: NavItem[] = [
{ id: 'workflows', icon: '🔀', label: 'Workflows' },
{ id: 'builder', icon: '', label: 'Nouveau' },
{ id: 'secrets', icon: '🔑', label: 'Secrets' },
{ id: 'dashboard', icon: '', label: 'Dashboard' },
{ id: 'cosmos', icon: '🌌', label: 'Cosmos' },
{ id: 'workflows', icon: '🔀', label: 'Workflows', separator: true },
{ id: 'infra', icon: '🖥️', label: 'Infra' },
{ id: 'cosmos', icon: '🌌', label: 'Cosmos', separator: true },
{ id: 'docs', icon: '📖', label: 'Docs' },
{ id: 'secrets', icon: '🔑', label: 'Secrets' },
]
function AppInner() {
@@ -48,10 +47,9 @@ function AppInner() {
// Detect URL path for direct routing (/ui/docs → docs view)
const initialView = (): ActiveView => {
const path = window.location.pathname
if (path.includes('/docs')) return 'docs'
if (path.includes('/cosmos')) return 'cosmos'
if (path.includes('/workspace')) return 'workspace'
return 'workflows'
return 'dashboard'
}
const [activeView, setActiveView] = useState<ActiveView>(initialView)
const [pendingGate, setPendingGate] = useState<PendingGate | null>(null)
@@ -173,6 +171,21 @@ function AppInner() {
})}
</nav>
{/* Docs — lien externe standalone */}
<div className="px-2 mt-2">
<a
href="/ui/docs.html"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-3 px-3 py-2 rounded text-sm font-medium text-left w-full transition-colors"
style={{ color: '#9ca3af', borderLeft: '2px solid transparent', paddingLeft: 10, textDecoration: 'none' }}
>
<span className="text-base leading-none">📖</span>
<span>Docs</span>
<span style={{ marginLeft: 'auto', fontSize: 9, color: '#4b5563' }}></span>
</a>
</div>
{/* Bouton Logs */}
<div className="px-2 mt-2">
<button
@@ -219,6 +232,9 @@ function AppInner() {
{/* Main content */}
<main className="flex-1 overflow-hidden flex flex-col">
{activeView === 'dashboard' && (
<Dashboard />
)}
{activeView === 'workflows' && (
<WorkflowBoard
workflows={workflows}
@@ -226,9 +242,6 @@ function AppInner() {
onWorkflowClick={(wfId) => setLogsProject(wfId)}
/>
)}
{activeView === 'builder' && (
<WorkflowBuilder />
)}
{activeView === 'secrets' && (
<TierGate feature="secrets" hasFeature={hasFeature}>
<SecretsZone sections={MOCK_SECTIONS} onSecretSave={handleSecretSave} />
@@ -250,15 +263,6 @@ function AppInner() {
</Suspense>
</div>
)}
{activeView === 'docs' && (
<Suspense fallback={
<div className="flex items-center justify-center h-full" style={{ color: '#4b5563' }}>
<span className="text-sm font-mono">Chargement Docs...</span>
</div>
}>
<DocsView />
</Suspense>
)}
{activeView === 'workspace' && (
<Suspense fallback={
<div className="flex items-center justify-center h-full" style={{ color: '#4b5563' }}>

View File

@@ -0,0 +1,404 @@
import { useState, useEffect, useCallback } from 'react'
const API = import.meta.env.VITE_BRAIN_API ?? ''
interface SearchResult {
score: number
title: string
filepath: string
excerpt: string
}
interface HealthData {
status: string
indexed: number
uptime: number
}
interface ClaimData {
sess_id: string
type: string
scope: string
status: string
opened_at: string
closed_at: string | null
}
interface AgentData {
id: string
label: string
tier: string
status: string
scope: string
}
interface DocData {
name: string
label: string
group: string
}
function formatUptime(seconds: number): string {
if (seconds < 60) return `${seconds}s`
if (seconds < 3600) return `${Math.floor(seconds / 60)}min`
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}min`
return `${Math.floor(seconds / 86400)}j ${Math.floor((seconds % 86400) / 3600)}h`
}
function formatTimeAgo(dateStr: string): string {
const diff = Date.now() - new Date(dateStr).getTime()
const mins = Math.floor(diff / 60000)
if (mins < 1) return "à l'instant"
if (mins < 60) return `il y a ${mins}min`
const hours = Math.floor(mins / 60)
if (hours < 24) return `il y a ${hours}h`
const days = Math.floor(hours / 24)
return `il y a ${days}j`
}
function StatCard({ label, value, sub, color }: { label: string; value: string | number; sub?: string; color?: string }) {
return (
<div style={{
background: '#141414', border: '1px solid #2a2a2a', borderRadius: 8,
padding: '16px 20px', flex: '1 1 0', minWidth: 140,
}}>
<div style={{ fontSize: 11, color: '#6b7280', fontFamily: 'monospace', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
{label}
</div>
<div style={{ fontSize: 28, fontWeight: 700, color: color ?? '#e5e7eb', marginTop: 4 }}>
{value}
</div>
{sub && (
<div style={{ fontSize: 11, color: '#4b5563', marginTop: 2, fontFamily: 'monospace' }}>
{sub}
</div>
)}
</div>
)
}
function SessionRow({ claim }: { claim: ClaimData }) {
const isOpen = claim.status === 'open'
return (
<div style={{
display: 'flex', alignItems: 'center', gap: 12,
padding: '10px 16px', borderBottom: '1px solid #1e1e1e',
}}>
<span style={{
width: 8, height: 8, borderRadius: '50%', flexShrink: 0,
background: isOpen ? '#22c55e' : '#4b5563',
}} />
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 13, color: '#e5e7eb', fontFamily: 'monospace' }}>
{claim.sess_id}
</div>
<div style={{ fontSize: 11, color: '#6b7280', marginTop: 2 }}>
{claim.type} {claim.scope}
</div>
</div>
<div style={{ fontSize: 11, color: '#4b5563', fontFamily: 'monospace', flexShrink: 0 }}>
{formatTimeAgo(claim.opened_at)}
</div>
</div>
)
}
function FileViewer({ path, onClose }: { path: string; onClose: () => void }) {
const [content, setContent] = useState<string | null>(null)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
fetch(`${API}/brain/${path}`)
.then(r => { if (!r.ok) throw new Error(`${r.status}`); return r.json() })
.then(d => setContent(d.content))
.catch(e => setError(`Impossible de charger ${path}: ${e.message}`))
}, [path])
return (
<div style={{
position: 'fixed', inset: 0, zIndex: 50,
background: 'rgba(0,0,0,0.7)', display: 'flex', alignItems: 'center', justifyContent: 'center',
}} onClick={onClose}>
<div
style={{
background: '#141414', border: '1px solid #2a2a2a', borderRadius: 12,
width: '70%', maxWidth: 800, maxHeight: '80vh',
display: 'flex', flexDirection: 'column', overflow: 'hidden',
}}
onClick={e => e.stopPropagation()}
>
<div style={{
padding: '12px 20px', borderBottom: '1px solid #2a2a2a',
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
}}>
<span style={{ fontSize: 13, fontFamily: 'monospace', color: '#818cf8' }}>{path}</span>
<button
onClick={onClose}
style={{ background: 'none', border: 'none', color: '#6b7280', cursor: 'pointer', fontSize: 18 }}
>
×
</button>
</div>
<div style={{ padding: '16px 20px', overflowY: 'auto', flex: 1 }}>
{error && <div style={{ color: '#ef4444', fontSize: 13 }}>{error}</div>}
{!content && !error && <div style={{ color: '#4b5563', fontSize: 13, fontFamily: 'monospace' }}>Chargement...</div>}
{content && (
<pre style={{
fontSize: 13, lineHeight: 1.6, color: '#d1d5db',
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
whiteSpace: 'pre-wrap', wordBreak: 'break-word', margin: 0,
}}>
{content}
</pre>
)}
</div>
</div>
</div>
)
}
function SearchBar() {
const [query, setQuery] = useState('')
const [results, setResults] = useState<SearchResult[]>([])
const [searching, setSearching] = useState(false)
const [searched, setSearched] = useState(false)
const [viewingFile, setViewingFile] = useState<string | null>(null)
const search = useCallback(async (q: string) => {
if (q.trim().length < 2) { setResults([]); setSearched(false); return }
setSearching(true)
try {
const res = await fetch(`${API}/search?q=${encodeURIComponent(q)}&top=6`)
if (!res.ok) throw new Error()
const data = await res.json()
setResults(data.results ?? [])
setSearched(true)
} catch {
setResults([])
} finally {
setSearching(false)
}
}, [])
useEffect(() => {
const timer = setTimeout(() => { if (query.trim().length >= 2) search(query) }, 400)
return () => clearTimeout(timer)
}, [query, search])
return (
<div style={{ marginBottom: 24 }}>
<div style={{
display: 'flex', alignItems: 'center', gap: 8,
background: '#141414', border: '1px solid #2a2a2a', borderRadius: 8,
padding: '8px 16px',
}}>
<span style={{ color: '#4b5563', fontSize: 16 }}>🔍</span>
<input
type="text"
placeholder="Rechercher dans le brain..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') search(query) }}
style={{
flex: 1, background: 'transparent', border: 'none', outline: 'none',
color: '#e5e7eb', fontSize: 14, fontFamily: 'inherit',
}}
/>
{searching && <span style={{ color: '#4b5563', fontSize: 12, fontFamily: 'monospace' }}>...</span>}
</div>
{searched && results.length > 0 && (
<div style={{
marginTop: 8, background: '#141414', border: '1px solid #2a2a2a',
borderRadius: 8, overflow: 'hidden',
}}>
{results.map((r, i) => (
<div key={i} style={{
padding: '12px 16px', borderBottom: i < results.length - 1 ? '1px solid #1e1e1e' : 'none',
cursor: 'pointer', transition: 'background 0.15s',
}}
onClick={() => setViewingFile(r.filepath)}
onMouseEnter={e => (e.currentTarget.style.background = '#1a1a1a')}
onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
<span style={{ fontSize: 12, fontFamily: 'monospace', color: '#818cf8' }}>
{r.filepath}
</span>
<span style={{
fontSize: 10, fontFamily: 'monospace', color: '#4b5563',
marginLeft: 'auto',
}}>
{(r.score * 100).toFixed(0)}%
</span>
</div>
<div style={{ fontSize: 13, color: '#9ca3af', lineHeight: 1.5 }}>
{r.excerpt.slice(0, 200)}{r.excerpt.length > 200 ? '...' : ''}
</div>
</div>
))}
</div>
)}
{searched && results.length === 0 && !searching && (
<div style={{ marginTop: 8, fontSize: 13, color: '#4b5563', fontFamily: 'monospace', padding: '8px 16px' }}>
Aucun résultat pour "{query}"
</div>
)}
{viewingFile && <FileViewer path={viewingFile} onClose={() => setViewingFile(null)} />}
</div>
)
}
export default function Dashboard() {
const [health, setHealth] = useState<HealthData | null>(null)
const [claims, setClaims] = useState<ClaimData[]>([])
const [agents, setAgents] = useState<AgentData[]>([])
const [docs, setDocs] = useState<DocData[]>([])
const [error, setError] = useState<string | null>(null)
useEffect(() => {
Promise.allSettled([
fetch(`${API}/health`).then(r => r.json()),
fetch(`${API}/bsi/claims`).then(r => r.ok ? r.json() : []),
fetch(`${API}/agents`).then(r => r.ok ? r.json() : []),
fetch(`${API}/docs`).then(r => r.ok ? r.json() : { docs: [] }),
]).then(([h, c, a, d]) => {
if (h.status === 'fulfilled') setHealth(h.value)
if (c.status === 'fulfilled') setClaims(Array.isArray(c.value) ? c.value : [])
if (a.status === 'fulfilled') setAgents(Array.isArray(a.value) ? a.value : [])
if (d.status === 'fulfilled') setDocs(d.value?.docs ?? [])
}).catch(() => setError('Impossible de charger les données'))
}, [])
const openClaims = claims.filter(c => c.status === 'open')
const recentClaims = claims.slice(0, 8)
const agentsByTier = agents.reduce<Record<string, number>>((acc, a) => {
acc[a.tier] = (acc[a.tier] || 0) + 1
return acc
}, {})
return (
<div style={{ padding: '2rem 3rem', overflowY: 'auto', height: '100%' }}>
{/* Header */}
<div style={{ marginBottom: 24 }}>
<h1 style={{ fontSize: 20, fontWeight: 700, color: '#fff', margin: 0 }}>
Dashboard
</h1>
<p style={{ fontSize: 12, color: '#4b5563', fontFamily: 'monospace', marginTop: 4 }}>
{health ? `brain-engine up — ${formatUptime(health.uptime)}` : 'connexion...'}
</p>
</div>
{error && (
<div style={{ color: '#ef4444', fontSize: 13, marginBottom: 16 }}>{error}</div>
)}
{/* Search */}
<SearchBar />
{/* Stats row */}
<div style={{ display: 'flex', gap: 12, marginBottom: 24, flexWrap: 'wrap' }}>
<StatCard
label="Embeddings"
value={health?.indexed?.toLocaleString() ?? '—'}
sub="chunks indexés"
color="#818cf8"
/>
<StatCard
label="Agents"
value={agents.length || '—'}
sub={Object.entries(agentsByTier).map(([t, n]) => `${n} ${t}`).join(' · ') || undefined}
color="#22c55e"
/>
<StatCard
label="Docs"
value={docs.length || '—'}
sub="pages live"
color="#f59e0b"
/>
<StatCard
label="Sessions"
value={openClaims.length}
sub={openClaims.length > 0 ? 'actives' : 'aucune active'}
color={openClaims.length > 0 ? '#22c55e' : '#6b7280'}
/>
</div>
{/* Two columns */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
{/* Recent sessions */}
<div style={{ background: '#141414', border: '1px solid #2a2a2a', borderRadius: 8, overflow: 'hidden' }}>
<div style={{
padding: '12px 16px', borderBottom: '1px solid #2a2a2a',
fontSize: 12, fontFamily: 'monospace', color: '#6b7280',
textTransform: 'uppercase', letterSpacing: '0.05em',
}}>
Sessions récentes
</div>
{recentClaims.length === 0 ? (
<div style={{ padding: 16, fontSize: 13, color: '#4b5563' }}>
Aucune session enregistrée
</div>
) : (
recentClaims.map(c => <SessionRow key={c.sess_id} claim={c} />)
)}
</div>
{/* Quick links */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
{/* Agents by scope */}
<div style={{ background: '#141414', border: '1px solid #2a2a2a', borderRadius: 8, overflow: 'hidden' }}>
<div style={{
padding: '12px 16px', borderBottom: '1px solid #2a2a2a',
fontSize: 12, fontFamily: 'monospace', color: '#6b7280',
textTransform: 'uppercase', letterSpacing: '0.05em',
}}>
Agents par scope
</div>
<div style={{ padding: 16, display: 'flex', gap: 16, flexWrap: 'wrap' }}>
{Object.entries(
agents.reduce<Record<string, number>>((acc, a) => {
acc[a.scope || 'unknown'] = (acc[a.scope || 'unknown'] || 0) + 1
return acc
}, {})
).map(([scope, count]) => (
<div key={scope} style={{ textAlign: 'center' }}>
<div style={{ fontSize: 20, fontWeight: 700, color: '#e5e7eb' }}>{count}</div>
<div style={{ fontSize: 10, color: '#6b7280', fontFamily: 'monospace' }}>{scope}</div>
</div>
))}
</div>
</div>
{/* Docs groups */}
<div style={{ background: '#141414', border: '1px solid #2a2a2a', borderRadius: 8, overflow: 'hidden' }}>
<div style={{
padding: '12px 16px', borderBottom: '1px solid #2a2a2a',
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
}}>
<span style={{ fontSize: 12, fontFamily: 'monospace', color: '#6b7280', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
Documentation
</span>
<a href="/docs" target="_blank" rel="noopener noreferrer"
style={{ fontSize: 11, color: '#818cf8', textDecoration: 'none', fontFamily: 'monospace' }}>
Ouvrir
</a>
</div>
<div style={{ padding: 16, display: 'flex', gap: 16, flexWrap: 'wrap' }}>
{Object.entries(
docs.reduce<Record<string, number>>((acc, d) => {
acc[d.group || 'Autres'] = (acc[d.group || 'Autres'] || 0) + 1
return acc
}, {})
).map(([group, count]) => (
<div key={group} style={{ textAlign: 'center' }}>
<div style={{ fontSize: 20, fontWeight: 700, color: '#e5e7eb' }}>{count}</div>
<div style={{ fontSize: 10, color: '#6b7280', fontFamily: 'monospace' }}>{group}</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
)
}

View File

@@ -212,7 +212,14 @@ export default function DocsView() {
return <article className="docs-markdown"><TierSingle tierName={tierName} /></article>
}
if (liveMode && activeDoc === 'agents') {
return <article className="docs-markdown"><AgentCatalog /></article>
return (
<article className="docs-markdown">
{!loading && content && (
<ReactMarkdown components={mdComponents}>{content}</ReactMarkdown>
)}
<AgentCatalog />
</article>
)
}
// Mode standard — markdown

View File

@@ -5,6 +5,8 @@ const ZONE_BADGE_COLORS: Record<ZoneKey, { bg: string; text: string }> = {
public: { bg: 'rgba(229,231,235,0.1)', text: '#e5e7eb' },
work: { bg: 'rgba(99,102,241,0.15)', text: '#6366f1' },
kernel: { bg: 'rgba(239,68,68,0.15)', text: '#ef4444' },
instance: { bg: 'rgba(168,85,247,0.15)', text: '#a855f7' },
satellite: { bg: 'rgba(34,197,94,0.15)', text: '#22c55e' },
unknown: { bg: 'rgba(75,85,99,0.2)', text: '#6b7280' },
}

View File

@@ -6,6 +6,8 @@ const ZONE_COLORS: Record<ZoneKey, string> = {
public: '#6366f1',
work: '#22c55e',
kernel: '#f59e0b',
instance: '#a855f7',
satellite: '#3b82f6',
unknown: '#6b7280',
}

View File

@@ -0,0 +1,45 @@
# session-capital.yml — Contexte BHP pour sessions capital professionnel
# Trigger : "brain boot mode capital"
# Focus : bilan, objectifs, CV, capital professionnel, progression long terme
# Cible : ~23% contexte max au boot
session_type: capital
description: "Session capital — bilan, objectifs, CV, capital professionnel"
tier_required: featured # coach.md complet + capital-scribe = tier featured (RAG + progression)
# L0 — Invariant (~5%)
L0:
- PATHS.md
- brain-compose.local.yml
- KERNEL.md
# L1 — Session type (~18%)
L1:
- now.md # bridge session précédente
- agents/coach.md # coach complet — bilan et cap stratégique (pas boot-summary)
- profil/objectifs.md # objectifs actifs + prochaines étapes
- profil/capital.md # preuves CV, milestones, capital accumulé
- progression/README.md # état métabolique + ratio sessions + tendances
# L2 — non applicable (capital = session introspective, pas de scope projet)
L2:
template: null
extras: []
fallback: null
# L3 — On demand
# progression/ détaillée, sessions passées, capital-scribe pour mise à jour capital.md
L3:
hint: "Charger à la demande : progression/ détaillée, sessions passées, capital-scribe"
# --- Note capital ---
# capital-scribe actif automatiquement si modification capital.md détectée en session.
# Pas de projets/ en L2 — le coaching capital est orthogonal aux projets actifs.
# session-coach = réflexion stratégique ; session-capital = capital pro + CV + milestones.
# --- Métriques cibles ---
context_target:
L0: "~5%"
L1: "~18%"
L2: "0%"
total_boot: "~23%"

View File

@@ -4,7 +4,7 @@
session_type: coach
description: "Session coaching — progression, réflexion, cap stratégique, clarté"
tier_required: pro # coaching complet (coach.md 365 lignes) = feature pro
tier_required: featured # coaching complet (coach.md) = tier featured (RAG + progression)
# L0 — Invariant (~5%)
L0:

View File

@@ -0,0 +1,46 @@
# session-deploy.yml — Contexte BHP pour sessions de déploiement
# Trigger : "brain boot mode deploy[/<project>]"
# Cible : ~25% contexte max au boot
session_type: deploy
description: "Session de déploiement — VPS, Docker, SSL, CI/CD, infra"
tier_required: pro # deploy = agents vps/ci-cd/monitoring (tous pro)
# L0 — Invariant (~5%)
L0:
- PATHS.md
- brain-compose.local.yml
- KERNEL.md
# L1 — Session type (~15%)
L1:
- now.md # état dernière session — ce qui a changé, ce qui est en attente
- focus.md # projets actifs — savoir quoi déployer
- agents/vps.md # VPS, Apache, Docker, SSL, vhost, certbot # tier: pro
- agents/ci-cd.md # pipelines, GitHub Actions, Gitea CI # tier: pro
- agents/monitoring.md # Kuma, alertes, logs post-déploiement # tier: pro
# L2 — Project scope (~8%) — optionnel sur projet déclaré
L2:
template: "projets/{project}.md"
extras:
- "todo/{project}.md"
fallback: null
# L3 — On demand
# Exemples : agents/security.md (audit pré-deploy), agents/mail.md (DNS/DKIM),
# agents/pm2.md, agents/migration.md, config spécifique
L3:
hint: "Charger à la demande : security pre-deploy, mail/DNS, pm2, migration schema"
# --- Règle deploy ---
# agents/security.md NOT en L1 par défaut — deploy ≠ audit sécurité.
# Charger si audit pré-déploiement explicitement demandé (→ session-audit).
# agents/monitoring.md en L1 : toujours vérifier les alertes après deploy.
# --- Métriques cibles ---
context_target:
L0: "~5%"
L1: "~12%"
L2: "~8%"
total_boot: "~25%"

View File

@@ -0,0 +1,52 @@
# session-handoff.yml — Contexte BHP pour sessions HANDOFF
# Trigger : "brain boot mode HANDOFF[/<handoff-id>]"
# Cible : ~15% contexte max au boot — minimum viable pour reprendre
session_type: HANDOFF
description: "Reprise d'une session via handoff — contexte minimal + fichier handoff cible"
tier_required: free # handoff = protocole BSI de base — disponible pour tous
# L0 — Invariant (~5%)
L0:
- PATHS.md
- brain-compose.local.yml
- KERNEL.md
# L1 — Session type (~8%) — chirurgical : seulement ce qu'il faut pour reprendre
L1:
- BRAIN-INDEX.md # trouver le handoff actif
# L2 — Handoff scope (~5%) — fichier handoff si déclaré dans le signal
# Signal : "HANDOFF/<handoff-id>" → charger le fichier handoff directement
L2:
template: "handoffs/{handoff_id}.md"
extras: []
fallback:
- handoffs/LATEST.md # si pas d'ID déclaré → dernier handoff
# L3 — On demand
# Exemples : context complet projet, agents métier, focus.md
# Principe : le fichier handoff CONTIENT les pointeurs vers L3.
# La nouvelle session lit le handoff → décide elle-même quoi charger.
L3:
hint: "Lire le handoff d'abord. Il indique quoi charger en L3."
# --- Règle HANDOFF ---
# Le handoff est la source de vérité pour la reprise.
# Ne pas charger focus.md, metabolism, agents au boot — le handoff décide.
# Après lecture du handoff → promouvoir le contexte nécessaire en L2 implicite.
# Le claim ouvert doit référencer le handoff : parent_satellite ou story_angle.
# --- Format handoff attendu ---
# handoffs/<id>.md doit contenir :
# - Contexte de la session précédente (état, décisions, bloquants)
# - Fichiers à charger (L3 → L2 pour cette session)
# - Todos ouverts prioritaires
# - Signal de session recommandé pour la suite
# --- Métriques cibles ---
context_target:
L0: "~5%"
L1: "~5%"
L2: "~5%"
total_boot: "~15%"

View File

@@ -0,0 +1,45 @@
# session-infra.yml — Contexte BHP pour sessions infrastructure
# Trigger : "brain boot mode infra"
# Focus : ops quotidien — monitoring, maintenance VPS, config, santé système
# Distinct de session-deploy (deploy = ship code ; infra = ops/health/maintenance)
# Cible : ~22% contexte max au boot
session_type: infra
description: "Session infrastructure — ops quotidien, maintenance VPS, santé système"
tier_required: pro # vps, pm2, monitoring = agents pro
# L0 — Invariant (~5%)
L0:
- PATHS.md
- brain-compose.local.yml
- KERNEL.md
# L1 — Session type (~14%)
L1:
- now.md # bridge session précédente
- agents/coach-boot.md # présence légère — pas de bilan complet en infra
- agents/vps.md # VPS, Apache, Docker, SSL, vhost, certbot # tier: pro
- agents/pm2.md # process manager, restart, logs # tier: pro
- focus.md # projets actifs — contexte des services en cours
# L2 — Project scope — optionnel si scope projet déclaré
L2:
template: "projets/{project}.md"
extras: []
fallback: null
# L3 — On demand
# agents/monitoring.md, agents/mail.md (DNS/DKIM), agents/ci-cd.md, agents/migration.md
L3:
hint: "Charger à la demande : monitoring, mail/DNS, ci-cd, migration schema, security pre-audit"
# --- Distinction infra / deploy ---
# infra = état du système, maintenance, vérifications, config — pas de CI/CD au boot
# deploy = livraison code, CI/CD, releases, pipeline — agents/ci-cd.md en L1
# --- Métriques cibles ---
context_target:
L0: "~5%"
L1: "~14%"
L2: "~3%"
total_boot: "~22%"

View File

@@ -22,6 +22,9 @@ L1:
- focus.md # projets actifs + prochaine frontière
- BRAIN-INDEX.md # claims actifs + signaux
- todo/README.md # index intentions (warm — fichiers projet sur demande)
- agents/guide.md # presentation systeme — accueil fresh fork + on demand
- agents/catalogist.md # exploration registres (agents, tiers, features)
- agents/pathfinder.md # routage intentionnel — oriente vers la bonne session
# L2 — Project scope (~2%) — déclenché si project déclaré dans le signal
L2:

View File

@@ -0,0 +1,45 @@
# session-urgence.yml — Contexte BHP pour sessions incident/urgence
# Trigger : "brain boot mode urgence"
# Focus : incident prod, production down, hotfix critique
# Mode conserve : automatique — cible context < 40%
# Cible : ~15% contexte max au boot (mode conserve)
session_type: urgence
description: "Session urgence — incident prod, production down, hotfix critique"
tier_required: pro # vps, pm2, debug = agents pro
# L0 — Invariant (~5%)
L0:
- PATHS.md
- brain-compose.local.yml
- KERNEL.md
# L1 — Session type (~10%) — minimal, on va vite
# Coach absent : urgence avant bilan pédagogique — invocation explicite si besoin
L1:
- agents/vps.md # VPS, Apache — infra down # tier: pro
- agents/pm2.md # process manager, restart, logs # tier: pro
- agents/debug.md # diagnostic, crash, comportement inattendu
# L2 — non applicable (urgence = focus immédiat, pas de scope projet)
L2:
template: null
extras: []
fallback: null
# L3 — On demand — déclenché après résolution
L3:
hint: "Après résolution : scribe (post-mortem), security (si faille), monitoring (état alertes)"
# --- Règles urgence ---
conserve: auto # mode conserve activé automatiquement — cible context < 40%
coach: silent # coach silencieux sauf invocation explicite après résolution
# post-mortem : scribe déclenché sur signal "résolution confirmée" ou "c'est résolu"
# secrets-guardian : actif en passif — rotation clés si compromis suspects
# --- Métriques cibles ---
context_target:
L0: "~5%"
L1: "~10%"
L2: "0%"
total_boot: "~15%"

View File

@@ -16,10 +16,14 @@ L0:
L1:
- now.md # bridge session précédente
- focus.md # focus actuel, todos prioritaires
- agents/coach.md # coach complet byTask — observe le projet en cours
- agents/coach-boot.md # coach boot-summary — observe le projet en cours (free)
- agents/debug.md # bug, crash, comportement inattendu
- metabolism/README.md # état métabolique, énergie session
# L1_featured — chargé si tier: featured+ — coach complet remplace coach-boot
L1_featured:
- agents/coach.md # coach complet byTask — observe le projet en cours
# L1_pro — Session type (~5%) — chargé uniquement si tier: pro déclaré
# Pas de code-review ni security pour tier free — chargement explicite sur demande sinon
L1_pro:

View File

@@ -1,6 +1,7 @@
# docs/ — Documentation humaine
> Guides lisibles sans contexte brain. Pour forks, onboarding, ou quand tu veux comprendre comment ca marche.
> L'histoire du projet → [story.tetardtek.com](https://story.tetardtek.com)
---

View File

@@ -1,8 +1,12 @@
# Le brain en 30 secondes
Un brain, c'est un systeme de **specialistes IA** qui travaillent ensemble. Chaque specialiste (agent) fait une chose bien : debugger, reviewer du code, deployer, ecrire des tests. Tu n'en charges jamais plus de 5 a la fois — le brain sait lesquels activer selon ce que tu fais.
## Pourquoi un brain plutot que Claude seul ?
Tu forkes le brain, tu codes. Les agents se chargent automatiquement.
Claude est puissant. Mais a chaque session, il repart de zero. Tu re-expliques ton projet, ta stack, tes conventions. Tu repetes les memes consignes. Tu perds du contexte a chaque compaction.
Le brain resout ca : **un systeme de specialistes IA qui persistent entre sessions.** Chaque specialiste (agent) fait une chose bien — debugger, reviewer du code, deployer, ecrire des tests. Ils connaissent tes regles, ta stack, tes decisions passees. Tu n'en charges jamais plus de 5 a la fois — le brain sait lesquels activer selon ce que tu fais.
Tu forkes le brain, tu codes. Les agents se chargent automatiquement. Ton contexte survit aux sessions.
---
@@ -10,25 +14,25 @@ Tu forkes le brain, tu codes. Les agents se chargent automatiquement.
> 🟢 **free — Tu forkes, ca marche**
>
> **14 agents + 8 systeme. 6 sessions.** Pas de cle API, pas de config.
> **17 agents + 9 systeme. 6 sessions.** Pas de cle API, pas de config.
>
> Debug, brainstorm, scribes automatiques, protection secrets, creation d'agents custom. Le coach observe en arriere-plan.
> Debug, brainstorm, scribes automatiques, protection secrets, creation d'agents custom. 3 agents d'onboarding (guide, catalogist, pathfinder) pour t'orienter. Le coach observe en arriere-plan.
> 🔵 **featured — Le brain te connait**
>
> **18 agents + systeme. 8 sessions.** Le coach se reveille.
> **21 agents + systeme. 8 sessions.** Le coach se reveille.
>
> Bilans de session, objectifs concrets, progression tracee. Le brain se souvient de tes acquis entre sessions grace a la distillation RAG.
> 🟠 **pro — L'atelier complet**
>
> **40 agents + systeme. 12 sessions.** Tu ship en prod.
> **42 agents + systeme. 14 sessions.** Tu ship en prod.
>
> Code review (7 priorites), audit securite (8 priorites OWASP), tests automatises, 3 optimiseurs perf, deploy VPS + CI/CD + SSL, sessions urgence et infra.
> 🟣 **full — Ton brain, tes regles**
>
> **75 agents (tous). 15 sessions.** Tu es owner.
> **81 agents (tous). 15 sessions.** Tu es owner.
>
> Modification du kernel, copilotage long (mode pilote), supervision multi-phase (hypervisor), coach proactif qui anticipe.
@@ -66,6 +70,11 @@ Charge les agents security et code-review
**Ils ne chargent que l'essentiel.** Un agent de 200 lignes → ~25 lignes au boot. Le reste se charge quand tu en as besoin.
**Premier fork ? 3 agents t'orientent.**
- `guide` — presente le systeme, repond a "c'est quoi ce truc ?"
- `catalogist` — explore ce qui est disponible (agents, tiers, features)
- `pathfinder` — t'oriente vers la bonne session selon ce que tu veux faire
---
## Explore les agents par famille
@@ -80,10 +89,19 @@ Charge les agents security et code-review
---
## Pour aller plus loin
**L'histoire du projet** — [story.tetardtek.com](https://story.tetardtek.com) raconte le pourquoi, le parcours, les decisions. Si tu veux comprendre la vision avant de fork.
---
## Nouveautes
| Date | Quoi de neuf |
|------|-------------|
| 2026-03-21 | 3 agents onboarding (guide, catalogist, pathfinder) — le brain accueille les nouveaux |
| 2026-03-21 | Docs live — git pull = docs a jour, zero rebuild |
| 2026-03-21 | VPS scission — vitrine template publique separee du brain prod |
| 2026-03-20 | Agents 87% plus legers au boot |
| 2026-03-20 | Coach adaptatif — 5 comportements selon la session |
| 2026-03-20 | Fermeture fiable — sequence deterministe |

View File

@@ -10,7 +10,7 @@ Le brain c'est 3 couches :
**1. Le kernel** — l'identite
- Les regles qui ne changent pas (KERNEL.md, constitution, PATHS.md)
- Les agents specialises (~75 fichiers .md)
- Les agents specialises (~81 fichiers .md)
- Le profil de collaboration
- Le brain-compose.yml (config, tiers, modes)

View File

@@ -1,6 +1,7 @@
# Demarrer avec le brain — le vrai tuto
> Du fork au premier `brain boot`. 10 minutes.
> Envie de comprendre le projet avant de fork ? → [story.tetardtek.com](https://story.tetardtek.com)
---
@@ -147,7 +148,6 @@ kill $(cat /tmp/brain-engine.pid)
Ouvre un **nouveau terminal** (brain-engine tourne dans l'autre) :
```bash
cd ~/Dev/Brain
claude
```
@@ -157,6 +157,8 @@ Claude Code s'ouvre. Tape :
brain boot
```
> Tu n'as pas besoin d'etre dans le dossier brain. `brain boot` fonctionne depuis n'importe quel repertoire — les chemins dans `~/.claude/CLAUDE.md` sont absolus.
### Ce que tu dois voir
```
@@ -211,8 +213,8 @@ cp profil/CLAUDE.md.example ~/.claude/CLAUDE.md
# 3. Lancer le dashboard (optionnel, a chaque session)
bash brain-engine/start.sh
# 4. Lancer Claude Code (a chaque session)
cd ~/Dev/Brain && claude
# 4. Lancer Claude Code (a chaque session, depuis n'importe ou)
claude
# Puis : brain boot
```
@@ -242,6 +244,20 @@ git fetch upstream
git merge upstream/main
```
### J'utilise Gitea self-hosted et git clone echoue ?
Gitea en Docker ecoute souvent sur un port SSH non standard (2222 au lieu de 22). Ajoute dans `~/.ssh/config` :
```
Host git.example.com
HostName git.example.com
Port 2222
User git
IdentityFile ~/.ssh/id_ed25519
```
Puis ajoute la host key : `ssh-keyscan -p 2222 git.example.com >> ~/.ssh/known_hosts`
### Ou est la documentation complete ?
- Dashboard : `http://localhost:7700/ui/` → onglet Docs

View File

@@ -1,6 +1,6 @@
# 🟢 free — Ce que tu as
> 🟢 **14 agents invocables + 8 systeme. 6 sessions. Pas de cle, pas de config.**
> 🟢 **17 agents invocables + 9 systeme. 6 sessions. Pas de cle, pas de config.**
---
@@ -36,6 +36,12 @@
- `pattern-scribe` — detection patterns recurrents
- `time-anchor` — conscience temporelle, recontextualisation
**S'orienter**
- `guide` — presentation du systeme, accueil fresh fork
- `catalogist` — exploration des registres (agents, tiers, features)
- `pathfinder` — routage intentionnel, oriente vers la bonne session
---
## Agents systeme

View File

@@ -7,7 +7,7 @@
## 🟢 free — Ce que tu as
> 🟢 **14 agents invocables + 8 systeme. 6 sessions.**
> 🟢 **17 agents invocables + 9 systeme. 6 sessions.**
### Sessions
@@ -37,6 +37,11 @@
- `pattern-scribe` — detection patterns recurrents
- `time-anchor` — conscience temporelle, recontextualisation
**S'orienter**
- `guide` — presentation du systeme, accueil fresh fork
- `catalogist` — exploration des registres (agents, tiers, features)
- `pathfinder` — routage intentionnel, oriente vers la bonne session
### Agents systeme (tournent a chaque boot)
- `helloWorld` — briefing, claim BSI

View File

@@ -1,10 +1,19 @@
---
name: CLAUDE-example
type: config
context_tier: cold
---
# CLAUDE.md — Bootstrap
# Copier vers ~/.claude/CLAUDE.md puis remplacer <BRAIN_ROOT> par le chemin réel
> **Fichier global** — `~/.claude/CLAUDE.md` — charge par Claude Code peu importe le cwd.
> `brain boot` fonctionne depuis n'importe quel repertoire : les chemins ci-dessous sont absolus.
> Ne jamais creer de CLAUDE.md projet qui override ce fichier sans raison explicite.
## Configuration machine (seul endroit a modifier sur une nouvelle machine)
brain_root: <BRAIN_ROOT>
brain_name: <BRAIN_NAME> ← prod / dev-laptop / template-test / etc.
brain_name: <BRAIN_NAME>
---
@@ -13,15 +22,25 @@ brain_name: <BRAIN_NAME> ← prod / dev-laptop / template-test / etc.
0. `<BRAIN_ROOT>/PATHS.md` — chemins machine
1. `<BRAIN_ROOT>/profil/collaboration.md` — regles de travail
2. `<BRAIN_ROOT>/agents/coach-boot.md` — presence permanente (boot-summary L0)
3. `<BRAIN_ROOT>/agents/secrets-guardian.md` — gardien secrets, permanent
3. `<BRAIN_ROOT>/agents/secrets-guardian.md` — gardien secrets, permanent, lit MYSECRETS silencieusement
4. `<BRAIN_ROOT>/agents/helloWorld.md` — briefing, focus, todos, CHECKPOINT, feature flags
helloWorld prend le relais : etat projets, todos prioritaires, detection de session, feature_set, CHECKPOINT.
Ne pas demander a l'utilisateur de se redecrire — tout est dans le brain.
> **Satellites manquants :** si todo/, toolkit/, progression/, reviews/ n'existent pas,
> le brain continue sans eux. helloWorld signale ce qui manque et propose de les creer.
> Ce n'est PAS une erreur — c'est un fresh fork.
> **BSI boot claim — non negociable** : apres le briefing, ouvrir un claim via `bash scripts/bsi-claim.sh open` (ADR-042).
> brain.db = source unique. Pas de commit git, pas de push. Voir protocole complet dans helloWorld.md ## Boot claim automatique.
> **brain boot navigate** (ou `brain boot mode navigate`) → charger `contexts/session-navigate.yml`
> via le BHP helloWorld — fenetre legere (~18%), time-anchor actif, coach en boot-summary uniquement.
> **brain boot sudo** (ou `brain boot mode edit-brain`) → charger `contexts/session-edit-brain.yml`
> Writes autorises partout dans le brain. Kernel (PATHS.md, KERNEL.md, CLAUDE.md) → confirmation explicite.
> **brain boot mode kernel** → charger `contexts/session-kernel.yml`
> Lecture seule sur le kernel. Toute tentative write kernel → refus + redirection session-edit-brain.
> **brain boot mode projet** → alias de `brain boot mode work` — meme session, meme contexte.
> Source unique de verite : brain `<BRAIN_NAME>` a `<BRAIN_ROOT>`.
> Si d'autres repertoires brain sont visibles sur le systeme — les ignorer.
@@ -29,14 +48,39 @@ Ne pas demander a l'utilisateur de se redecrire — tout est dans le brain.
---
## Agents 🔴 chauds — detection automatique sur domaine
## MCP brain — interface cognitive live
Le brain expose 7 outils MCP sur `<BRAIN_API_URL>/mcp/`.
A utiliser EN PLUS du bootstrap fichiers — pas a la place (CLAUDE.md reste Layer 0).
**Reflexes de session :**
- `brain_boot()` → enrichit le contexte avec les chunks RAG les plus pertinents
- `brain_focus()` → direction active + projets + blockers (live, pas le fichier stale)
- `brain_workflows()` → sprints en cours + etats des steps
- `brain_agents('X')` → charge le contenu d'un agent en contexte
- `brain_decisions(5)` → ADRs recentes
- `brain_search('q')` → RAG semantique sur n'importe quel sujet
- `brain_write('p', c)` → ecriture fichier brain [owner only]
**Quand utiliser :**
- Debut de session longue → `brain_boot()` + `brain_focus()` + `brain_workflows()`
- Avant de charger un agent → `brain_agents('nom-agent')` si contexte insuffisant
- Recherche de contexte precis → `brain_search('sujet')`
- Mise a jour brain en session → `brain_write('fichier.md', contenu)`
> MCP enrichit le contexte — ne remplace pas Layer 0. Les fichiers restent la source de verite.
---
## Agents chauds — detection automatique sur domaine
| Domaine detecte | Agent |
|-----------------|-------|
| VPS, Apache, Docker, SSL, vhost, certbot, deploy | `agents/vps.md` |
| Mail, SMTP, IMAP, Stalwart, DNS, SPF, DKIM | `agents/mail.md` |
| Review code, qualite, PR, validation avant prod | `agents/code-review.md` |
| Securite, faille, JWT, OAuth, OWASP, secrets | `agents/security.md` |
| Securite, faille, JWT, OAuth, OWASP | `agents/security.md` |
| .env, secrets, credentials, token manquant, API key, MYSECRETS | `agents/secrets-guardian.md` |
| Tests, Jest, Vitest, coverage, TDD | `agents/testing.md` |
| Bug, erreur, crash, comportement inattendu | `agents/debug.md` |
| Refacto, dette technique, DDD | `agents/refacto.md` |
@@ -50,8 +94,9 @@ Ne pas demander a l'utilisateur de se redecrire — tout est dans le brain.
| Stack frontend, shadcn, Tailwind, UI libs | `agents/frontend-stack.md` |
| i18n, traductions, cles manquantes | `agents/i18n.md` |
| README, doc API, Swagger | `agents/doc.md` |
| Game design, GDD, mecanique, equilibrage, progression jeu | `agents/game-designer.md` |
Agents 🔵 stables → invocation manuelle uniquement. Index complet : `agents/AGENTS.md`
Agents stables → invocation manuelle uniquement. Index complet : `agents/AGENTS.md`
Invocation explicite : "charge l'agent X" → lire `agents/X.md` immediatement.
Convention /btw : message prefixe `/btw` → agent `aside` — reponse 2-3 lignes, capture todo si actionnable, retour session explicite (`→ on reprend.`)
@@ -65,3 +110,6 @@ Convention /btw : message prefixe `/btw` → agent `aside` — reponse 2-3 ligne
- Ne jamais exposer secrets, tokens, cles privees
- Fait non verifie → `Information manquante`
- Incertitude → `Niveau de confiance: faible/moyen/eleve`
- Secret detecte (code, chat, shell, output outil) ACCIDENTELLEMENT → SESSION SUSPENDUE — afficher l'interruption, ne jamais continuer avant confirmation explicite
- Exception : "session securite active" declaree explicitement → suspension levee pour la session. Les valeurs ne s'affichent JAMAIS dans le chat meme en mode securite. Read tool sur MYSECRETS interdit meme en mode securite — utiliser Bash silencieux uniquement.
- identityShow: on (defaut owner) — agents utilisent leurs marqueurs visuels complets (prefixes, emojis). Consequence de kerneluser: true dans brain-compose.yml.

View File

@@ -92,6 +92,42 @@ if [ -d "$BRAIN_ROOT/wiki" ]; then
fi
fi
# --- Docs ---
echo ""
echo "── docs/ ───────────────────────────────────────"
if [ -d "$BRAIN_ROOT/docs" ]; then
if [ -z "$DRY" ]; then
mkdir -p "$TEMPLATE_DIR/docs"
rsync -a --delete "$BRAIN_ROOT/docs/" "$TEMPLATE_DIR/docs/"
fi
doc_count=$(ls "$BRAIN_ROOT/docs/"*.md 2>/dev/null | wc -l | tr -d ' ')
echo "$doc_count docs"
fi
# --- Contexts ---
echo ""
echo "── contexts/ ──────────────────────────────────"
if [ -d "$BRAIN_ROOT/contexts" ]; then
if [ -z "$DRY" ]; then
mkdir -p "$TEMPLATE_DIR/contexts"
rsync -a --delete "$BRAIN_ROOT/contexts/" "$TEMPLATE_DIR/contexts/"
fi
ctx_count=$(ls "$BRAIN_ROOT/contexts/"*.yml 2>/dev/null | wc -l | tr -d ' ')
echo "$ctx_count contexts"
fi
# --- Brain-UI (source + dist) ---
echo ""
echo "── brain-ui/ ──────────────────────────────────"
if [ -d "$BRAIN_ROOT/brain-ui" ]; then
if [ -z "$DRY" ]; then
rsync -a --delete \
--exclude='node_modules/' \
"$BRAIN_ROOT/brain-ui/" "$TEMPLATE_DIR/brain-ui/"
fi
echo " ✅ brain-ui (src + dist)"
fi
# --- Gitkeep ---
[ -z "$DRY" ] && mkdir -p "$TEMPLATE_DIR/locks" && \
touch "$TEMPLATE_DIR/locks/.gitkeep"

View File

@@ -19,7 +19,10 @@ if [ ! -f "$BRAIN_ROOT/brain-compose.local.yml" ]; then
MACHINE=$(hostname)
sed -i "s|<MACHINE_NAME>|$MACHINE|g" "$BRAIN_ROOT/brain-compose.local.yml"
sed -i "s|<YYYY-MM-DD>|$(date +%Y-%m-%d)|g" "$BRAIN_ROOT/brain-compose.local.yml"
echo "✅ brain-compose.local.yml cree"
# brain_name : dérivé du nom du dossier parent (ex: ~/Dev/Brain → brain)
BRAIN_NAME=$(basename "$BRAIN_ROOT" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
sed -i "s|<BRAIN_NAME>|$BRAIN_NAME|g" "$BRAIN_ROOT/brain-compose.local.yml"
echo "✅ brain-compose.local.yml cree (brain_name: $BRAIN_NAME)"
else
echo "✅ brain-compose.local.yml existe deja"
fi
@@ -52,19 +55,42 @@ if [ ! -f "$BRAIN_ROOT/profil/collaboration.md" ] && [ -f "$BRAIN_ROOT/profil/co
echo " → profil/collaboration.md cree depuis l'exemple"
fi
echo "✅ Satellites prets"
echo ""
echo " Les satellites sont des dossiers ou le brain ecrit :"
echo " todo/ → tes intentions de session"
echo " progression/ → ton parcours et tes metriques"
echo " toolkit/ → tes patterns valides en prod"
echo " reviews/ → audits de tes agents"
echo " Ils fonctionnent sans Git. Pour les versionner : docs/satellites.md"
# 3. Build dashboard
echo ""
echo "=== Dashboard ==="
if [ -d "$BRAIN_ROOT/brain-ui/dist" ]; then
if [ -d "$BRAIN_ROOT/brain-ui" ]; then
# Creer .env.local si absent — pointe vers brain-engine local
if [ ! -f "$BRAIN_ROOT/brain-ui/.env.local" ]; then
cat > "$BRAIN_ROOT/brain-ui/.env.local" << 'ENVEOF'
# VITE_BRAIN_API vide = requetes relatives (meme serveur)
# brain-engine sert l'UI ET l'API sur le meme port
VITE_BRAIN_API=
VITE_USE_MOCK=false
ENVEOF
echo "✅ brain-ui/.env.local cree"
fi
if [ -d "$BRAIN_ROOT/brain-ui/dist" ]; then
echo "✅ brain-ui deja build"
else
else
if command -v node &>/dev/null && command -v npm &>/dev/null; then
bash "$BRAIN_ROOT/brain-ui/build.sh"
echo "→ Build brain-ui..."
cd "$BRAIN_ROOT/brain-ui" && npm install --silent && npm run build && cd "$BRAIN_ROOT"
echo "✅ brain-ui build"
else
echo "⚠️ Node.js/npm absent — le dashboard ne sera pas disponible."
echo " Installe Node.js 18+ puis relance : bash brain-ui/build.sh"
echo " Installe Node.js 18+ puis relance setup.sh"
fi
fi
else
echo "⚠️ brain-ui/ absent — dashboard non disponible."
fi
# 3. Init brain-engine