[DTP-Worker] Sprint 8 · buffer · Fiches agents/colonne « Entrée CLI » : gate d'IDENTITÉ des 16 jeux de sous-commandes documentés (03_agents/*/AGENT.md) recomputés PAR AST des subparsers.add_parser() réels des scripts
Classe fermée `agent-fiche-role-attrs-ungated` (sous-classe colonne CLI). La colonne « Entrée CLI » des tables de livrables cite à la main le jeu de verbes de chaque module (`publiciste.py parse|validate|generate|run`, `faisabilite_gen.py score|scaffold|generate|batch`, …). Les blocs amont gatent déjà génériquement — pour TOUTES les fiches — les colonnes « Tests » (vs regression_plan) et « Job CI » (vs ci.yml), mais RIEN ne liait les VERBES écrits aux sous-commandes que le CLI EXPOSE. Piège #6 : renommer/ajouter/retirer/réordonner un add_parser dans le .py (ou le mal recopier dans la fiche) laisse la fiche documenter une surface d'invocation FANTÔME — la doc d'identité qui dit à l'agent COMMENT lancer son livrable ment en silence, le CI reste vert (le module tourne sous le vrai verbe), aucune suite tests/ ne l'attrape. Gate ajoutée (ci/check_readme_claims.sh, bloc « colonne Entrée CLI ») : _cli_subcommands() recompute via AST l'ensemble ORDONNÉ des subparsers.add_parser("<verbe>") ; script résolu par le chemin de module de la MÊME ligne de table (lien …/<path>/README.md) + basename ; égalité EXACTE ensemble ET ordre exigée ; `|` markdown échappés `\|` dé-échappés avant découpe. 16 cellules gatées (6 fiches ; regex [a-z0-9_] capte audit_5d/4big_gen.py). 7 morsures vérifiées, restaurées vert : fiche renomme/ajoute/retire/réordonne un verbe (4) · le vrai « vert trompeur » add_parser renommé dans le .py fiche inchangée (5) · nouveau add_parser non documenté (6) · script déplacé => INTROUVABLE (7). 6 gates re-verts. ci/README.md catalogue mis à jour. Incident : un git checkout large a reverté l'edit non-commité du gate ; ré-appliqué (git clean JAMAIS utilisé · ci-allow sur le log). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1345,3 +1345,61 @@ Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit
|
||||
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; le seed réel des
|
||||
Workspaces/rôles `bench` reste côté VPS).
|
||||
- **Auto-score 4Big** : 96/100.
|
||||
|
||||
---
|
||||
|
||||
## Sprint 8 · buffer · Gate d'IDENTITÉ de la colonne « Entrée CLI » des fiches agents — verbes documentés == subparsers argparse réels
|
||||
|
||||
**Classe fermée** : `agent-fiche-role-attrs-ungated` (mémoire) — sous-classe « colonne
|
||||
CLI ». Chaque table de livrables des fiches `03_agents/*/AGENT.md` porte une colonne
|
||||
« Entrée CLI » citant, entre backticks, le point d'entrée du module ET son jeu de
|
||||
verbes (`publiciste.py parse|validate|generate|run`, `faisabilite_gen.py
|
||||
score|scaffold|generate|batch`, `rbac_apply_plan.py build|validate`, …). Cette liste
|
||||
est **saisie à la main**. Les gates amont couvrent déjà génériquement — pour TOUTES les
|
||||
fiches, via `git ls-files` — la colonne « Tests » (recomputée depuis `regression_plan`)
|
||||
et la colonne « Job CI » (contre `ci.yml`), mais **RIEN** ne vérifiait que les VERBES
|
||||
écrits sont ceux que le CLI **expose réellement**.
|
||||
|
||||
**Piège** : renommer une sous-commande (`generate`→`render`), en AJOUTER une
|
||||
(`publish`), en RETIRER une ou les RÉORDONNER laisse la fiche documenter une surface
|
||||
d'invocation **fantôme** : le module tourne (le CI appelle le vrai verbe), la doc
|
||||
d'identité de l'agent — celle qui lui dit COMMENT lancer son propre livrable — ment en
|
||||
silence. Aucune suite `tests/` (FONCTIONS du module, jamais la prose de fiche) ne
|
||||
l'attrape. Classe explicitement notée en mémoire.
|
||||
|
||||
**Gate ajoutée** (`ci/check_readme_claims.sh`, bloc « colonne Entrée CLI », après le
|
||||
bloc « Job CI fiches ») : `_cli_subcommands()` **RECOMPUTE** via **AST** l'ensemble
|
||||
ORDONNÉ des `subparsers.add_parser("<verbe>")` de chaque script (jamais une liste à la
|
||||
main · #6). Résolution du script : chemin du module de la MÊME ligne de table (lien
|
||||
`…/05_deliverables_mvp/<path>/README.md`) + basename du CLI → `{D}/<path>/<file>.py`
|
||||
(chaque cellule liée à SON module · pas de collision de basename). Égalité EXIGÉE
|
||||
**exacte** — ensemble ET ordre (l'ordre de `add_parser` == ordre de l'aide argparse).
|
||||
Les `|` séparateurs markdown sont échappés `\|` dans les cellules : dé-échappés avant
|
||||
découpe. Direction fiche→CLI : la fiche ne peut pas mentir sur les verbes du script.
|
||||
**16 cellules** gatées (6 fiches : crm×3 · devops · erpnext_backend×5 · faisabilite×2 ·
|
||||
publiciste · qa×4 — dont `audit_5d_gen.py`/`audit_4big_gen.py` que les regex `[a-z_]`
|
||||
manquaient, d'où `[a-z0-9_]`). Actuellement **toutes exactes** — la gate verrouille
|
||||
l'état correct.
|
||||
|
||||
**7 morsures vérifiées** (restaurées vert après chaque) :
|
||||
1. FICHE renomme verbe (`generate→render`) → manquants/en trop, `seul_ordre_diffère=False`.
|
||||
2. FICHE ajoute verbe fantôme (`publish`) → en trop.
|
||||
3. FICHE retire un verbe (`batch`) → manquant.
|
||||
4. FICHE réordonne (`parse|validate`↔) → `seul_ordre_diffère=True` (ordre discriminé).
|
||||
5. **Le vrai « vert trompeur »** : `add_parser("generate")→("render")` dans le `.py`,
|
||||
fiche INCHANGÉE → red (la source de vérité a bougé sous la fiche).
|
||||
6. Nouveau `add_parser("audit")` dans le `.py` non documenté → manquant côté fiche.
|
||||
7. Script déplacé/renommé (`publiciste.py→publiciste_v2.py`) → « script INTROUVABLE ».
|
||||
|
||||
**Incident process** : un `git checkout .` de nettoyage d'un scénario adverse a reverté
|
||||
l'edit **non-commité** de `ci/check_readme_claims.sh` (bloc effacé). Ré-appliqué à
|
||||
l'identique puis re-vérifié. Leçon : ne jamais `git checkout .`/`--` large avec un edit
|
||||
de gate non commité en cours ; restaurer par chemin ciblé uniquement. (`git clean` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->
|
||||
reste l'interdit absolu — non utilisé.)
|
||||
|
||||
- **6 gates re-verts** : `check_artifacts` · `check_docs` · `guard_constraints` ·
|
||||
`check_readme_claims` (16 ✓ Entrée CLI) · `check_regression` · `check_ci_integrity`.
|
||||
Aucun hash-lock sur les scripts de gate → rien d'autre à régénérer.
|
||||
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ·
|
||||
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact).
|
||||
- **Auto-score 4Big** : 96/100.
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -4908,6 +4908,109 @@ if ci_lines is not None:
|
||||
bad("Job CI fiches · AUCUN token `*-tests` trouvé dans les fiches — la "
|
||||
"disparition de la colonne « Job CI » est elle-même une régression.")
|
||||
|
||||
# ============================================================================
|
||||
# Fiches agents · colonne « Entrée CLI » — le JEU DE SOUS-COMMANDES documenté
|
||||
# DOIT être l'ensemble EXACT des subparsers argparse du générateur (même classe
|
||||
# de « vert trompeur », côté surface d'invocation du module).
|
||||
# ----------------------------------------------------------------------------
|
||||
# Chaque table de livrables des fiches 03_agents/*/AGENT.md porte une colonne
|
||||
# « Entrée CLI » qui cite, entre backticks, le point d'entrée du module ET son
|
||||
# jeu de verbes : `publiciste.py parse|validate|generate|run`,
|
||||
# `faisabilite_gen.py score|scaffold|generate|batch`, `rbac_apply_plan.py
|
||||
# build|validate`, … Cette liste est SAISIE À LA MAIN. La source de vérité est
|
||||
# l'ensemble des `subparsers.add_parser("<verbe>")` du script réel. Les gates
|
||||
# amont couvrent le COMPTE de tests (colonne « Tests »), le NOM du job CI
|
||||
# (colonne « Job CI ») et la reproductibilité des artefacts `out/` — mais RIEN
|
||||
# ne vérifiait que les VERBES écrits dans la fiche sont ceux que le CLI expose
|
||||
# réellement. Renommer une sous-commande (`generate` → `render`), en AJOUTER une
|
||||
# (`publish`), en RETIRER une ou les RÉORDONNER laisse la fiche documenter une
|
||||
# surface d'invocation FANTÔME : le module tourne (le CI appelle le vrai verbe),
|
||||
# la doc d'identité de l'agent — celle qui lui dit COMMENT lancer son propre
|
||||
# livrable — ment en silence. Aucune suite tests/ (FONCTIONS du module, jamais
|
||||
# la prose de fiche) ne l'attrape ; classe explicitement notée en mémoire
|
||||
# (`agent-fiche-role-attrs-ungated`) : la fiche restitue une donnée data-derived
|
||||
# (ici la CLI) sur une surface distincte de tout gate.
|
||||
#
|
||||
# On RECOMPUTE l'ensemble ordonné des sous-commandes depuis l'AST du script
|
||||
# (jamais une liste à la main · zéro chiffre/verbe inventé · #6) et on exige
|
||||
# l'égalité EXACTE (ensemble ET ordre — l'ordre de `add_parser` est celui de
|
||||
# l'aide argparse) avec ce que la fiche ÉCRIT. Le script est résolu par le
|
||||
# chemin du module de la MÊME ligne de table (lien `…/05_deliverables_mvp/<path>/
|
||||
# README.md`) + le basename du CLI → `{D}/<path>/<file>.py`, ce qui lie chaque
|
||||
# cellule CLI à SON module (pas de collision de basename). Direction fiche→CLI :
|
||||
# la fiche ne peut pas mentir sur les verbes que le script expose.
|
||||
# ============================================================================
|
||||
def _cli_subcommands(pyfile):
|
||||
"""Sous-commandes argparse d'un script, en ordre source (via AST)."""
|
||||
src = open(pyfile, encoding="utf-8").read()
|
||||
tree = ast.parse(src, filename=pyfile)
|
||||
hits = []
|
||||
for node in ast.walk(tree):
|
||||
if (isinstance(node, ast.Call)
|
||||
and isinstance(node.func, ast.Attribute)
|
||||
and node.func.attr == "add_parser"
|
||||
and node.args
|
||||
and isinstance(node.args[0], ast.Constant)
|
||||
and isinstance(node.args[0].value, str)):
|
||||
hits.append((node.lineno, node.col_offset, node.args[0].value))
|
||||
hits.sort()
|
||||
return [v for _, _, v in hits]
|
||||
|
||||
# Ligne de table portant À LA FOIS le lien module (…/<path>/README.md) et une
|
||||
# cellule CLI `\`<file>.py <verbe>|<verbe>|…\`` → on scope aux vraies lignes de
|
||||
# table (une mention CLI en prose, sans lien module, est ignorée). Les `|`
|
||||
# séparateurs de colonnes markdown sont échappés `\|` dans la cellule.
|
||||
cli_pathre = re.compile(r"05_deliverables_mvp/([\w/]+?)/README\.md")
|
||||
cli_cellre = re.compile(r"`([a-z][a-z0-9_]*\.py) ([a-z][a-z|\\]+)`")
|
||||
fiches_cli = subprocess.run(
|
||||
["git", "ls-files", "03_agents/*/AGENT.md"],
|
||||
capture_output=True, text=True).stdout.split()
|
||||
seen_cli = 0
|
||||
for af in fiches_cli:
|
||||
try:
|
||||
flines = open(af, encoding="utf-8").read().splitlines()
|
||||
except OSError as e:
|
||||
bad(f"Entrée CLI fiches · {af} illisible : {e}"); continue
|
||||
for ln, line in enumerate(flines, 1):
|
||||
mp = cli_pathre.search(line)
|
||||
mc = cli_cellre.search(line)
|
||||
if not (mp and mc):
|
||||
continue
|
||||
path, cli_file, verbs_raw = mp.group(1), mc.group(1), mc.group(2)
|
||||
# retirer les backslashes d'échappement avant de découper les verbes
|
||||
claimed = verbs_raw.replace("\\", "").split("|")
|
||||
pyfile = f"{D}/{path}/{cli_file}"
|
||||
seen_cli += 1
|
||||
try:
|
||||
actual = _cli_subcommands(pyfile)
|
||||
except FileNotFoundError:
|
||||
bad(f"Entrée CLI · {af}:{ln} · `{cli_file}` — script INTROUVABLE à "
|
||||
f"`{pyfile}` (module déplacé/renommé ? régénérer la fiche)")
|
||||
continue
|
||||
except SyntaxError as e:
|
||||
bad(f"Entrée CLI · {af}:{ln} · `{cli_file}` — AST cassé ({e}) : "
|
||||
f"source de vérité illisible")
|
||||
continue
|
||||
if not actual:
|
||||
bad(f"Entrée CLI · {af}:{ln} · `{cli_file}` — la fiche cite les "
|
||||
f"verbes {claimed} MAIS le script n'expose AUCUN "
|
||||
f"`add_parser(...)` (CLI refondue ? régénérer la fiche)")
|
||||
elif claimed == actual:
|
||||
good(f"Entrée CLI · {af}:{ln} · `{cli_file}` — verbes "
|
||||
f"{'|'.join(claimed)} == subparsers ({pyfile})")
|
||||
else:
|
||||
miss = [v for v in actual if v not in claimed]
|
||||
extra = [v for v in claimed if v not in actual]
|
||||
order_only = set(claimed) == set(actual)
|
||||
bad(f"Entrée CLI · {af}:{ln} · `{cli_file}` — la fiche dit "
|
||||
f"{claimed} MAIS le CLI expose {actual} "
|
||||
f"(manquants={miss} · en trop={extra} · "
|
||||
f"seul_ordre_diffère={order_only}) → régénérer la fiche")
|
||||
if seen_cli == 0:
|
||||
bad("Entrée CLI fiches · AUCUNE cellule CLI `<file>.py <verbes>` liée à un "
|
||||
"module trouvée dans les fiches — la disparition de la colonne « Entrée "
|
||||
"CLI » est elle-même une régression.")
|
||||
|
||||
# ============================================================================
|
||||
# Legal · CONFOTUR — IDENTITÉ des ENTITÉS PORTEUSES ancrée sur CLAUDE.md §Entités
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user