mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-04-06 13:31:43 +02:00
Affiner sync projets + publication skills Claude/Codex
This commit is contained in:
18
00_README.md
18
00_README.md
@@ -111,3 +111,21 @@ Cette variable constitue la **référence portable** vers le repo.
|
|||||||
|
|
||||||
> **Si ça a déjà coûté du temps une fois,
|
> **Si ça a déjà coûté du temps une fois,
|
||||||
> ça mérite d’être documenté pour ne jamais le reperdre.**
|
> ça mérite d’être documenté pour ne jamais le reperdre.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sync IA (Claude + Codex)
|
||||||
|
|
||||||
|
Pour synchroniser les instructions globales **et** les skills locaux (`skills/*/SKILL.md`) vers les emplacements attendus par Claude et Codex :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash "$LEADTECH/scripts/sync-ai-instructions.sh"
|
||||||
|
```
|
||||||
|
|
||||||
|
Effets attendus :
|
||||||
|
|
||||||
|
- met à jour `~/.claude/CLAUDE.md`
|
||||||
|
- aligne `~/.codex/AGENTS.md` dessus (symlink)
|
||||||
|
- publie les skills locaux dans :
|
||||||
|
- `~/.claude/skills/<skill>`
|
||||||
|
- `~/.codex/skills/<skill>`
|
||||||
|
|||||||
@@ -193,6 +193,67 @@ Sans ce check, les erreurs 404/500 passent silencieusement si le service appelan
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
2026-03-28 — app-alexandrie
|
||||||
|
|
||||||
|
FILE_UPDATE_PROPOSAL
|
||||||
|
Fichier cible : knowledge/frontend/risques/state.md
|
||||||
|
|
||||||
|
Pourquoi :
|
||||||
|
Anti-pattern découvert en review 5.3 : `followIsLoading: boolean` global dans un store Zustand bloquait tous les boutons "Suivre" de l'annuaire simultanément. Un seul flag booléen ne peut pas représenter des états par entité.
|
||||||
|
|
||||||
|
Proposition :
|
||||||
|
|
||||||
|
## État de chargement par entité dans les stores Zustand — préférer un Set<string>
|
||||||
|
|
||||||
|
Quand plusieurs instances d'un même composant peuvent déclencher une action async en parallèle (ex: bouton "Suivre" sur chaque carte d'une liste), un flag booléen global `isLoading: boolean` est insuffisant — il désactive toutes les instances dès qu'une action est en cours.
|
||||||
|
|
||||||
|
Pattern correct :
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// État
|
||||||
|
followingInProgress: Set<string>; // userId actuellement en cours de follow/unfollow
|
||||||
|
|
||||||
|
// Selector
|
||||||
|
isFollowInProgress: (userId: string) => boolean;
|
||||||
|
// dans le store : (userId) => get().followingInProgress.has(userId)
|
||||||
|
|
||||||
|
// Mutation
|
||||||
|
set((state) => {
|
||||||
|
const next = new Set(state.followingInProgress);
|
||||||
|
next.add(targetUserId);
|
||||||
|
return { followingInProgress: next };
|
||||||
|
});
|
||||||
|
// Après succès/erreur : next.delete(targetUserId)
|
||||||
|
```
|
||||||
|
|
||||||
|
Appliquer ce pattern pour toute action async dans une liste (like, bookmark, follow, reaction, etc.).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
2026-03-28 — app-alexandrie
|
||||||
|
|
||||||
|
FILE_UPDATE_PROPOSAL
|
||||||
|
Fichier cible : knowledge/frontend/risques/state.md
|
||||||
|
|
||||||
|
Pourquoi :
|
||||||
|
Review 5.3 : `followError` partagé entre les actions follow/unfollow et les erreurs de chargement des listes followers/followings. Un ancien `followError` de type "ALREADY_FOLLOWING" pouvait s'afficher comme "Erreur de chargement" dans l'écran followers.
|
||||||
|
|
||||||
|
Proposition :
|
||||||
|
|
||||||
|
## Séparer les erreurs d'action et les erreurs de liste dans les stores Zustand
|
||||||
|
|
||||||
|
Quand un store gère à la fois des actions (mutations) et des listes (fetches paginés), ne pas partager la même clé d'erreur. Nommer explicitement :
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
followError: string | null; // erreur de followUser/unfollowUser
|
||||||
|
followersError: string | null; // erreur de fetchFollowers/loadMoreFollowers
|
||||||
|
followingsError: string | null; // erreur de fetchFollowings/loadMoreFollowings
|
||||||
|
```
|
||||||
|
|
||||||
|
Les écrans de liste doivent afficher leur propre erreur (`followersError`) et non l'erreur d'action globale (`followError`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# Format attendu
|
# Format attendu
|
||||||
|
|
||||||
Chaque proposition doit suivre ce format :
|
Chaque proposition doit suivre ce format :
|
||||||
|
|||||||
@@ -50,4 +50,4 @@
|
|||||||
# - automatiser certains audits inter-projets
|
# - automatiser certains audits inter-projets
|
||||||
#
|
#
|
||||||
# Les lignes commençant par # sont ignorées.
|
# Les lignes commençant par # sont ignorées.
|
||||||
app-alexandrie|NestJS + Expo (React Native) + Prisma + pnpm monorepo|mindleaf|Epic 2 en préparation
|
app-alexandrie|NestJS + Expo (React Native) + Prisma + pnpm monorepo|mindleaf|Epic 6 en cours
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ SOURCE="$REPO_ROOT/_AI_INSTRUCTIONS.md"
|
|||||||
# --- Détection machine ---
|
# --- Détection machine ---
|
||||||
OS="$(uname -s)"
|
OS="$(uname -s)"
|
||||||
CHANGED=0
|
CHANGED=0
|
||||||
|
SKILLS_SOURCE_DIR="$REPO_ROOT/skills"
|
||||||
|
|
||||||
generate_repo_claude() {
|
generate_repo_claude() {
|
||||||
local header="$1"
|
local header="$1"
|
||||||
@@ -57,6 +58,40 @@ ensure_symlink() {
|
|||||||
CHANGED=1
|
CHANGED=1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sync_skills_for_target() {
|
||||||
|
local target_root="$1"
|
||||||
|
local source_dir="$2"
|
||||||
|
local skill_dir
|
||||||
|
local skill_name
|
||||||
|
local target_link
|
||||||
|
|
||||||
|
mkdir -p "$target_root"
|
||||||
|
[ -d "$source_dir" ] || return 0
|
||||||
|
|
||||||
|
for skill_dir in "$source_dir"/*; do
|
||||||
|
[ -d "$skill_dir" ] || continue
|
||||||
|
[ -f "$skill_dir/SKILL.md" ] || continue
|
||||||
|
|
||||||
|
skill_name="$(basename "$skill_dir")"
|
||||||
|
target_link="$target_root/$skill_name"
|
||||||
|
|
||||||
|
if [ -L "$target_link" ]; then
|
||||||
|
local current_target
|
||||||
|
current_target="$(readlink "$target_link")"
|
||||||
|
if [ "$current_target" = "$skill_dir" ] && [ -e "$target_link" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
rm -f "$target_link"
|
||||||
|
elif [ -e "$target_link" ]; then
|
||||||
|
echo "WARN: skill déjà présent et non symlink, conservé: $target_link" >&2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
ln -s "$skill_dir" "$target_link"
|
||||||
|
CHANGED=1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
CLAUDE_HEADER="# Instructions globales — Lead Tech Copilote
|
CLAUDE_HEADER="# Instructions globales — Lead Tech Copilote
|
||||||
|
|
||||||
Ce fichier est chargé automatiquement par Claude Code ou Codex à chaque session.
|
Ce fichier est chargé automatiquement par Claude Code ou Codex à chaque session.
|
||||||
@@ -65,6 +100,8 @@ Il constitue la porte d'entrée principale de la base de connaissance Lead_tech
|
|||||||
generate_repo_claude "$CLAUDE_HEADER" "$HOME/.claude/CLAUDE.md"
|
generate_repo_claude "$CLAUDE_HEADER" "$HOME/.claude/CLAUDE.md"
|
||||||
|
|
||||||
ensure_symlink "$HOME/.claude/CLAUDE.md" "$HOME/.codex/AGENTS.md"
|
ensure_symlink "$HOME/.claude/CLAUDE.md" "$HOME/.codex/AGENTS.md"
|
||||||
|
sync_skills_for_target "$HOME/.claude/skills" "$SKILLS_SOURCE_DIR"
|
||||||
|
sync_skills_for_target "$HOME/.codex/skills" "$SKILLS_SOURCE_DIR"
|
||||||
|
|
||||||
if [ "$CHANGED" -eq 1 ]; then
|
if [ "$CHANGED" -eq 1 ]; then
|
||||||
echo "Sync AI instructions (OS: $OS)"
|
echo "Sync AI instructions (OS: $OS)"
|
||||||
|
|||||||
@@ -137,12 +137,124 @@ infer_scope() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
infer_state() {
|
||||||
|
local root="$1"
|
||||||
|
local sprint_status_file="$root/_bmad-output/implementation-artifacts/sprint-status.yaml"
|
||||||
|
local stories_dir="$root/_bmad-output/implementation-artifacts/stories"
|
||||||
|
local first_story
|
||||||
|
local latest_story
|
||||||
|
local epic_num
|
||||||
|
|
||||||
|
if [ -f "$sprint_status_file" ]; then
|
||||||
|
local in_dev="false"
|
||||||
|
local line trimmed key val story_epic
|
||||||
|
local active_epic="" active_ts="-1"
|
||||||
|
local prep_epic=""
|
||||||
|
local declared_in_progress_epic=""
|
||||||
|
local story_file story_ts
|
||||||
|
local -a matches=()
|
||||||
|
|
||||||
|
while IFS= read -r line || [ -n "$line" ]; do
|
||||||
|
if [ "$line" = "development_status:" ]; then
|
||||||
|
in_dev="true"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$in_dev" = "true" ] && [ -n "$(trim "$line")" ] && [[ ! "$line" =~ ^[[:space:]] ]]; then
|
||||||
|
in_dev="false"
|
||||||
|
fi
|
||||||
|
[ "$in_dev" = "true" ] || continue
|
||||||
|
|
||||||
|
trimmed="$(trim "$line")"
|
||||||
|
[[ "$trimmed" == *:* ]] || continue
|
||||||
|
key="$(trim "${trimmed%%:*}")"
|
||||||
|
val="$(trim "${trimmed#*:}")"
|
||||||
|
|
||||||
|
if [[ "$key" =~ ^epic-([0-9]+)$ ]]; then
|
||||||
|
story_epic="${BASH_REMATCH[1]}"
|
||||||
|
if [ "$val" = "in-progress" ]; then
|
||||||
|
if [ -z "$declared_in_progress_epic" ] || [ "$story_epic" -lt "$declared_in_progress_epic" ]; then
|
||||||
|
declared_in_progress_epic="$story_epic"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$key" =~ ^([0-9]+)-[0-9]+[a-z]?-.*$ ]]; then
|
||||||
|
story_epic="${BASH_REMATCH[1]}"
|
||||||
|
if [ "$val" = "in-progress" ] || [ "$val" = "review" ]; then
|
||||||
|
matches=()
|
||||||
|
shopt -s nullglob
|
||||||
|
matches=( "$stories_dir/epic-$story_epic/${key}"*.md )
|
||||||
|
shopt -u nullglob
|
||||||
|
story_ts="0"
|
||||||
|
if [ ${#matches[@]} -gt 0 ]; then
|
||||||
|
story_file="${matches[0]}"
|
||||||
|
story_ts="$(stat -c %Y "$story_file" 2>/dev/null || printf '0')"
|
||||||
|
fi
|
||||||
|
if [ "$story_ts" -gt "$active_ts" ] || { [ "$story_ts" -eq "$active_ts" ] && { [ -z "$active_epic" ] || [ "$story_epic" -gt "$active_epic" ]; }; }; then
|
||||||
|
active_ts="$story_ts"
|
||||||
|
active_epic="$story_epic"
|
||||||
|
fi
|
||||||
|
elif [ "$val" = "ready-for-dev" ] || [ "$val" = "backlog" ]; then
|
||||||
|
if [ -z "$prep_epic" ] || [ "$story_epic" -lt "$prep_epic" ]; then
|
||||||
|
prep_epic="$story_epic"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < "$sprint_status_file"
|
||||||
|
|
||||||
|
if [ -n "$active_epic" ]; then
|
||||||
|
printf 'Epic %s en cours' "$active_epic"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [ -n "$prep_epic" ]; then
|
||||||
|
printf 'Epic %s en préparation' "$prep_epic"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [ -n "$declared_in_progress_epic" ]; then
|
||||||
|
printf 'Epic %s en préparation' "$declared_in_progress_epic"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "$stories_dir" ]; then
|
||||||
|
first_story="$(find "$stories_dir" -maxdepth 2 -type f -name '*.md' \
|
||||||
|
! -name 'epic-*-retro*.md' -print -quit 2>/dev/null)"
|
||||||
|
if [ -n "$first_story" ]; then
|
||||||
|
latest_story="$(find "$stories_dir" -maxdepth 2 -type f -name '*.md' \
|
||||||
|
! -name 'epic-*-retro*.md' -print 2>/dev/null \
|
||||||
|
| xargs ls -1t 2>/dev/null \
|
||||||
|
| head -n 1)"
|
||||||
|
if [ -n "$latest_story" ]; then
|
||||||
|
epic_num="$(echo "$latest_story" | sed -n 's#.*\/epic-\([0-9][0-9]*\)\/.*#\1#p')"
|
||||||
|
if [ -n "$epic_num" ]; then
|
||||||
|
printf 'Epic %s en préparation' "$epic_num"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s' "dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_progress_state() {
|
||||||
|
local state="$1"
|
||||||
|
[ -z "$state" ] && return 0
|
||||||
|
case "$state" in
|
||||||
|
dev|active) return 0 ;;
|
||||||
|
esac
|
||||||
|
echo "$state" | grep -Eq '^Epic [0-9]+ en (préparation|cours)$'
|
||||||
|
}
|
||||||
|
|
||||||
PROJECT_NAME="${PROJECT_NAME:-$(basename "$PROJECT_ROOT")}"
|
PROJECT_NAME="${PROJECT_NAME:-$(basename "$PROJECT_ROOT")}"
|
||||||
STACK_INFERRED="$(infer_stack "$PROJECT_ROOT")"
|
STACK_INFERRED="$(infer_stack "$PROJECT_ROOT")"
|
||||||
SCOPE_INFERRED="$(infer_scope "$PROJECT_ROOT")"
|
SCOPE_INFERRED="$(infer_scope "$PROJECT_ROOT")"
|
||||||
|
STATE_INFERRED="$(infer_state "$PROJECT_ROOT")"
|
||||||
STACK="${STACK_OVERRIDE:-$STACK_INFERRED}"
|
STACK="${STACK_OVERRIDE:-$STACK_INFERRED}"
|
||||||
SCOPE="${SCOPE_OVERRIDE:-$SCOPE_INFERRED}"
|
SCOPE="${SCOPE_OVERRIDE:-$SCOPE_INFERRED}"
|
||||||
STATE="${STATE_OVERRIDE:-dev}"
|
STATE="${STATE_OVERRIDE:-$STATE_INFERRED}"
|
||||||
|
|
||||||
if ! [[ "$SCOPE" =~ ^(perso|mindleaf|lab|archive)$ ]]; then
|
if ! [[ "$SCOPE" =~ ^(perso|mindleaf|lab|archive)$ ]]; then
|
||||||
echo "Erreur: scope invalide '$SCOPE' (attendu: perso|mindleaf|lab|archive)" >&2
|
echo "Erreur: scope invalide '$SCOPE' (attendu: perso|mindleaf|lab|archive)" >&2
|
||||||
@@ -183,6 +295,8 @@ EOF
|
|||||||
|
|
||||||
if [ -n "$STATE_OVERRIDE" ]; then
|
if [ -n "$STATE_OVERRIDE" ]; then
|
||||||
TARGET_STATE="$STATE_OVERRIDE"
|
TARGET_STATE="$STATE_OVERRIDE"
|
||||||
|
elif is_progress_state "$existing_state"; then
|
||||||
|
TARGET_STATE="$STATE_INFERRED"
|
||||||
else
|
else
|
||||||
TARGET_STATE="$existing_state"
|
TARGET_STATE="$existing_state"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
---
|
---
|
||||||
name: capitalisation-triage
|
name: capitalisation-triage
|
||||||
description: Analyse et trie les propositions de `95_a_capitaliser.md` pour décider si elles doivent être intégrées dans la base globale `Lead_tech` ou déplacées vers le `CLAUDE.md` du projet source. Utiliser ce skill quand il faut: (1) détecter les doublons avec `knowledge/`, (2) filtrer ce qui est réellement global et réutilisable, (3) préparer des propositions d’intégration propres par fichier cible, et (4) router les apprentissages trop spécifiques vers la mémoire projet.
|
description: >-
|
||||||
|
Analyse et trie les propositions de `95_a_capitaliser.md` pour décider si
|
||||||
|
elles doivent être intégrées dans la base globale `Lead_tech` ou déplacées
|
||||||
|
vers le `CLAUDE.md` du projet source. Utiliser ce skill quand il faut :
|
||||||
|
(1) détecter les doublons avec `knowledge/`, (2) filtrer ce qui est
|
||||||
|
réellement global et réutilisable, (3) préparer des propositions
|
||||||
|
d’intégration propres par fichier cible, et (4) router les apprentissages
|
||||||
|
trop spécifiques vers la mémoire projet.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Objectif
|
# Objectif
|
||||||
|
|||||||
Reference in New Issue
Block a user