diff --git a/70_templates/bmad_post_install/workflows/create-story-parallelization.xmlfrag b/70_templates/bmad_post_install/workflows/create-story-parallelization.xmlfrag new file mode 100644 index 0000000..0d4d12e --- /dev/null +++ b/70_templates/bmad_post_install/workflows/create-story-parallelization.xmlfrag @@ -0,0 +1,6 @@ + + Every created story must explicitly define its parallelization metadata. + Set `Parallel-safe`, `Depends-on`, and `Can-run-with` in the story header based on real technical dependencies, not optimism. + If the story depends on non-merged code from another story, set `Parallel-safe: false` and fill `Depends-on` explicitly. + Use `Can-run-with` only for stories that can be developed in parallel without merge or logic conflicts. + If multiple stories are technically linked, keep them on the same branch until they are independently testable, reviewable, and mergeable. diff --git a/70_templates/bmad_post_install/workflows/dev-story-parallelization-check.xmlfrag b/70_templates/bmad_post_install/workflows/dev-story-parallelization-check.xmlfrag new file mode 100644 index 0000000..8c86f16 --- /dev/null +++ b/70_templates/bmad_post_install/workflows/dev-story-parallelization-check.xmlfrag @@ -0,0 +1,3 @@ + + Before implementation, read `Parallel-safe`, `Depends-on`, and `Can-run-with` from the story file. + If metadata conflicts with the actual code state or branch reality, stop and reconcile the story before coding. diff --git a/70_templates/bmad_post_install/workflows/story-template-frontmatter.mdfrag b/70_templates/bmad_post_install/workflows/story-template-frontmatter.mdfrag new file mode 100644 index 0000000..1834999 --- /dev/null +++ b/70_templates/bmad_post_install/workflows/story-template-frontmatter.mdfrag @@ -0,0 +1,3 @@ +Parallel-safe: false +Depends-on: ~ +Can-run-with: ~ diff --git a/80_bmad/process_llm_et_parallelisation.md b/80_bmad/process_llm_et_parallelisation.md index a1558bb..e5515ea 100644 --- a/80_bmad/process_llm_et_parallelisation.md +++ b/80_bmad/process_llm_et_parallelisation.md @@ -24,6 +24,7 @@ Si l'état réel du code, du `.md` et du sprint diverge, la story n'est pas sous --- + ## Convention : métadonnées de parallélisation dans le template story - Objectif : rendre explicite si une story peut être développée en parallèle d'une autre. @@ -50,9 +51,16 @@ Can-run-with: ~ - `Depends-on` documente une dépendance de code réelle - `Can-run-with` documente les stories compatibles en exécution parallèle +### Règle de branche + +- Si plusieurs stories sont techniquement liées, elles restent sur la même branche tant que leur séparation n'est pas réellement sûre +- Des branches séparées ne sont justifiées que si chaque story peut être testée, reviewée et mergée indépendamment +- En cas de doute, préférer une seule branche avec plusieurs commits propres plutôt que deux branches artificiellement parallèles + --- + ## Convention : synchronisation obligatoire `.md` ↔ `sprint-status` - Objectif : éviter qu'une story soit considérée terminée alors que ses artefacts d'état divergent. @@ -75,6 +83,7 @@ Can-run-with: ~ --- + ## Anti-pattern : multi-sessions LLM sur une même story sans handoff ### Risques diff --git a/95_a_capitaliser.md b/95_a_capitaliser.md index 6759abb..245aa10 100644 --- a/95_a_capitaliser.md +++ b/95_a_capitaliser.md @@ -165,3 +165,68 @@ Ce mécanisme permet : - d'éviter la pollution de la base de connaissance - de capitaliser progressivement l'expérience des projets - de garder `Lead_tech` cohérent et fiable. + +--- + +2026-03-10 — app-alexandrie + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_patterns_valides.md + +Pourquoi : +Quand une story métier demande un pouvoir interne limité, ajouter un rôle minimal persistant + un guard dédié permet de sécuriser l’authz sans ouvrir prématurément un RBAC complet. Le pattern a été validé ici pour la publication admin de contenus. + +Proposition : +Pattern "Autorisation interne minimale sans RBAC complet" : introduire un enum de rôle simple côté source de vérité backend (ex. `USER | ADMIN`), le propager dans la session/auth, puis créer un décorateur + guard dédiés à la capacité sensible. Éviter les booléens front, les emails hardcodés et les `if` dispersés dans les contrôleurs. + +--- + +2026-03-10 — app-alexandrie + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_frontend_patterns_valides.md + +Pourquoi : +Pour une capacité admin mince sur mobile, une route dédiée légère branchée sur le domaine existant et un refresh explicite du store après mutation permettent de rester testable et robuste sans lancer un back-office séparé. + +Proposition : +Pattern "UI admin légère sur domaine existant" : pour une action interne simple (publication, activation, modération légère), ajouter une route dédiée minimale qui réutilise le service/store métier existant, afficher le statut courant, bloquer les actions concurrentes avec un flag `isUpdating*`, et déclencher un refresh explicite des vues impactées après succès au lieu d’un `fire-and-forget`. + +--- +2026-03-10 — app-alexandrie + +FILE_UPDATE_PROPOSAL +Fichier cible : 90_debug_et_postmortem.md + +Pourquoi : +`tsx` (esbuild) ignore `emitDecoratorMetadata` — l'injection par type NestJS est silencieusement `undefined` au runtime, même avec `emitDecoratorMetadata: true` dans tsconfig.json. Bug difficile à diagnostiquer car aucune erreur de compilation. + +Proposition : + +## tsx + NestJS : injection par type cassée silencieusement + +**Contexte :** app-alexandrie, Epic 3, 2026-03-10 + +**Symptôme :** `TypeError: Cannot read properties of undefined (reading 'get')` dans le constructeur d'un service NestJS. `this.config` (de type `ConfigService`) est `undefined` malgré `@Injectable()` et `ConfigModule` correctement configuré. + +**Cause racine :** `tsx` utilise esbuild pour transpiler TypeScript. esbuild ignore `emitDecoratorMetadata` même si activé dans tsconfig. NestJS ne peut donc pas résoudre les types pour l'injection de dépendances par type (`constructor(private config: ConfigService)`). + +**Conditions :** runner = `tsx watch`, `module: nodenext` dans tsconfig (requis pour Prisma v7 ESM), `emitDecoratorMetadata: true` présent mais ignoré. + +**Fix :** Remplacer l'injection `ConfigService` par `process.env['VAR']` directement dans le constructeur. `ConfigModule.forRoot` avec `isGlobal: true` charge dotenv en amont → `process.env` est peuplé avant l'instanciation des services. + +```typescript +// AVANT (cassé avec tsx) +constructor(private readonly config: ConfigService) { + const host = this.config.get('REDIS_HOST'); +} + +// APRÈS (compatible tsx) +constructor() { + const host = process.env['REDIS_HOST'] ?? 'localhost'; +} +``` + +**Alternative non retenue :** `nest start --watch` → conflits ESM/CJS avec Prisma v7 + `module: nodenext` (exports is not defined). + +**Règle :** Dans un projet NestJS avec `tsx` comme runner, ne jamais injecter `ConfigService` par type. Toujours utiliser `process.env` directement dans les services d'infra (Redis, DB pools, etc.). diff --git a/scripts/post-bmad-install.sh b/scripts/post-bmad-install.sh index ea02ea8..2d867b9 100755 --- a/scripts/post-bmad-install.sh +++ b/scripts/post-bmad-install.sh @@ -17,6 +17,8 @@ DEV_STORY_XML="$PROJECT_ROOT/_bmad/bmm/workflows/4-implementation/dev-story/inst CODE_REVIEW_XML="$PROJECT_ROOT/_bmad/bmm/workflows/4-implementation/code-review/instructions.xml" DEV_STORY_CHECKLIST="$PROJECT_ROOT/_bmad/bmm/workflows/4-implementation/dev-story/checklist.md" CODE_REVIEW_CHECKLIST="$PROJECT_ROOT/_bmad/bmm/workflows/4-implementation/code-review/checklist.md" +STORY_TEMPLATE_MD="$PROJECT_ROOT/_bmad/bmm/workflows/4-implementation/create-story/template.md" +CREATE_STORY_XML="$PROJECT_ROOT/_bmad/bmm/workflows/4-implementation/create-story/instructions.xml" PRODUCER_AGENTS_CONFIG="$TEMPLATES_DIR/config/producer_agents.tsv" MEMORIES_DIR="$TEMPLATES_DIR/memories" @@ -25,6 +27,8 @@ CLAUDE_FRAGMENTS_DIR="$TEMPLATES_DIR/claude" CAPITALIZE_MARKER="95_a_capitaliser.md" CAPITALIZE_MARKER_XML="Capitalisation Lead_tech" +PARALLELIZATION_MARKER_XML="Story parallelization check" +CREATE_STORY_PARALLELIZATION_MARKER_XML="Story parallelization metadata" if [ ! -d "$AGENTS_DIR" ]; then echo "Erreur : dossier _bmad/_config/agents/ introuvable dans $PROJECT_ROOT" >&2 @@ -50,6 +54,14 @@ render_template() { sed "s/__PROJECT_NAME__/$(escape_sed_replacement "$PROJECT_NAME")/g" "$file" } +render_template_to_temp_file() { + local file="$1" + local output + output="$(mktemp)" + render_template "$file" > "$output" + printf '%s\n' "$output" +} + patch_line_after_match() { local file="$1" local match="$2" @@ -65,17 +77,19 @@ patch_line_after_match() { return 0 fi - local fragment - fragment="$(render_template "$fragment_file")" + local rendered_fragment + rendered_fragment="$(render_template_to_temp_file "$fragment_file")" - awk -v match="$match" -v block="$fragment" ' + awk -v match="$match" -v fragfile="$rendered_fragment" ' index($0, match) { print - print block + while ((getline line < fragfile) > 0) print line + close(fragfile) next } { print } ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file" + rm -f "$rendered_fragment" echo " [ok] $(basename "$file") — capitalisation injectée" } @@ -95,19 +109,114 @@ patch_block_before_match() { return 0 fi - local fragment - fragment="$(render_template "$fragment_file")" + local rendered_fragment + rendered_fragment="$(render_template_to_temp_file "$fragment_file")" - awk -v match="$match" -v block="$fragment" ' + awk -v match="$match" -v fragfile="$rendered_fragment" ' index($0, match) { - print block + while ((getline line < fragfile) > 0) print line + close(fragfile) } { print } ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file" + rm -f "$rendered_fragment" echo " [ok] $(basename "$file") — capitalisation injectée" } +patch_create_story_parallelization() { + local file="$CREATE_STORY_XML" + local fragment_file="$WORKFLOWS_DIR/create-story-parallelization.xmlfrag" + + if [ ! -f "$file" ]; then + echo " [skip] $(basename "$file") — fichier absent" + return 0 + fi + + if grep -q "$CREATE_STORY_PARALLELIZATION_MARKER_XML" "$file"; then + echo " [skip] $(basename "$file") — parallélisation create-story déjà présente" + return 0 + fi + + local rendered_fragment + rendered_fragment="$(render_template_to_temp_file "$fragment_file")" + + awk -v fragfile="$rendered_fragment" ' + /story_header<\/template-output>/ { + print + while ((getline line < fragfile) > 0) print line + close(fragfile) + next + } + { print } + ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file" + rm -f "$rendered_fragment" + + echo " [ok] $(basename "$file") — règle create-story injectée" +} + +patch_story_template() { + local file="$STORY_TEMPLATE_MD" + local fragment_file="$WORKFLOWS_DIR/story-template-frontmatter.mdfrag" + + if [ ! -f "$file" ]; then + echo " [skip] $(basename "$file") — fichier absent" + return 0 + fi + + if grep -q "^Parallel-safe:" "$file"; then + echo " [skip] $(basename "$file") — parallélisation déjà présente" + return 0 + fi + + local rendered_fragment + rendered_fragment="$(render_template_to_temp_file "$fragment_file")" + + awk -v fragfile="$rendered_fragment" ' + /^Status:/ { + print + while ((getline line < fragfile) > 0) print line + close(fragfile) + next + } + { print } + ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file" + rm -f "$rendered_fragment" + + echo " [ok] $(basename "$file") — frontmatter de parallélisation injecté" +} + +patch_dev_story_parallelization() { + local file="$DEV_STORY_XML" + local fragment_file="$WORKFLOWS_DIR/dev-story-parallelization-check.xmlfrag" + + if [ ! -f "$file" ]; then + echo " [skip] $(basename "$file") — fichier absent" + return 0 + fi + + if grep -q "$PARALLELIZATION_MARKER_XML" "$file"; then + echo " [skip] $(basename "$file") — parallélisation déjà présente" + return 0 + fi + + local rendered_fragment + rendered_fragment="$(render_template_to_temp_file "$fragment_file")" + + awk -v fragfile="$rendered_fragment" ' + // { + while ((getline line < fragfile) > 0) print line + close(fragfile) + print + next + } + { print } + ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file" + rm -f "$rendered_fragment" + + echo " [ok] $(basename "$file") — règle de parallélisation injectée" +} + patch_agent() { local agent="$1" local template_name="$2" @@ -173,20 +282,22 @@ patch_code_review() { return 0 fi - local fragment - fragment="$(render_template "$fragment_file")" + local rendered_fragment + rendered_fragment="$(render_template_to_temp_file "$fragment_file")" - awk -v block="$fragment" ' + awk -v fragfile="$rendered_fragment" ' /✅ Review Complete!/ { in_review_complete = 1 } in_review_complete && /<\/output>/ { print print "" - print block + while ((getline line < fragfile) > 0) print line + close(fragfile) in_review_complete = 0 next } { print } ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file" + rm -f "$rendered_fragment" echo " [ok] $(basename "$file") — capitalisation injectée" } @@ -235,6 +346,9 @@ patch_agents echo "" echo "Patch workflows :" +patch_story_template +patch_create_story_parallelization +patch_dev_story_parallelization patch_dev_story patch_code_review patch_dev_story_checklist