mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-04-06 13:31:43 +02:00
feat: capitalise Epic 2 app-alexandrie + enrichit post-bmad-install
- Intègre 9 propositions de 95_a_capitaliser.md (Stripe, webhooks, Redis, entitlements, guards, catch silencieux, conventions File List) - Ajoute core-bmad-master dans les agents patchés (orchestrateur) - Différencie les fichiers cibles par rôle d'agent (dev/architect/qa…) - Patch dev-story et code-review XML pour déclencher la capitalisation à chaque fin de story et après chaque code review Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,9 @@ Dernière mise à jour : 09-03-2026
|
|||||||
- [Contracts-First / Zod-Infer / No-DTO (monorepo TypeScript fullstack)](#pattern-contracts-first-zod-infer-no-dto)
|
- [Contracts-First / Zod-Infer / No-DTO (monorepo TypeScript fullstack)](#pattern-contracts-first-zod-infer-no-dto)
|
||||||
- [Guard global NestJS — ordre d’enregistrement et décorateurs de bypass](#pattern-guard-global-nestjs)
|
- [Guard global NestJS — ordre d’enregistrement et décorateurs de bypass](#pattern-guard-global-nestjs)
|
||||||
- [Provider-Strategy pour intégrations tierces — périmètre complet](#pattern-provider-strategy-integrations-tierces)
|
- [Provider-Strategy pour intégrations tierces — périmètre complet](#pattern-provider-strategy-integrations-tierces)
|
||||||
|
- [Stripe — metadata sur `subscription_data`, pas sur la Session](#pattern-stripe-subscription-metadata)
|
||||||
|
- [Webhooks entrants — parsing unique (single constructWebhookEvent)](#pattern-webhook-parsing-unique)
|
||||||
|
- [Contracts-First — error codes comme contrat obligatoire](#pattern-contracts-error-codes)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -494,6 +497,67 @@ async handleWebhook(rawBody: Buffer, signature: string): Promise<void> {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<a id=”pattern-stripe-subscription-metadata”></a>
|
||||||
|
## Pattern : Stripe — metadata sur `subscription_data`, pas sur la Session
|
||||||
|
|
||||||
|
- Objectif : garantir que `userId` (ou tout identifiant métier) soit accessible dans les events `customer.subscription.*`, pas seulement dans `checkout.session.completed`.
|
||||||
|
- Contexte : intégration Stripe Checkout avec webhooks abonnement.
|
||||||
|
- Quand l’utiliser : systématiquement dès qu’on crée une Checkout Session liée à une Subscription.
|
||||||
|
- Risque si ignoré : `metadata.userId` absent des events `customer.subscription.updated/deleted` → silent failure en prod.
|
||||||
|
- Validé le : 09-03-2026
|
||||||
|
- Contexte technique : Stripe API v17+ / NestJS
|
||||||
|
|
||||||
|
### Implémentation
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
stripe.checkout.sessions.create({
|
||||||
|
metadata: { userId }, // pour checkout.session.completed
|
||||||
|
subscription_data: { metadata: { userId } }, // pour customer.subscription.*
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<a id=”pattern-webhook-parsing-unique”></a>
|
||||||
|
## Pattern : Webhooks entrants — parsing unique (single `constructWebhookEvent`)
|
||||||
|
|
||||||
|
- Objectif : appeler `constructWebhookEvent` une seule fois par requête, puis router vers des extracteurs purs.
|
||||||
|
- Contexte : endpoint webhook recevant des events de plusieurs types (subscription, pack, facture…).
|
||||||
|
- Quand l’utiliser : dès qu’on a 2+ handlers webhook sur le même endpoint.
|
||||||
|
- Risque si ignoré : double vérification de signature + états partiels possibles (sub OK / pack KO).
|
||||||
|
- Validé le : 09-03-2026
|
||||||
|
- Contexte technique : Stripe / NestJS
|
||||||
|
|
||||||
|
### Implémentation
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 1. Parser unique — 1 seul constructWebhookEvent(rawBody, sig) → event opaque
|
||||||
|
// 2. Extracteurs purs, sans effet de bord :
|
||||||
|
handleSubscriptionWebhookEvent(event): WebhookResult | null
|
||||||
|
handlePackWebhookEvent(event): PackWebhookResult | null
|
||||||
|
// 3. Orchestrateur unique appelle les extracteurs, persiste les résultats
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<a id=”pattern-contracts-error-codes”></a>
|
||||||
|
## Pattern : Contracts-First — error codes comme contrat obligatoire
|
||||||
|
|
||||||
|
- Objectif : maintenir les codes d’erreur API dans `packages/contracts` pour éviter les clients stringly-typed.
|
||||||
|
- Contexte : monorepo TypeScript avec `packages/contracts/src/errors/error-code.ts`.
|
||||||
|
- Règle : toute nouvelle erreur API ⇒ ajout obligatoire dans `error-code.ts` **avant merge**, pas après.
|
||||||
|
- Risque si ignoré : clients qui testent des strings hardcodées au lieu d’importer l’enum → drift silencieux.
|
||||||
|
- Validé le : 09-03-2026
|
||||||
|
- Contexte technique : TypeScript / NestJS + Expo (React Native)
|
||||||
|
|
||||||
|
### Checklist
|
||||||
|
|
||||||
|
- [ ] Nouvel `error.code` → ajout dans `packages/contracts/src/errors/error-code.ts` en même commit
|
||||||
|
- [ ] Clients importent l’enum, pas une string littérale
|
||||||
|
- [ ] PR review : vérifier `error-code.ts` à chaque ajout d’endpoint d’erreur
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Notes importantes
|
### Notes importantes
|
||||||
|
|
||||||
- On préfère 5 patterns solides à 50 “bons conseils”.
|
- On préfère 5 patterns solides à 50 “bons conseils”.
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ Dernière mise à jour : 09-03-2026
|
|||||||
- [Stripe : `billing_cycle_anchor` vs `current_period_end`](#risque-stripe-current-period-end)
|
- [Stripe : `billing_cycle_anchor` vs `current_period_end`](#risque-stripe-current-period-end)
|
||||||
- [PostgreSQL/Prisma : `@unique` nullable](#risque-prisma-unique-nullable)
|
- [PostgreSQL/Prisma : `@unique` nullable](#risque-prisma-unique-nullable)
|
||||||
- [Observabilité insuffisante](#risque-observabilite-insuffisante)
|
- [Observabilité insuffisante](#risque-observabilite-insuffisante)
|
||||||
|
- [Webhooks entrants — répondre 200 pendant `processing` (event perdu)](#risque-webhook-200-processing)
|
||||||
|
- [Redis — thrash de connexion sous charge](#risque-redis-thrash-connexion)
|
||||||
|
- [Entitlements — TTL cache supérieur au SLA de propagation](#risque-entitlements-ttl-sla)
|
||||||
|
- [Guard NestJS route-level — null-check manquant sur `request.user`](#risque-guard-request-user-null)
|
||||||
|
- [Compteurs in-memory ≠ métriques persistées](#risque-compteurs-inmemory)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -254,3 +259,122 @@ Dernière mise à jour : 09-03-2026
|
|||||||
- Logs structurés + requestId/traceId
|
- Logs structurés + requestId/traceId
|
||||||
- Métriques de base (latence, erreurs, throughput)
|
- Métriques de base (latence, erreurs, throughput)
|
||||||
- Alertes simples sur 5xx/latence
|
- Alertes simples sur 5xx/latence
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<a id="risque-webhook-200-processing"></a>
|
||||||
|
## Webhooks entrants — répondre 200 pendant `processing` (event perdu)
|
||||||
|
|
||||||
|
### Risques
|
||||||
|
|
||||||
|
- Le provider (Stripe, etc.) arrête ses retries après un 2xx, même si le premier worker a échoué
|
||||||
|
- Event non appliqué mais marqué "traité" → état incohérent silencieux
|
||||||
|
|
||||||
|
### Symptômes
|
||||||
|
|
||||||
|
- Webhook reçu, 200 retourné, mais l'état en base n'est pas mis à jour
|
||||||
|
- Aucun retry du provider → impossible à détecter sans monitoring actif
|
||||||
|
|
||||||
|
### Bonnes pratiques / mitigations
|
||||||
|
|
||||||
|
- Lock DB (`WebhookEvent`) avec machine d'état : `pending` → `processing` → `processed` / `failed`
|
||||||
|
- Si `processing` détecté (concurrent) : attendre brièvement la transition `processed`, sinon répondre **non-2xx** (force retry provider)
|
||||||
|
- Ne jamais passer à `processed` sans preuve d'un traitement effectif
|
||||||
|
- Contexte technique : Stripe / NestJS — 09-03-2026
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<a id="risque-redis-thrash-connexion"></a>
|
||||||
|
## Redis — thrash de connexion sous charge
|
||||||
|
|
||||||
|
### Risques
|
||||||
|
|
||||||
|
- Connexions concurrentes multiples si `connect()` est appelé "à la demande" sans lock
|
||||||
|
- Spam logs + saturation connexions quand Redis est down ou lent
|
||||||
|
|
||||||
|
### Symptômes
|
||||||
|
|
||||||
|
- N appels simultanés → N tentatives de connexion en parallèle
|
||||||
|
- Logs "Redis connection failed" en rafale au démarrage ou lors d'un restart Redis
|
||||||
|
|
||||||
|
### Bonnes pratiques / mitigations
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Pattern single-flight + cooldown + fallback DB best-effort
|
||||||
|
if (!this.connectPromise) {
|
||||||
|
this.connectPromise = this.client.connect().finally(() => { this.connectPromise = null; });
|
||||||
|
}
|
||||||
|
await this.connectPromise;
|
||||||
|
// Si échec → nextConnectRetryAtMs = now + 1000 → return false → fallback DB
|
||||||
|
```
|
||||||
|
|
||||||
|
- Contexte technique : Redis / NestJS — 09-03-2026
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<a id="risque-entitlements-ttl-sla"></a>
|
||||||
|
## Entitlements — TTL cache supérieur au SLA de propagation
|
||||||
|
|
||||||
|
### Risques
|
||||||
|
|
||||||
|
- TTL cache > SLA propagation → un webhook raté viole mécaniquement le SLA (accès stale plus long que garanti)
|
||||||
|
- Utilisateur avec accès périmé ou sans accès dû, pendant toute la durée du TTL résiduel
|
||||||
|
|
||||||
|
### Symptômes
|
||||||
|
|
||||||
|
- Accès premium encore actif après annulation (ou inversement)
|
||||||
|
- NFR "propagation ≤ 60s" non respecté en cas de webhook manqué
|
||||||
|
|
||||||
|
### Bonnes pratiques / mitigations
|
||||||
|
|
||||||
|
- TTL cache ≤ SLA cible (ex : NFR "≤ 60s" → TTL = 60s max)
|
||||||
|
- Toujours coupler TTL + invalidation explicite via webhook (les deux, pas l'un ou l'autre)
|
||||||
|
- Contexte technique : Redis / entitlements / NestJS — 09-03-2026
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<a id="risque-guard-request-user-null"></a>
|
||||||
|
## Guard NestJS route-level — null-check manquant sur `request.user`
|
||||||
|
|
||||||
|
### Risques
|
||||||
|
|
||||||
|
- Un guard route-level qui lit `request.user.userId` sans null-check lève une `TypeError` (500) si `request.user` est absent
|
||||||
|
- Mauvaise registration de module, test d'intégration mal configuré, ou middleware custom peuvent produire cet état
|
||||||
|
|
||||||
|
### Symptômes
|
||||||
|
|
||||||
|
- `TypeError: Cannot read properties of undefined (reading 'userId')` en prod
|
||||||
|
- Tests "verts" car `request.user` mocké globalement, mais pas le guard isolé
|
||||||
|
|
||||||
|
### Bonnes pratiques / mitigations
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const user = (request as any).user as { userId: string } | undefined;
|
||||||
|
if (!user?.userId) {
|
||||||
|
throw new UnauthorizedException({ error: { code: 'UNAUTHENTICATED', message: '...' } });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Règle** : les guards route-level ne font pas confiance aux guards globaux pour leurs invariants — ils se défendent eux-mêmes.
|
||||||
|
- Contexte technique : NestJS v10+ — 09-03-2026
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<a id="risque-compteurs-inmemory"></a>
|
||||||
|
## Compteurs in-memory ≠ métriques persistées
|
||||||
|
|
||||||
|
### Risques
|
||||||
|
|
||||||
|
- Compteurs in-memory remis à zéro au restart (perte de données)
|
||||||
|
- Non agrégables sur plusieurs instances (données partielles par pod)
|
||||||
|
|
||||||
|
### Symptômes
|
||||||
|
|
||||||
|
- Métriques qui "repartent de 0" à chaque déploiement
|
||||||
|
- Dashboards incorrects en environnement multi-instance
|
||||||
|
|
||||||
|
### Bonnes pratiques / mitigations
|
||||||
|
|
||||||
|
- V1 low-cost : `Redis INCRBY` best-effort par `eventType` → persisté et agrégé multi-instances
|
||||||
|
- Évolutif vers Prometheus/OTel sans changer l'interface (abstraction dès le départ)
|
||||||
|
- Contexte technique : Redis / NestJS — 09-03-2026
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ Dernière mise à jour : 2026-03-09
|
|||||||
## Index
|
## Index
|
||||||
|
|
||||||
- [Langue par type de document](#convention-langue-par-type-de-document)
|
- [Langue par type de document](#convention-langue-par-type-de-document)
|
||||||
|
- [File List story — exhaustivité obligatoire](#convention-file-list-story)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -57,3 +58,16 @@ Pas de "bonne pratique" théorique.
|
|||||||
- Contexte projet : Lead_tech (convention globale)
|
- Contexte projet : Lead_tech (convention globale)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<a id="convention-file-list-story"></a>
|
||||||
|
### Convention : File List story — exhaustivité obligatoire
|
||||||
|
|
||||||
|
- Scope : section "File List" des story files BMAD (Dev Agent Record)
|
||||||
|
- Règle : inclure **tous** les fichiers créés ou modifiés pendant la story — migrations, modules infra, fichiers contracts, fichiers de config. Un reviewer ne doit pas avoir à faire `git status` pour reconstituer le périmètre.
|
||||||
|
- Règle complémentaire : les fichiers créés en avance de phase (scope d'une story future) doivent être annotés : `— créé en avance (scope story X.Y)`
|
||||||
|
- Vérification recommandée : cross-checker via `git status --porcelain` avant de passer la story en review
|
||||||
|
- Contre-exemple : story 2.3 app-alexandrie — 13 fichiers manquants (migrations, modules Redis, services entitlements, error codes contracts)
|
||||||
|
- Validé le : 09-03-2026
|
||||||
|
- Contexte projet : app-alexandrie
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ Dernière mise à jour : 09-03-2026
|
|||||||
- [Appels API en state local d’écran](#risque-api-state-local-ecran)
|
- [Appels API en state local d’écran](#risque-api-state-local-ecran)
|
||||||
- [Performances : sur-renders + bundle](#risque-performances-sur-renders)
|
- [Performances : sur-renders + bundle](#risque-performances-sur-renders)
|
||||||
- [Accessibilité oubliée (a11y)](#risque-accessibilite-oubliee)
|
- [Accessibilité oubliée (a11y)](#risque-accessibilite-oubliee)
|
||||||
|
- [Catch silencieux — erreur inconnue sans feedback utilisateur](#risque-catch-silencieux)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -172,3 +173,35 @@ Dernière mise à jour : 09-03-2026
|
|||||||
- Checklist a11y minimale sur chaque écran clé
|
- Checklist a11y minimale sur chaque écran clé
|
||||||
- Gestion de focus (modales, erreurs formulaire)
|
- Gestion de focus (modales, erreurs formulaire)
|
||||||
- Labels/aria cohérents + tests simples
|
- Labels/aria cohérents + tests simples
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<a id="risque-catch-silencieux"></a>
|
||||||
|
## Catch silencieux — erreur inconnue sans feedback utilisateur
|
||||||
|
|
||||||
|
### Risques
|
||||||
|
|
||||||
|
- Un `catch` qui ne traite que les cas connus laisse l'utilisateur face à un spinner qui disparaît sans message
|
||||||
|
- L'état d'erreur reste implicite → impossible de diagnostiquer ou de reproduire
|
||||||
|
|
||||||
|
### Symptômes
|
||||||
|
|
||||||
|
- Bouton spinner qui s'arrête, rien ne se passe
|
||||||
|
- Pas de toast / message d'erreur affiché
|
||||||
|
- Erreur "avalée" silencieusement dans les logs
|
||||||
|
|
||||||
|
### Bonnes pratiques / mitigations
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const code = (err as { code?: string }).code;
|
||||||
|
if (code === 'SUBSCRIPTION_REQUIRED') {
|
||||||
|
setSubscriptionRequired(true);
|
||||||
|
} else {
|
||||||
|
setError('Une erreur est survenue. Veuillez réessayer.'); // toujours un fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Règle** : tout `catch` doit avoir une branche `else` (ou `default`) qui affiche un feedback utilisateur explicite.
|
||||||
|
- Contexte technique : React Native / Expo — 09-03-2026
|
||||||
|
|||||||
@@ -81,8 +81,6 @@ Sinon `request.user` peut être undefined dans les guards suivants.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
_Aucune proposition en attente pour le moment._
|
|
||||||
|
|
||||||
# Rôle dans l'architecture
|
# Rôle dans l'architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ PRODUCER_AGENTS=(
|
|||||||
"bmm-tech-writer"
|
"bmm-tech-writer"
|
||||||
"bmm-ux-designer"
|
"bmm-ux-designer"
|
||||||
"tea-tea"
|
"tea-tea"
|
||||||
|
"core-bmad-master"
|
||||||
)
|
)
|
||||||
|
|
||||||
CAPITALIZE_MARKER="95_a_capitaliser.md"
|
CAPITALIZE_MARKER="95_a_capitaliser.md"
|
||||||
@@ -85,10 +86,10 @@ build_memory() {
|
|||||||
|
|
||||||
case "$agent" in
|
case "$agent" in
|
||||||
bmm-dev|bmm-quick-flow-solo-dev)
|
bmm-dev|bmm-quick-flow-solo-dev)
|
||||||
echo "${base} during implementation, write a proposal to ~/AI_RULES/_Assistant_Lead_Tech/95_a_capitaliser.md (NUC: /srv/projects/_Assistant_Lead_Tech/95_a_capitaliser.md). Format: DATE — ${PROJECT_NAME} / FILE_UPDATE_PROPOSAL / Fichier cible: <target file> / Pourquoi: <reason> / Proposition: <content>. Never write directly to Lead_tech validated files."
|
echo "${base} during implementation, write a proposal to ~/AI_RULES/_Assistant_Lead_Tech/95_a_capitaliser.md (NUC: /srv/projects/_Assistant_Lead_Tech/95_a_capitaliser.md). Format: DATE — ${PROJECT_NAME} / FILE_UPDATE_PROPOSAL / Fichier cible: <10_backend_patterns_valides.md | 10_frontend_patterns_valides.md | 10_backend_risques_et_vigilance.md | 10_frontend_risques_et_vigilance.md | 90_debug_et_postmortem.md> / Pourquoi: <reason> / Proposition: <content>. Never write directly to Lead_tech validated files."
|
||||||
;;
|
;;
|
||||||
bmm-architect)
|
bmm-architect)
|
||||||
echo "${base}, write a proposal to ~/AI_RULES/_Assistant_Lead_Tech/95_a_capitaliser.md (NUC: /srv/projects/_Assistant_Lead_Tech/95_a_capitaliser.md). Format: DATE — ${PROJECT_NAME} / FILE_UPDATE_PROPOSAL / Fichier cible: <target file> / Pourquoi: <reason> / Proposition: <content>. Never write directly to Lead_tech validated files."
|
echo "${base} during architecture or technical design, write a proposal to ~/AI_RULES/_Assistant_Lead_Tech/95_a_capitaliser.md (NUC: /srv/projects/_Assistant_Lead_Tech/95_a_capitaliser.md). Format: DATE — ${PROJECT_NAME} / FILE_UPDATE_PROPOSAL / Fichier cible: <40_decisions_et_archi.md | 10_backend_patterns_valides.md | 10_backend_risques_et_vigilance.md> / Pourquoi: <reason> / Proposition: <content>. Never write directly to Lead_tech validated files."
|
||||||
;;
|
;;
|
||||||
bmm-sm)
|
bmm-sm)
|
||||||
echo "When a process improvement, recurring friction, or architecture decision emerges during sprint work, write a proposal to ~/AI_RULES/_Assistant_Lead_Tech/95_a_capitaliser.md (NUC: /srv/projects/_Assistant_Lead_Tech/95_a_capitaliser.md). Format: DATE — ${PROJECT_NAME} / FILE_UPDATE_PROPOSAL / Fichier cible: <target file> / Pourquoi: <reason> / Proposition: <content>. Never write directly to Lead_tech validated files."
|
echo "When a process improvement, recurring friction, or architecture decision emerges during sprint work, write a proposal to ~/AI_RULES/_Assistant_Lead_Tech/95_a_capitaliser.md (NUC: /srv/projects/_Assistant_Lead_Tech/95_a_capitaliser.md). Format: DATE — ${PROJECT_NAME} / FILE_UPDATE_PROPOSAL / Fichier cible: <target file> / Pourquoi: <reason> / Proposition: <content>. Never write directly to Lead_tech validated files."
|
||||||
@@ -108,9 +109,80 @@ build_memory() {
|
|||||||
bmm-tech-writer)
|
bmm-tech-writer)
|
||||||
echo "When a reusable documentation pattern, writing convention, or recurring documentation friction emerges, write a proposal to ~/AI_RULES/_Assistant_Lead_Tech/95_a_capitaliser.md (NUC: /srv/projects/_Assistant_Lead_Tech/95_a_capitaliser.md). Format: DATE — ${PROJECT_NAME} / FILE_UPDATE_PROPOSAL / Fichier cible: <10_conventions_redaction.md | 40_decisions_et_archi.md> / Pourquoi: <reason> / Proposition: <content>. Never write directly to Lead_tech validated files."
|
echo "When a reusable documentation pattern, writing convention, or recurring documentation friction emerges, write a proposal to ~/AI_RULES/_Assistant_Lead_Tech/95_a_capitaliser.md (NUC: /srv/projects/_Assistant_Lead_Tech/95_a_capitaliser.md). Format: DATE — ${PROJECT_NAME} / FILE_UPDATE_PROPOSAL / Fichier cible: <10_conventions_redaction.md | 40_decisions_et_archi.md> / Pourquoi: <reason> / Proposition: <content>. Never write directly to Lead_tech validated files."
|
||||||
;;
|
;;
|
||||||
|
core-bmad-master)
|
||||||
|
echo "As the orchestrating agent, when any cross-cutting pattern, process improvement, recurring friction, or architectural decision emerges across the project, write a proposal to ~/AI_RULES/_Assistant_Lead_Tech/95_a_capitaliser.md (NUC: /srv/projects/_Assistant_Lead_Tech/95_a_capitaliser.md). Format: DATE — ${PROJECT_NAME} / FILE_UPDATE_PROPOSAL / Fichier cible: <10_backend_patterns_valides.md | 10_frontend_patterns_valides.md | 10_product_patterns_valides.md | 10_ux_patterns_valides.md | 10_backend_risques_et_vigilance.md | 10_frontend_risques_et_vigilance.md | 40_decisions_et_archi.md | 90_debug_et_postmortem.md> / Pourquoi: <reason> / Proposition: <content>. Never write directly to Lead_tech validated files."
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CAPITALIZE_MARKER_XML="Capitalisation Lead_tech"
|
||||||
|
DEV_STORY_XML="$PROJECT_ROOT/_bmad/bmm/workflows/4-implementation/dev-story/instructions.xml"
|
||||||
|
CODE_REVIEW_XML="$PROJECT_ROOT/_bmad/bmm/workflows/4-implementation/code-review/instructions.xml"
|
||||||
|
|
||||||
|
patch_dev_story() {
|
||||||
|
local file="$DEV_STORY_XML"
|
||||||
|
|
||||||
|
if [ ! -f "$file" ]; then
|
||||||
|
echo " [skip] dev-story/instructions.xml — fichier absent"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "$CAPITALIZE_MARKER_XML" "$file"; then
|
||||||
|
echo " [skip] dev-story/instructions.xml — capitalisation déjà présente"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Insérer le bloc capitalisation juste avant les Final validation gates
|
||||||
|
awk '
|
||||||
|
/<!-- Final validation gates -->/ {
|
||||||
|
print " <!-- Capitalisation Lead_tech -->"
|
||||||
|
print " <action>Review implementation for reusable patterns, difficult bug fixes, anti-patterns, or architecture decisions that emerged during this story</action>"
|
||||||
|
print " <check if=\"capitalisation-worthy content identified\">"
|
||||||
|
print " <critical>Write proposals to ~/AI_RULES/_Assistant_Lead_Tech/95_a_capitaliser.md ONLY \xe2\x80\x94 NEVER inside the project repo</critical>"
|
||||||
|
print " <action>For each proposal: FORMAT = \"DATE \xe2\x80\x94 '"$PROJECT_NAME"' / FILE_UPDATE_PROPOSAL / Fichier cible: <target> / Pourquoi: <reason> / Proposition: <content>\"</action>"
|
||||||
|
print " </check>"
|
||||||
|
print ""
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
|
||||||
|
|
||||||
|
echo " [ok] dev-story/instructions.xml — capitalisation injectée"
|
||||||
|
}
|
||||||
|
|
||||||
|
patch_code_review() {
|
||||||
|
local file="$CODE_REVIEW_XML"
|
||||||
|
|
||||||
|
if [ ! -f "$file" ]; then
|
||||||
|
echo " [skip] code-review/instructions.xml — fichier absent"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "$CAPITALIZE_MARKER_XML" "$file"; then
|
||||||
|
echo " [skip] code-review/instructions.xml — capitalisation déjà présente"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Insérer le bloc capitalisation après le output "✅ Review Complete!"
|
||||||
|
awk '
|
||||||
|
/✅ Review Complete!/ { in_review_complete = 1 }
|
||||||
|
in_review_complete && /<\/output>/ {
|
||||||
|
print
|
||||||
|
print ""
|
||||||
|
print " <!-- Capitalisation Lead_tech -->"
|
||||||
|
print " <action>Review findings for patterns worth capitalizing: anti-patterns found, recurring issues, architecture decisions confirmed or invalidated</action>"
|
||||||
|
print " <check if=\"capitalisation-worthy findings identified\">"
|
||||||
|
print " <critical>Write proposals to ~/AI_RULES/_Assistant_Lead_Tech/95_a_capitaliser.md ONLY \xe2\x80\x94 NEVER inside the project repo</critical>"
|
||||||
|
print " <action>For each proposal: FORMAT = \"DATE \xe2\x80\x94 '"$PROJECT_NAME"' / FILE_UPDATE_PROPOSAL / Fichier cible: <target> / Pourquoi: <reason> / Proposition: <content>\"</action>"
|
||||||
|
print " </check>"
|
||||||
|
in_review_complete = 0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
|
||||||
|
|
||||||
|
echo " [ok] code-review/instructions.xml — capitalisation injectée"
|
||||||
|
}
|
||||||
|
|
||||||
patch_claude_md() {
|
patch_claude_md() {
|
||||||
if [ ! -f "$CLAUDE_MD" ]; then
|
if [ ! -f "$CLAUDE_MD" ]; then
|
||||||
echo " [skip] CLAUDE.md — fichier absent"
|
echo " [skip] CLAUDE.md — fichier absent"
|
||||||
@@ -170,6 +242,11 @@ for agent in "${PRODUCER_AGENTS[@]}"; do
|
|||||||
patch_agent "$agent"
|
patch_agent "$agent"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Patch workflows :"
|
||||||
|
patch_dev_story
|
||||||
|
patch_code_review
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Patch CLAUDE.md :"
|
echo "Patch CLAUDE.md :"
|
||||||
patch_claude_md
|
patch_claude_md
|
||||||
|
|||||||
Reference in New Issue
Block a user