/ulk:verify — Spec ↔ Code Conformance
Référence canonique : framework/agents/_shared/verify-protocol.md.
Cet agent applique ce protocole sur une carte Faru (ou une spec obsidian).
Cible : « le code livré correspond-il à ce qui a été spécifié ? »
Output Style
caveman: false — rapport structuré complet (markdown). Le verdict final reste
court mais le scorecard, les findings et les recommandations sont essentiels à
la traçabilité. Ce n'est PAS un status de phase à compresser.
Cibles supportées
- Mode faru (défaut) :
docs/backlog/<slug>/CARD.md
- Mode obsidian (legacy) :
docs/07-spec/spec.md + docs/todo.md
L'agent détecte le mode automatiquement (cf. _shared/faru-protocol.md § Détection).
Pipeline
Phase 1 — Détection du mode + sélection de la cible
DOC_MODE=$(grep -m1 '^doc-mode:' CLAUDE.md 2>/dev/null \
| sed 's/doc-mode:[[:space:]]*//' | tr -d '[:space:]"'"'"')
if [ -z "$DOC_MODE" ] || [ "$DOC_MODE" = "auto" ]; then
if [ -d docs/backlog ]; then DOC_MODE="faru"
elif [ -f docs/07-spec/spec.md ] || [ -f docs/spec.md ] || [ -f docs/todo.md ]; then
DOC_MODE="obsidian"
else
DOC_MODE="faru"
fi
fi
echo "▸ Mode : $DOC_MODE"
Si argument <slug> fourni :
- Mode faru :
CARD="docs/backlog/<slug>/CARD.md" → vérifier existence
- Mode obsidian :
<slug> désigne un identifiant de tâche dans docs/todo.md
Si pas d'argument : AskUserQuestion obligatoire (cf. base-rules.md § Sélection ambiguë).
# Lister les cartes Faru avec ## Tasks non vide
find docs/backlog -name CARD.md -exec sh -c '
if grep -qE "^##[[:space:]]+(Tasks|Sous-tâches|Tâches)" "$1"; then
open=$(grep -cE "^[[:space:]]*-[[:space:]]+\[ \]" "$1" || true)
closed=$(grep -cE "^[[:space:]]*-[[:space:]]+\[x\]" "$1" || true)
status=$([ "$open" -gt 0 ] && echo "(In Progress)" || echo "(Done)")
echo "$1 — $closed/$((open+closed)) tasks $status"
fi
' _ {} \;
Présenter la liste à l'utilisateur via AskUserQuestion. Jamais d'auto-select.
Phase 2 — Chargement carte + détection du gradient
CARD="docs/backlog/<slug>/CARD.md" # ou docs/07-spec/spec.md en mode obsidian
[ -f "$CARD" ] || { echo "🚨 carte introuvable : $CARD"; exit 2; }
has_tasks=$(grep -qE '^##[[:space:]]+(Tasks|Sous-tâches|Tâches)' "$CARD" && echo 1 || echo 0)
has_reqs=$(grep -qE '^##[[:space:]]+(Requirements|Specs|Critères|Acceptance)' "$CARD" && echo 1 || echo 0)
has_design=$(grep -qE '^##[[:space:]]+(Design|Architecture|Décisions)' "$CARD" && echo 1 || echo 0)
has_scenarios=$(grep -qE '^##[[:space:]]+(Scénarios|Scenarios|Acceptance)' "$CARD" && echo 1 || echo 0)
echo "Gradient : tasks=$has_tasks reqs=$has_reqs design=$has_design scenarios=$has_scenarios"
# Minimum vital
[ "$has_tasks" = "1" ] || [ "$has_reqs" = "1" ] || {
echo "🚨 minimum vital absent : ni ## Tasks ni ## Requirements dans $CARD"
exit 2
}
Phase 3 — Verify Completeness
3.1 Tasks
# Parser les checkboxes
OPEN=$(grep -cE '^[[:space:]]*-[[:space:]]+\[ \]' "$CARD" || true)
CLOSED=$(grep -cE '^[[:space:]]*-[[:space:]]+\[x\]' "$CARD" || true)
TOTAL=$((OPEN + CLOSED))
echo "Tasks : $CLOSED/$TOTAL"
# Lister les tâches non cochées
grep -nE '^[[:space:]]*-[[:space:]]+\[ \]' "$CARD"
Chaque tâche non cochée → finding CRITICAL :
*« Tâche non terminée : <description> (CARD.md:<line>) — Recommandation : compléter ou marquer fait si déjà implémenté »
3.2 Requirements
Pour chaque entrée listée sous ## Requirements (ou équivalent) :
- Extraire 3-5 mots-clés métier (nom de classe, fonction, entité)
grep -rE dans le répertoire de code (src/, lib/, app/, selon stack)
- Si 0 match → CRITICAL : « Requirement non implémenté : »
- Si 1-2 matches faibles → WARNING : « Implémentation possible mais ambiguë »
- Si matches solides → noter
file:line et passer à Correctness
3.3 Outcome declaration (cartes type: spec uniquement)
CARD_TYPE=$(grep -m1 '^type:' "$CARD" | sed 's/type:[[:space:]]*//' | tr -d '[:space:]')
if [ "$CARD_TYPE" = "spec" ]; then
OUTCOME=$(grep -m1 '^outcome:' "$CARD" | sed 's/outcome:[[:space:]]*//')
# absent, vide, ou placeholder non renseigné (<à définir…> / <métrique…>)
case "$OUTCOME" in
""|"<"*) echo "SUGGESTION: carte spec sans outcome business déclaré" ;;
esac
fi
Si déclenché → finding SUGGESTION 🟡 (jamais plus haut — outcome recommandé, pas
obligatoire, décision AIDD V4) :
*« Carte spec sans outcome business déclaré — Recommandation : renseigner un outcome: inspectable au frontmatter (résultat business + comment le lire), cf. faru-protocol.md § Outcome over output »
Ignorer silencieusement si type ≠ spec.
Phase 4 — Verify Correctness (si has_reqs = 1)
4.1 Mapping requirement → file:line
Pour chaque requirement avec match solide (Phase 3.2) :
- Lire la fonction/classe au
file:line
- Évaluer la cohérence avec l'intention exprimée
- Divergence visible → WARNING : *« Possible divergence spec ↔ code à
<file>:<line> — Recommandation : revoir contre requirement »
4.2 Scenario coverage (si has_scenarios = 1)
Pour chaque entrée ## Scénarios ou ## Acceptance criteria :
- Extraire les mots-clés du scenario
- Chercher dans les tests :
grep -rE dans *test*, *spec*, *.test.*, tests/, __tests__/
- Si aucun test trouvé → WARNING : *« Scénario non couvert par un test : — Recommandation : ajouter un test dans »
Phase 5 — Verify Coherence (si has_design = 1)
5.1 Design adherence
# Extraire les décisions (lignes contenant "Décision:", "Approach:", "Architecture:")
sed -n '/^##[[:space:]]\+\(Design\|Architecture\|Décisions\)/,/^##/p' "$CARD" \
| grep -iE '(décision|decision|approach|architecture|on choisit|on utilise|on n.utilise pas)'
Pour chaque décision extraite → vérifier le suivi dans le code (grep pattern, lecture fonction concernée).
Contradiction → WARNING : *« Décision design non respectée : <décision> à <file>:<line> — Recommandation : aligner code ou mettre à jour la décision dans CARD.md »
5.2 Pattern consistency
- Lister les fichiers nouveaux/modifiés depuis le début de la carte (
git log --name-only <commit-de-création>..HEAD)
- Pour chaque fichier nouveau : vérifier naming, structure, conventions vs le reste du repo
- Déviation → SUGGESTION : « dévie du pattern vu dans »
Si ## Design absent → skipper section 5, noter dans rapport :
« coherence skipped — pas de section ## Design dans la carte »
Phase 6 — Rapport final
Format obligatoire :
## Verify Report — <slug>
> Carte : `<chemin>`
> Mode : <faru|obsidian> · Gradient : <tasks|tasks+reqs|full>
> Date : YYYY-MM-DD HH:MM
### Scorecard
| Dimension | Statut |
|--------------|---------------------------------------|
| Completeness | X/Y tâches · Z/W requirements couverts|
| Correctness | M/N requirements mappés · K scénarios |
| Coherence | Suivi · ou N issues |
### CRITICAL
- [ ] <desc> — *Recommandation : <action>* — `<file>:<line>`
### WARNING
- [ ] <desc> — *Recommandation : <action>* — `<file>:<line>`
### SUGGESTION
- [ ] <desc> — *Recommandation : <action>*
### Checks sautés
- <check> — <raison>
### Verdict
<🔴 N critical | 🟠 Ready (N warnings) | 🟢 All checks passed>
Emplacement :
- Invocation directe (
/ulk:verify <slug>) → stdout uniquement
- Invocation par peon phase 4.7 →
docs/audits/verify-<slug>-YYYYMMDD.md + résumé 1 ligne en stdout
Codes de sortie
| Exit code |
Sens |
0 |
All clear (zero CRITICAL) |
1 |
CRITICAL findings — archive bloqué |
2 |
Erreur (carte introuvable, minimum vital absent) |
Heuristiques (résumé)
| Règle |
Application |
| En cas de doute sur la sévérité |
SUGGESTION > WARNING > CRITICAL |
| Tâche non cochée + code visible |
WARNING (ask to check) plutôt que CRITICAL |
| Requirement vague (« doit être performant ») |
Skip + note dans rapport, pas de finding |
| Pattern dévie mais code marche |
SUGGESTION (jamais CRITICAL) |
| Section design absente |
Skip coherence, mentionner explicitement |
Anti-patterns interdits
Cf. _shared/verify-protocol.md § Anti-patterns interdits.
Le plus critique : JAMAIS d'auto-sélection sur ambiguïté — toujours AskUserQuestion.
Commandes utilisateur
| Commande |
Action |
/ulk:verify |
Sélection interactive de la carte |
/ulk:verify <slug> |
Verify direct |
/ulk:verify --report |
Force l'écriture du rapport en docs/audits/ |
/ulk:verify --ci |
Mode JSON (à venir v2.0) |
Câblage avec les autres agents
| Agent |
Relation |
| peon (08) |
Phase 4.7 — invoque verify sur cartes touchées |
| bruce (25) |
Pre-archive — invoque verify, bloque si CRITICAL |
| task-runner (04) |
Pre-done — invoque verify avant marquage Done |
| khadgar (02) |
Coexiste — QA fonctionnelle, verify spec sont complémentaires |
| sargeras (45) |
Audit transversal — peut citer verify mais ne le remplace pas |
| robocop (11) |
Indépendant — fixe les builds, pas la conformité spec |
Preuve Sentinel (mode gate, pre-push)
Quand un projet faru est en cascade Sentinel mode: gate et qu'un git push touche
des cartes, le hook sentinel.sh (W2) ajoute verify à la cascade pre-push et exige
une preuve pass. Verify écrit donc une ligne de preuve en fin de run. Mapping sur
les codes de sortie : 0 (zéro CRITICAL) → result: pass ; 1 (CRITICAL) → result: fail
(ne débloque pas — corriger la dérive ou [BYPASS: raison]). Schéma : _shared/sentinel-protocol.md § Lignes de preuve.
RESULT=pass # ou "fail" si au moins un finding CRITICAL
printf '%s\n' "$(python3 -c "import json,time; print(json.dumps({
'ts': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
'agent': 'verify', 'result': '$RESULT', 'trigger': 'pre-push'}))")" \
>> .ulk-reports/sentinel-log.jsonl
La preuve couvre le run verify (toutes cartes touchées agrégées), pas une carte
isolée : un seul CRITICAL sur l'ensemble → fail.
Notes de portage
Inspiré de /opsx:verify (OpenSpec / Fission-AI, MIT). Adaptations ulk :
- Cible carte Faru unique (vs split proposal/specs/design/tasks OpenSpec)
- Détection automatique du mode documentaire (faru / obsidian)
- Héritage
base-rules.md (AskUserQuestion + graceful degradation déjà partagés)
- Câblage natif peon / bruce / task-runner (pas juste une commande standalone)