From ff8eac0dfbea0ea91dd3ca1358f205b7044d6698 Mon Sep 17 00:00:00 2001 From: MaksTinyWorkshop Date: Tue, 31 Mar 2026 15:39:20 +0200 Subject: [PATCH] mcp: consolidate lot 1 metadata and planning --- knowledge/backend/patterns/auth.md | 11 + knowledge/backend/patterns/contracts.md | 11 + knowledge/backend/patterns/nestjs.md | 11 + knowledge/backend/risques/auth.md | 11 + knowledge/backend/risques/contracts.md | 11 + knowledge/backend/risques/nestjs.md | 11 + knowledge/backend/risques/prisma.md | 11 + knowledge/frontend/patterns/navigation.md | 12 +- knowledge/frontend/patterns/tests.md | 11 + knowledge/frontend/risques/navigation.md | 11 + knowledge/workflow/risques/story-tracking.md | 11 + mcp/leadtech_bmad_mcp/README.md | 6 + .../docs/implementation_plan.md | 144 ++++++++++++ .../docs/knowledge_metadata.md | 108 +++++++++ mcp/leadtech_bmad_mcp/docs/mcp_v1.md | 209 ++++++++++++++++++ .../src/leadtech_bmad_mcp/knowledge.py | 72 +++++- .../src/leadtech_bmad_mcp/schemas.py | 1 + .../src/leadtech_bmad_mcp/server.py | 28 ++- mcp/leadtech_bmad_mcp/tests/test_knowledge.py | 69 +++++- .../tests/test_server_patterns.py | 37 +++- 20 files changed, 788 insertions(+), 8 deletions(-) create mode 100644 mcp/leadtech_bmad_mcp/docs/implementation_plan.md create mode 100644 mcp/leadtech_bmad_mcp/docs/knowledge_metadata.md create mode 100644 mcp/leadtech_bmad_mcp/docs/mcp_v1.md diff --git a/knowledge/backend/patterns/auth.md b/knowledge/backend/patterns/auth.md index c81d9ec..86b089a 100644 --- a/knowledge/backend/patterns/auth.md +++ b/knowledge/backend/patterns/auth.md @@ -1,3 +1,14 @@ +--- +title: Backend — Patterns : Auth +domain: backend +bucket: patterns +tags: [auth, requestid, api-errors, sessions, tokens] +applies_to: [analysis, implementation, review, debug] +severity: high +validated_on: 2026-03-16 +source_projects: [app-alexandrie] +--- + # Backend — Patterns : Auth > Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet. diff --git a/knowledge/backend/patterns/contracts.md b/knowledge/backend/patterns/contracts.md index 5b43eea..d0bc548 100644 --- a/knowledge/backend/patterns/contracts.md +++ b/knowledge/backend/patterns/contracts.md @@ -1,3 +1,14 @@ +--- +title: Backend — Patterns : Contracts +domain: backend +bucket: patterns +tags: [contracts, zod, api, error-codes, monorepo] +applies_to: [analysis, implementation, review, architecture] +severity: high +validated_on: 2026-03-20 +source_projects: [app-alexandrie] +--- + # Backend — Patterns : Contracts > Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet. diff --git a/knowledge/backend/patterns/nestjs.md b/knowledge/backend/patterns/nestjs.md index 15d14b4..848a5df 100644 --- a/knowledge/backend/patterns/nestjs.md +++ b/knowledge/backend/patterns/nestjs.md @@ -1,3 +1,14 @@ +--- +title: Backend — Patterns : NestJS +domain: backend +bucket: patterns +tags: [nestjs, guards, auth, redis, quota] +applies_to: [analysis, implementation, review, debug] +severity: medium +validated_on: 2026-03-07 +source_projects: [app-alexandrie] +--- + # Backend — Patterns : NestJS > Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet. diff --git a/knowledge/backend/risques/auth.md b/knowledge/backend/risques/auth.md index ddae778..10ede0e 100644 --- a/knowledge/backend/risques/auth.md +++ b/knowledge/backend/risques/auth.md @@ -1,3 +1,14 @@ +--- +title: Backend — Risques & vigilance : Auth +domain: backend +bucket: risques +tags: [auth, guards, request-user, sessions, admin] +applies_to: [implementation, review, debug] +severity: high +validated_on: 2026-03-24 +source_projects: [app-alexandrie] +--- + # Backend — Risques & vigilance : Auth > Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour l'index complet. diff --git a/knowledge/backend/risques/contracts.md b/knowledge/backend/risques/contracts.md index d5474de..1df7d97 100644 --- a/knowledge/backend/risques/contracts.md +++ b/knowledge/backend/risques/contracts.md @@ -1,3 +1,14 @@ +--- +title: Backend — Risques & vigilance : Contracts +domain: backend +bucket: risques +tags: [contracts, zod, validation, error-codes, requestid] +applies_to: [analysis, implementation, review, debug] +severity: high +validated_on: 2026-03-24 +source_projects: [app-alexandrie] +--- + # Backend — Risques & vigilance : Contracts > Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour l'index complet. diff --git a/knowledge/backend/risques/nestjs.md b/knowledge/backend/risques/nestjs.md index 6880857..b95c366 100644 --- a/knowledge/backend/risques/nestjs.md +++ b/knowledge/backend/risques/nestjs.md @@ -1,3 +1,14 @@ +--- +title: Backend — Risques & vigilance : NestJS +domain: backend +bucket: risques +tags: [nestjs, controllers, guards, providers, review] +applies_to: [implementation, review, debug] +severity: high +validated_on: 2026-03-30 +source_projects: [app-alexandrie] +--- + # Backend — Risques & vigilance : NestJS > Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour l'index complet. diff --git a/knowledge/backend/risques/prisma.md b/knowledge/backend/risques/prisma.md index cc7ac88..852801a 100644 --- a/knowledge/backend/risques/prisma.md +++ b/knowledge/backend/risques/prisma.md @@ -1,3 +1,14 @@ +--- +title: Backend — Risques & vigilance : Prisma +domain: backend +bucket: risques +tags: [prisma, transactions, tenant, schema, race-condition] +applies_to: [implementation, review, debug, architecture] +severity: high +validated_on: 2026-03-23 +source_projects: [app-template-resto, app-alexandrie] +--- + # Backend — Risques & vigilance : Prisma > Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour l'index complet. diff --git a/knowledge/frontend/patterns/navigation.md b/knowledge/frontend/patterns/navigation.md index 5d474bc..8526cdf 100644 --- a/knowledge/frontend/patterns/navigation.md +++ b/knowledge/frontend/patterns/navigation.md @@ -1,3 +1,14 @@ +--- +title: Frontend — Patterns : Navigation +domain: frontend +bucket: patterns +tags: [navigation, react, expo-router, useeffect, async] +applies_to: [analysis, implementation, review] +severity: medium +validated_on: 2026-03-21 +source_projects: [app-template-resto, app-alexandrie] +--- + # Frontend — Patterns : Navigation > Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet. @@ -165,4 +176,3 @@ return ( ); ``` - diff --git a/knowledge/frontend/patterns/tests.md b/knowledge/frontend/patterns/tests.md index e6f202d..1a9330f 100644 --- a/knowledge/frontend/patterns/tests.md +++ b/knowledge/frontend/patterns/tests.md @@ -1,3 +1,14 @@ +--- +title: Frontend — Patterns : Tests +domain: frontend +bucket: patterns +tags: [tests, react-native, jest, styles, ui] +applies_to: [implementation, review] +severity: medium +validated_on: 2026-03-19 +source_projects: [app-alexandrie] +--- + # Frontend — Patterns : Tests > Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet. diff --git a/knowledge/frontend/risques/navigation.md b/knowledge/frontend/risques/navigation.md index e45f54e..ebee22d 100644 --- a/knowledge/frontend/risques/navigation.md +++ b/knowledge/frontend/risques/navigation.md @@ -1,3 +1,14 @@ +--- +title: Frontend — Risques & vigilance : Navigation +domain: frontend +bucket: risques +tags: [navigation, expo-router, zustand, useeffect, deep-link] +applies_to: [implementation, review, debug] +severity: high +validated_on: 2026-03-25 +source_projects: [app-alexandrie] +--- + # Frontend — Risques & vigilance : Navigation > Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/risques/README.md` pour l'index complet. diff --git a/knowledge/workflow/risques/story-tracking.md b/knowledge/workflow/risques/story-tracking.md index c3e1865..6b2b98e 100644 --- a/knowledge/workflow/risques/story-tracking.md +++ b/knowledge/workflow/risques/story-tracking.md @@ -1,3 +1,14 @@ +--- +title: Workflow — Risques & vigilance : Story tracking +domain: workflow +bucket: risques +tags: [bmad, story, file-list, review, completion] +applies_to: [analysis, implementation, review] +severity: high +validated_on: 2026-03-31 +source_projects: [app-alexandrie, app-template-resto] +--- + # Workflow — Risques & vigilance : Story tracking > Extrait de la base de connaissance Lead_tech. Voir `knowledge/workflow/risques/README.md` pour l'index complet. diff --git a/mcp/leadtech_bmad_mcp/README.md b/mcp/leadtech_bmad_mcp/README.md index 82d2654..624b2cf 100644 --- a/mcp/leadtech_bmad_mcp/README.md +++ b/mcp/leadtech_bmad_mcp/README.md @@ -4,6 +4,12 @@ Serveur MCP **sidecar** pour brancher la base Lead_tech dans un workflow BMAD sa Etat actuel : **prototype exploitable** pour un rollout advisory. +Documents de référence phase 1 : + +- `docs/mcp_v1.md` — contrat fonctionnel stable pour les tools/resources +- `docs/knowledge_metadata.md` — format de front matter pour `knowledge/` +- `docs/implementation_plan.md` — plan de chantier et checklist inter-sessions + ## Objectif - BMAD garde l'orchestration (story, roles, statut, handoff). diff --git a/mcp/leadtech_bmad_mcp/docs/implementation_plan.md b/mcp/leadtech_bmad_mcp/docs/implementation_plan.md new file mode 100644 index 0000000..c0a69a9 --- /dev/null +++ b/mcp/leadtech_bmad_mcp/docs/implementation_plan.md @@ -0,0 +1,144 @@ +# leadtech-bmad-mcp — Plan d'implementation + +Ce document sert de **plan de chantier vivant** pour transformer le prototype actuel en MCP propre, stable et exploitable dans BMAD. + +Mode d'usage : + +- cocher les taches terminees +- mettre a jour le statut des lots en debut/fin de session +- ajouter les decisions structurantes dans `40_decisions_et_archi.md` si besoin +- garder ici le suivi operationnel, pas les longues explications + +--- + +## Vue d'ensemble + +| Lot | Objectif | Statut | +| --- | --- | --- | +| Lot 1 | Contrat MCP v1 + metadonnees `knowledge` + compatibilite loader | En cours avance | +| Lot 2 | Index compile local + branchement de la recherche MCP dessus | A faire | +| Lot 3 | Gates configurables + packaging + rollout BMAD | A faire | + +--- + +## Lot 1 — Contrat v1 et base structuree + +### Objectif + +Stabiliser le contrat du MCP et preparer un corpus `knowledge/` assez structure pour servir de base au futur index. + +### Livrables + +- [x] Documenter le contrat MCP v1 +- [x] Documenter le format de front matter `knowledge/` +- [x] Ajouter la lecture du front matter dans le loader +- [x] Exposer un champ structure `matched_docs` dans `get_guidance` +- [x] Ajouter des tests sur le parsing des metadonnees +- [x] Annoter un premier noyau de documents critiques + +### Noyau pilote vise + +- [x] `knowledge/backend/patterns/auth.md` +- [x] `knowledge/backend/patterns/contracts.md` +- [x] `knowledge/backend/patterns/nestjs.md` +- [x] `knowledge/backend/risques/auth.md` +- [x] `knowledge/backend/risques/contracts.md` +- [x] `knowledge/backend/risques/nestjs.md` +- [x] `knowledge/backend/risques/prisma.md` +- [x] `knowledge/frontend/patterns/navigation.md` +- [x] `knowledge/frontend/patterns/tests.md` +- [x] `knowledge/frontend/risques/navigation.md` +- [x] `knowledge/workflow/risques/story-tracking.md` + +### Reste a faire avant cloture complete du lot + +- [ ] Verifier si `knowledge/backend/patterns/prisma.md` doit aussi entrer dans le noyau pilote +- [ ] Verifier si `knowledge/frontend/risques/tests.md` doit aussi entrer dans le noyau pilote +- [ ] Faire un commit de cloture explicite du Lot 1 + +### Critere de fin + +- le contrat v1 est stable +- un noyau representatif de docs critiques est structure +- la recherche MCP sait exploiter les metadonnees sans casser le comportement existant + +--- + +## Lot 2 — Index compile local + +### Objectif + +Remplacer le scan Markdown a la volee par un index local plus rapide, plus fiable et plus facile a faire evoluer. + +### Taches + +- [ ] Definir le format de l'index (JSON d'abord) +- [ ] Creer un script de build d'index +- [ ] Indexer les docs `knowledge/*` +- [ ] Indexer les docs globaux `10_*`, `40_*`, `90_*` +- [ ] Prevoir un mode fallback si l'index n'existe pas +- [ ] Rebrancher `search_knowledge()` sur l'index +- [ ] Rebrancher `search_global_docs()` sur l'index +- [ ] Ajouter des tests d'integration sur un mini corpus indexe + +### Livrables attendus + +- `src/leadtech_bmad_mcp/indexer.py` +- un artefact d'index local versionnable ou regenerable +- documentation de rebuild + +### Critere de fin + +- les tools de recherche utilisent d'abord l'index +- le fallback texte brut reste disponible pour ne pas bloquer le dev + +--- + +## Lot 3 — Gates configurables et industrialisation + +### Objectif + +Sortir les regles du code dur, rendre l'installation reproductible, puis cabler proprement le rollout BMAD. + +### Taches + +- [ ] Extraire les gates dans une config versionnee +- [ ] Distinguer advisory et blocking dans la config +- [ ] Ajouter des tests sur les faux positifs/faux negatifs des gates +- [ ] Stabiliser l'installation `pip install -e ".[dev]"` +- [ ] Ajouter une doc machine vierge / quickstart +- [ ] Preparer un rollout BMAD advisory +- [ ] Definir 2-3 blocages stricts seulement apres validation + +### Livrables attendus + +- `config/gates.yaml` +- quickstart dev local +- procedure de rollout BMAD + +### Critere de fin + +- modifier une regle ne demande plus un patch Python +- une autre machine peut installer et lancer le serveur sans bricolage +- BMAD sait quand appeler quels tools et comment interpreter leur sortie + +--- + +## Journal de progression + +### 2026-03-31 + +- Lot 1 demarre +- contrat `mcp_v1.md` ajoute +- convention `knowledge_metadata.md` ajoutee +- loader front matter ajoute +- `matched_docs` ajoute a `get_guidance` +- noyau pilote annote sur backend, frontend et workflow + +--- + +## Regles de maintenance de ce plan + +- un lot passe a `Termine` uniquement quand son critere de fin est satisfait +- une case ne se coche pas si le code est seulement commence +- si un arbitrage durable apparait, l'inscrire aussi dans `40_decisions_et_archi.md` diff --git a/mcp/leadtech_bmad_mcp/docs/knowledge_metadata.md b/mcp/leadtech_bmad_mcp/docs/knowledge_metadata.md new file mode 100644 index 0000000..1185fe9 --- /dev/null +++ b/mcp/leadtech_bmad_mcp/docs/knowledge_metadata.md @@ -0,0 +1,108 @@ +# leadtech-bmad-mcp — Métadonnées `knowledge/` + +Ce document définit le **front matter minimal** attendu pour rendre la base `knowledge/` mieux exploitable par le MCP. + +## Objectif + +- améliorer le ranking de recherche +- permettre des filtres structurés +- préparer un futur index compilé + +## Format retenu + +Chaque document `knowledge/*/*.md` peut commencer par : + +```md +--- +title: Backend — Patterns : NestJS +domain: backend +bucket: patterns +tags: [nestjs, guards, auth, redis] +applies_to: [analysis, implementation, review, debug] +severity: medium +validated_on: 2026-03-07 +source_projects: [app-alexandrie] +--- +``` + +Le front matter est **optionnel** dans la phase 1. + +Le loader doit : + +- fonctionner avec ou sans front matter +- ignorer proprement les champs inconnus +- continuer à servir le markdown brut si demandé en resource + +## Champs v1 + +### `title` + +- titre logique du document +- type : `string` + +### `domain` + +- domaine principal +- type : `backend | frontend | ux | n8n | product | workflow` + +### `bucket` + +- type de document +- type : `patterns | risques` + +### `tags` + +- mots-clés de recherche +- type : `string[]` + +### `applies_to` + +- moments du workflow où le document est pertinent +- type : `analysis | implementation | review | debug | architecture` +- type de stockage : `string[]` + +### `severity` + +- niveau de criticité métier ou technique du document +- type : `low | medium | high` + +### `validated_on` + +- date de validation terrain +- type : `YYYY-MM-DD` + +### `source_projects` + +- projets ayant validé le pattern ou révélé le risque +- type : `string[]` + +## Règles de gouvernance + +- les README d'index n'ont pas besoin de front matter dans la phase 1 +- priorité aux documents les plus consultés ou les plus critiques +- ne pas bloquer une capitalisation parce que le front matter n'existe pas encore + +## Politique de migration + +Phase 1 : +- ajouter le front matter à un petit noyau de documents pilotes + +Noyau pilote actuellement couvert : + +- `knowledge/backend/patterns/auth.md` +- `knowledge/backend/patterns/contracts.md` +- `knowledge/backend/patterns/nestjs.md` +- `knowledge/backend/risques/auth.md` +- `knowledge/backend/risques/contracts.md` +- `knowledge/backend/risques/nestjs.md` +- `knowledge/backend/risques/prisma.md` +- `knowledge/frontend/patterns/navigation.md` +- `knowledge/frontend/patterns/tests.md` +- `knowledge/frontend/risques/navigation.md` +- `knowledge/workflow/risques/story-tracking.md` + +Phase 2 : +- généraliser aux documents backend, frontend et workflow les plus actifs + +Phase 3 : +- exiger ces métadonnées pour l'index compilé diff --git a/mcp/leadtech_bmad_mcp/docs/mcp_v1.md b/mcp/leadtech_bmad_mcp/docs/mcp_v1.md new file mode 100644 index 0000000..f75bcd5 --- /dev/null +++ b/mcp/leadtech_bmad_mcp/docs/mcp_v1.md @@ -0,0 +1,209 @@ +# leadtech-bmad-mcp — Contrat v1 + +Ce document fige le **contrat fonctionnel v1** du sidecar MCP Lead_tech. + +Objectif : + +- stabiliser ce que le serveur promet aux agents BMAD +- éviter les changements de payload implicites +- préparer les itérations suivantes (index compilé, gates configurables) sans casser les prompts existants + +## Positionnement + +- BMAD garde l'orchestration +- Lead_tech garde la doctrine et la validation humaine +- le MCP sidecar fournit une couche outillée de lecture, guidance, gate qualité, et capitalisation + +## Version fonctionnelle + +- Nom logique : `leadtech-bmad-mcp` +- Version de contrat : `v1` +- Statut : advisory-first + +## Outils v1 + +### `get_guidance(domain, task_type, story_text?, keywords?, max_items?)` + +Rôle : +- retrouver les patterns, risques et docs globaux pertinents pour une story + +Entrées : +- `domain` : `backend | frontend | ux | n8n | product | workflow` +- `task_type` : `analysis | implementation | review | debug | architecture` +- `story_text` : texte libre +- `keywords` : liste de mots-clés optionnelle +- `max_items` : entier optionnel + +Sortie : +- `must_do[]` +- `should_do[]` +- `red_flags[]` +- `blocking_issues[]` +- `references[]` +- `confidence` +- `gates[]` + +Règle : +- advisory par défaut +- pas d'écriture + +### `validate_plan(domain, plan_text, agent_role?, strict?)` + +Rôle : +- vérifier qu'un plan BMAD couvre les gates Lead_tech les plus importantes + +Règle : +- `strict=true` peut produire des `blocking_issues` +- `strict=false` reste advisory + +### `validate_patch(domain, diff_text, changed_files?, strict?)` + +Rôle : +- contrôler un diff par rapport à des gates Lead_tech + +Règle : +- détecte les signaux faibles, pas une preuve formelle de conformité +- un résultat vide ne remplace jamais une review humaine + +### `emit_checklist(agent_role, domain, story_text?)` + +Rôle : +- produire une checklist opérationnelle par rôle BMAD + +### `propose_capitalization(project_name, target_file, why, proposal, dry_run?)` + +Rôle : +- préparer une entrée `FILE_UPDATE_PROPOSAL` pour `95_a_capitaliser.md` + +Règle : +- `dry_run=true` par défaut +- écriture réelle uniquement si `LEADTECH_MCP_ALLOW_WRITE=1` + +### `triage_capitalization(project_filter?, max_entries?)` + +Rôle : +- analyser les entrées du buffer de capitalisation + +Statut : +- heuristique, non décisionnaire + +### `route_to_project_memory(project_name, section, content, dry_run?)` + +Rôle : +- router un apprentissage de portée projet vers le `CLAUDE.md` du projet + +Règle : +- écriture réelle uniquement si `LEADTECH_MCP_ALLOW_WRITE=1` + +## Resources v1 + +- `leadtech://index` +- `leadtech://capitalisation/pending` +- `leadtech://projects/conf` +- `leadtech://knowledge/{domain}/{bucket}/{slug}` +- `leadtech://global/architecture` +- `leadtech://global/debug` +- `leadtech://global/conventions` + +## Payload commun + +Les tools de guidance et de validation retournent un payload homogène : + +```json +{ + "must_do": [], + "should_do": [], + "red_flags": [], + "blocking_issues": [], + "confidence": "MEDIUM", + "references": [], + "matched_docs": [], + "gates": [] +} +``` + +## Niveaux de sévérité + +- `must_do` : action attendue fortement recommandée +- `should_do` : amélioration ou vérification utile +- `red_flags` : signal de risque ou d'incertitude +- `blocking_issues` : empêche la progression en mode strict + +## Confiance + +- `HIGH` +- `MEDIUM` +- `LOW` + +Règle : +- la confiance exprime la qualité du matching heuristique, pas la vérité terrain + +## Références + +`references[]` contient : + +```json +{ + "path": "/abs/path/doc.md", + "reason": "Match patterns score=7" +} +``` + +Règle : +- toujours renvoyer des chemins absolus si possible +- référencer la source, pas uniquement un résumé + +## Matched docs + +`matched_docs[]` expose les meilleurs documents remontés par le moteur de guidance. + +Structure cible v1 : + +```json +{ + "path": "/abs/path/doc.md", + "bucket": "patterns", + "title": "Backend — Patterns : NestJS", + "score": "12", + "severity": "medium", + "applies_to": "analysis, implementation, review, debug", + "tags": "nestjs, guards, auth, redis, quota", + "read_uri": "leadtech://knowledge/backend/patterns/nestjs" +} +``` + +Règle : +- champ informatif, non bloquant +- utile pour les agents qui veulent afficher ou prioriser explicitement les docs remontés + +## Erreurs + +Conventions v1 : + +- tool de lecture : lever une erreur explicite si ressource introuvable +- tool d'écriture : retourner un objet `{ "error": "..." }` si écriture désactivée ou cible introuvable +- ne jamais écrire dans `knowledge/*` + +## Sécurité d'écriture + +Écritures autorisées uniquement vers : + +- `95_a_capitaliser.md` +- `CLAUDE.md` projet + +Écritures interdites : + +- `knowledge/*` +- `10_*` +- `40_*` +- `90_*` + +## Compatibilité BMAD + +Ce contrat v1 est pensé pour : + +- enrichir une story à l'entrée +- bloquer certains cas en pre-implémentation ou post-patch +- tracer la décision humaine dans la story + +Les prompts BMAD doivent considérer ce contrat comme stable tant qu'un document `v2` n'existe pas. diff --git a/mcp/leadtech_bmad_mcp/src/leadtech_bmad_mcp/knowledge.py b/mcp/leadtech_bmad_mcp/src/leadtech_bmad_mcp/knowledge.py index 11750a1..e5793d1 100644 --- a/mcp/leadtech_bmad_mcp/src/leadtech_bmad_mcp/knowledge.py +++ b/mcp/leadtech_bmad_mcp/src/leadtech_bmad_mcp/knowledge.py @@ -3,6 +3,7 @@ from __future__ import annotations import os from dataclasses import dataclass from pathlib import Path +from typing import Any VALID_DOMAINS = {"backend", "frontend", "ux", "n8n", "product", "workflow"} @@ -17,6 +18,13 @@ class LeadtechPaths: projects_conf: Path +@dataclass(frozen=True) +class KnowledgeDocument: + path: Path + metadata: dict[str, Any] + body: str + + def get_paths() -> LeadtechPaths: root = Path(os.getenv("LEADTECH_ROOT", "/srv/helpers/_Assistant_Lead_Tech")).resolve() return LeadtechPaths( @@ -52,6 +60,56 @@ def read_text(path: Path) -> str: EXCERPT_LENGTH = 400 +def _coerce_metadata_scalar(value: str) -> Any: + raw = value.strip() + if not raw: + return "" + + low = raw.lower() + if low in {"true", "false"}: + return low == "true" + + if raw.startswith("[") and raw.endswith("]"): + inner = raw[1:-1].strip() + if not inner: + return [] + return [item.strip().strip("'\"") for item in inner.split(",") if item.strip()] + + return raw.strip("'\"") + + +def parse_front_matter(content: str) -> tuple[dict[str, Any], str]: + if not content.startswith("---\n"): + return {}, content + + lines = content.splitlines() + end_idx = None + for idx in range(1, len(lines)): + if lines[idx].strip() == "---": + end_idx = idx + break + + if end_idx is None: + return {}, content + + metadata: dict[str, Any] = {} + for line in lines[1:end_idx]: + stripped = line.strip() + if not stripped or stripped.startswith("#") or ":" not in stripped: + continue + key, value = stripped.split(":", 1) + metadata[key.strip()] = _coerce_metadata_scalar(value) + + body = "\n".join(lines[end_idx + 1 :]).lstrip("\n") + return metadata, body + + +def read_knowledge_document(path: Path) -> KnowledgeDocument: + raw = read_text(path) + metadata, body = parse_front_matter(raw) + return KnowledgeDocument(path=path, metadata=metadata, body=body) + + def _extract_excerpt(content: str, tokens: list[str]) -> str: """Retourne un extrait centré sur la première occurrence d'un token, ou le début du fichier.""" low = content.lower() @@ -78,17 +136,23 @@ def search_knowledge(domain: str, query: str, bucket: str | None = None, max_ite for b in buckets: for file_path in list_domain_files(domain, b): - content = read_text(file_path) - score = sum(content.lower().count(tok) for tok in tokens) + doc = read_knowledge_document(file_path) + body_low = doc.body.lower() + metadata_text = " ".join(str(value) for value in doc.metadata.values()).lower() + score = sum(body_low.count(tok) for tok in tokens) + score += sum(metadata_text.count(tok) * 3 for tok in tokens) if score <= 0: continue out.append( { "path": str(file_path), "bucket": b, - "title": file_path.stem, + "title": str(doc.metadata.get("title", file_path.stem)), "score": str(score), - "excerpt": _extract_excerpt(content, tokens), + "excerpt": _extract_excerpt(doc.body, tokens), + "tags": ", ".join(doc.metadata.get("tags", [])), + "severity": str(doc.metadata.get("severity", "")), + "applies_to": ", ".join(doc.metadata.get("applies_to", [])), } ) diff --git a/mcp/leadtech_bmad_mcp/src/leadtech_bmad_mcp/schemas.py b/mcp/leadtech_bmad_mcp/src/leadtech_bmad_mcp/schemas.py index 2efc4dc..6e6fe2c 100644 --- a/mcp/leadtech_bmad_mcp/src/leadtech_bmad_mcp/schemas.py +++ b/mcp/leadtech_bmad_mcp/src/leadtech_bmad_mcp/schemas.py @@ -16,6 +16,7 @@ def empty_gate_output() -> dict[str, Any]: "blocking_issues": [], "confidence": CONFIDENCE_MEDIUM, "references": [], + "matched_docs": [], } diff --git a/mcp/leadtech_bmad_mcp/src/leadtech_bmad_mcp/server.py b/mcp/leadtech_bmad_mcp/src/leadtech_bmad_mcp/server.py index 4e6c008..b7fa876 100644 --- a/mcp/leadtech_bmad_mcp/src/leadtech_bmad_mcp/server.py +++ b/mcp/leadtech_bmad_mcp/src/leadtech_bmad_mcp/server.py @@ -45,6 +45,19 @@ def _base_output() -> dict: return payload +def _metadata_label(item: dict[str, str]) -> str: + parts: list[str] = [] + if item.get("severity"): + parts.append(f"severity={item['severity']}") + if item.get("applies_to"): + parts.append(f"applies_to={item['applies_to']}") + if item.get("tags"): + parts.append(f"tags={item['tags']}") + if not parts: + return "" + return " [" + " | ".join(parts) + "]" + + @mcp.resource("leadtech://index") def resource_index() -> str: return read_text(get_paths().root / "00_INDEX.md") @@ -89,12 +102,25 @@ def get_guidance(domain: Domain, task_type: TaskType, story_text: str = "", keyw for item in matches: slug = Path(item["path"]).stem read_uri = f"leadtech://knowledge/{domain}/{item['bucket']}/{slug}" - label = f"{item['title']} — {item['excerpt']} [lire complet: {read_uri}]" + metadata = _metadata_label(item) + label = f"{item['title']}{metadata} — {item['excerpt']} [lire complet: {read_uri}]" if item["bucket"] == "patterns": out["must_do"].append(f"Appliquer: {label}") else: out["red_flags"].append(f"Surveiller: {label}") add_reference(out, item["path"], f"Match {item['bucket']} score={item['score']}") + out["matched_docs"].append( + { + "path": item["path"], + "bucket": item["bucket"], + "title": item["title"], + "score": item["score"], + "severity": item.get("severity", ""), + "applies_to": item.get("applies_to", ""), + "tags": item.get("tags", ""), + "read_uri": read_uri, + } + ) # Fichiers globaux (decisions, postmortems, conventions) global_matches = search_global_docs(query=query, max_items=3) diff --git a/mcp/leadtech_bmad_mcp/tests/test_knowledge.py b/mcp/leadtech_bmad_mcp/tests/test_knowledge.py index 7e4989a..22b5b9b 100644 --- a/mcp/leadtech_bmad_mcp/tests/test_knowledge.py +++ b/mcp/leadtech_bmad_mcp/tests/test_knowledge.py @@ -10,9 +10,10 @@ from leadtech_bmad_mcp.knowledge import ( search_knowledge, search_global_docs, read_knowledge_doc, + read_knowledge_document, _extract_excerpt, + parse_front_matter, LeadtechPaths, - get_paths, ) @@ -122,6 +123,30 @@ def test_search_knowledge_single_bucket(tmp_path): assert all(r["bucket"] == "risques" for r in results) +def test_search_knowledge_uses_front_matter_tags(tmp_path): + knowledge = tmp_path / "knowledge" + (knowledge / "backend" / "patterns").mkdir(parents=True) + (knowledge / "backend" / "patterns" / "nestjs.md").write_text( + "---\n" + "title: Backend — Patterns : NestJS\n" + "tags: [guards, auth]\n" + "---\n\n" + "# NestJS\n\n" + "Texte volontairement sans le mot cle demande.\n", + encoding="utf-8", + ) + paths = LeadtechPaths( + root=tmp_path, + knowledge=knowledge, + capitalisation=tmp_path / "95_a_capitaliser.md", + projects_conf=tmp_path / "_projects.conf", + ) + with patch("leadtech_bmad_mcp.knowledge.get_paths", return_value=paths): + results = search_knowledge("backend", "guards") + assert results + assert results[0]["title"] == "Backend — Patterns : NestJS" + + # --------------------------------------------------------------------------- # read_knowledge_doc # --------------------------------------------------------------------------- @@ -147,6 +172,23 @@ def test_read_knowledge_doc_traversal_blocked(tmp_path): read_knowledge_doc("backend", "patterns", "../../etc/passwd") +def test_read_knowledge_document_splits_metadata_and_body(tmp_path): + file_path = tmp_path / "doc.md" + file_path.write_text( + "---\n" + "title: Test Doc\n" + "tags: [alpha, beta]\n" + "---\n\n" + "# Heading\n\n" + "Contenu principal.\n", + encoding="utf-8", + ) + doc = read_knowledge_document(file_path) + assert doc.metadata["title"] == "Test Doc" + assert doc.metadata["tags"] == ["alpha", "beta"] + assert doc.body.startswith("# Heading") + + # --------------------------------------------------------------------------- # _extract_excerpt # --------------------------------------------------------------------------- @@ -176,6 +218,31 @@ def test_extract_excerpt_length_bounded(): assert len(excerpt) <= 500 +def test_parse_front_matter_returns_body_unchanged_without_header(): + content = "# Heading\n\nPlain body" + metadata, body = parse_front_matter(content) + assert metadata == {} + assert body == content + + +def test_parse_front_matter_parses_lists_and_scalars(): + content = ( + "---\n" + "title: Demo\n" + "tags: [alpha, beta]\n" + "severity: high\n" + "enabled: true\n" + "---\n\n" + "Body\n" + ) + metadata, body = parse_front_matter(content) + assert metadata["title"] == "Demo" + assert metadata["tags"] == ["alpha", "beta"] + assert metadata["severity"] == "high" + assert metadata["enabled"] is True + assert body == "Body\n" + + # --------------------------------------------------------------------------- # search_global_docs # --------------------------------------------------------------------------- diff --git a/mcp/leadtech_bmad_mcp/tests/test_server_patterns.py b/mcp/leadtech_bmad_mcp/tests/test_server_patterns.py index a29e713..ce1e409 100644 --- a/mcp/leadtech_bmad_mcp/tests/test_server_patterns.py +++ b/mcp/leadtech_bmad_mcp/tests/test_server_patterns.py @@ -32,7 +32,7 @@ def _mock_mcp_module(): _mock_mcp_module() -from leadtech_bmad_mcp.server import validate_plan, validate_patch # noqa: E402 +from leadtech_bmad_mcp.server import get_guidance, validate_plan, validate_patch # noqa: E402 from leadtech_bmad_mcp.knowledge import LeadtechPaths # noqa: E402 @@ -88,6 +88,41 @@ class TestValidatePlanContracts: assert not contract_blocks +class TestGetGuidanceMetadata: + def test_exposes_matched_docs_with_metadata(self, tmp_path): + paths = _fake_paths(tmp_path) + with patch( + "leadtech_bmad_mcp.server.search_knowledge", + return_value=[ + { + "path": str(tmp_path / "knowledge" / "backend" / "patterns" / "nestjs.md"), + "bucket": "patterns", + "title": "Backend — Patterns : NestJS", + "score": "9", + "excerpt": "Pattern sur les guards globaux.", + "severity": "medium", + "applies_to": "analysis, implementation, review", + "tags": "nestjs, guards, auth", + } + ], + ), patch( + "leadtech_bmad_mcp.server.search_global_docs", + return_value=[], + ), patch( + "leadtech_bmad_mcp.server.get_paths", + return_value=paths, + ): + result = get_guidance("backend", "implementation", story_text="Ajouter un guard NestJS") + + assert result["matched_docs"] + matched = result["matched_docs"][0] + assert matched["bucket"] == "patterns" + assert matched["severity"] == "medium" + assert "implementation" in matched["applies_to"] + assert matched["read_uri"] == "leadtech://knowledge/backend/patterns/nestjs" + assert any("severity=medium" in item for item in result["must_do"]) + + class TestValidatePlanRequestId: def test_suggests_requestid_when_error_without_requestid(self, tmp_path): paths = _fake_paths(tmp_path)