openspec-aware

Par chorus-aidlc · chorus

Création de contenu en mode OpenSpec opt-in pour les workflows Chorus PM sur OpenClaw. Effectue une détection inline en trois étapes de la CLI `openspec` locale, génère la structure `openspec/changes/<slug>/` sur le disque, et synchronise les fichiers Markdown dans des brouillons de documents Chorus via le wrapper `chorus-api.sh`. Lecture obligatoire pour les skills proposal, develop et yolo lorsque l'utilisateur dispose de la CLI `openspec`.

npx skills add https://github.com/chorus-aidlc/chorus --skill openspec-aware

Authoring compatible avec OpenSpec (plugin OpenClaw)

Ce skill est une sous-procédure partagée invoquée par les skills de la scène Chorus (proposal, develop, yolo) chaque fois que l'utilisateur souhaite un authoring piloté par spec via la CLI OpenSpec. C'est un opt-in :

  • S'active quand les trois signaux sont présents (voir §1) : CHORUS_OPENSPEC_MODE n'est pas off, un répertoire openspec/ existe à la racine du projet, et la CLI openspec est dans PATH.
  • Sinon le skill appelant revient à son comportement free-form existant.

Espace de noms des outils : les outils Chorus MCP sont exposés sous un préfixe chorus__ sur OpenClaw (ex. chorus__chorus_pm_create_proposal). Des noms nus sont utilisés en prose pour la lisibilité — ajoutez chorus__ en invoquant directement les outils MCP. Les appels document-mirror ne passent PAS du tout par le harnais MCP — ils passent par le wrapper chorus-api.sh (voir §2 Règle 1), qui communique avec le endpoint Chorus MCP via HTTP en utilisant votre clé API, indépendamment du namespacing chorus__.


§1. Détection — exécution inline, à chaque fois (pas de hook SessionStart sur OpenClaw)

Différence OpenClaw : le plugin Claude Code pré-calcule CHORUS_OPENSPEC_ACTIVE une fois dans un hook SessionStart et l'injecte en contexte. OpenClaw n'exécute pas ce hook. Vous DEVEZ calculer l'activité vous-même, inline, dès que vous atteignez ce skill à partir d'un skill de scène. Ne cherchez pas une valeur injectée CHORUS_OPENSPEC_ACTIVE — elle n'existera pas sur OpenClaw.

Calculez la valeur avec les trois vérifications. CHORUS_OPENSPEC_ACTIVE vaut 1 uniquement quand les trois conditions sont vraies :

  1. CHORUS_OPENSPEC_MODE n'est pas défini à off (l'opt-out explicite prime).
  2. La racine du projet contient un répertoire openspec/ (c.-à-d. quelqu'un a exécuté openspec init ici).
  3. La CLI openspec est dans PATH.

Les deux signaux (2) et (3) sont requis car le chemin authoring OpenSpec a besoin du répertoire de travail et de la CLI — avoir l'un sans l'autre rend le workflow inutilisable. Si le signal (2) est présent mais (3) ne l'est pas, affichez un conseil à l'utilisateur — "Dépôt OpenSpec détecté — installez avec : npm i -g @fission-ai/openspec" — plutôt que de choisir silencieusement le free-form.

Bloc de détection inline (exécutez ceci)

# Exécutez les trois vérifications directement. PROJECT_DIR est votre racine de projet
# (OpenClaw n'exporte pas CLAUDE_PROJECT_DIR — utilisez par défaut $PWD).
PROJECT_DIR="${PWD}"
if [ "${CHORUS_OPENSPEC_MODE:-}" = "off" ]; then
  CHORUS_OPENSPEC_ACTIVE=0
elif [ ! -d "${PROJECT_DIR}/openspec" ]; then
  CHORUS_OPENSPEC_ACTIVE=0
elif ! openspec --version >/dev/null 2>&1; then
  CHORUS_OPENSPEC_ACTIVE=0   # envisagez d'afficher le conseil d'installation à l'utilisateur
else
  CHORUS_OPENSPEC_ACTIVE=1
fi
echo "CHORUS_OPENSPEC_ACTIVE=$CHORUS_OPENSPEC_ACTIVE"

Branchez selon le résultat :

  • CHORUS_OPENSPEC_ACTIVE=1 → suivez §3 (authoring OpenSpec).
  • CHORUS_OPENSPEC_ACTIVE=0 → retournez au chemin free-form du skill appelant. Ne créez pas d'échafaudage openspec/changes/. N'ajoutez pas la ligne slug à la description de la proposal.

Exécutez cette détection inline chaque fois que proposal / develop / yolo font référence à ce skill. Il n'y a pas de valeur injectée par l'hôte à lire sur OpenClaw ; recalculer les trois vérifications est le contrat.


§2. ⛔ Deux règles non négociables

Les deux sont appliquées au moment de la review. Les deux ont causé des incidents dans les versions antérieures.

Règle 1 — Mirroir via le wrapper, ne jamais retaper le contenu du document depuis la sortie de l'agent

Les appels document/draft mirror (chorus_pm_add_document_draft, chorus_pm_update_document_draft, chorus_pm_update_document) DOIVENT passer par :

chorus-api.sh mcp-tool <tool_name> "$PAYLOAD"

avec $PAYLOAD construit en utilisant json_encode_file (défini en §3.4). Appeler ces outils directement depuis le harnais MCP de l'agent avec un champ content retapé à la main est une violation de protocole pour le mode OpenSpec et échouera la review. Raisons :

  1. Coût en tokens. Retaper un corps markdown de plusieurs milliers de lignes via le LLM brûle les tokens d'entrée + sortie pour chaque draft. Le wrapper fait circuler les octets via jq -Rs '.' — le contenu n'entre jamais en contexte LLM. Un mirroir de proposal typique à 3 docs via le script coûte à peu près zéro tokens de contenu ; via MCP direct cela coûte régulièrement 20k+.
  2. Égalité au niveau des octets. jq -Rs '.' est un encodeur byte-faithful : les antislashs, guillemets, sauts de ligne, contenu des délimiteurs de code, caractères de largeur zéro survivent tous. La ré-émission par le LLM a un taux d'échec non nul sur du long markdown — l'alignement des tableaux dérive, les échappements des délimiteurs se font « corriger », les longues URLs s'enroulent. La garantie d'égalité au niveau des octets (modulo \n final) tient uniquement sur le chemin du wrapper.
  3. Source unique de vérité. Avec le wrapper, le openspec/changes/<slug>/*.md local est autoritaire et Chorus est un miroir. Avec la ré-émission par l'agent, l'autorité se partage entre le fichier local et ce que le LLM a émis — un diff futur ne peut pas dire quel est le bon.

Disponibilité de chorus-api.sh sur OpenClaw. Ce wrapper est le transport document-mirror. Il doit être accessible sous chorus-api.sh dans PATH (le bundle standalone skill Chorus l'inclut ; si vous avez installé via ce bundle il est déjà dans PATH). S'il n'est pas dans PATH dans votre environnement OpenClaw, faites l'une des choses suivantes :

  • l'appeler par son chemin absolu (ex. "$HOME/.chorus/bin/chorus-api.sh" mcp-tool ...), ou
  • reproduire son unique comportement inline — POSTez un JSON-RPC tools/call pour <tool_name> avec arguments $PAYLOAD à "$CHORUS_URL/api/mcp" avec en-tête Authorization: Bearer $CHORUS_API_KEY en utilisant curl, capturant le corps brut pour la vérification halt-on-error de §6.

Le wrapper requiert CHORUS_URL et CHORUS_API_KEY dans l'environnement (mêmes valeurs que votre config de plugin chorusUrl / apiKey). Exportez-les avant le premier appel s'ils ne sont pas déjà définis. Ce que vous ne DEVEZ PAS faire c'est retaper le corps du document via le modèle — la règle wrapper-only tient indépendamment de comment vous invoquez le wrapper.

Règle 2 — Halt on error via chorus_check_response

Chaque appel wrapper doit vérifier trois signaux : code de sortie du wrapper, "error": dans le corps, corps vide. Un simple RC=$? est insuffisant — le wrapper sort 0 sur HTTP 401 (échec auth) avec corps vide, donc une vérification single-signal manque silencieusement l'échec runtime le plus courant. Voir §6 pour la définition du helper.


§3. Authoring en mode OpenSpec

3.1 Choisir un slug

openspec/changes/<slug>/ est le dossier local de changement. Le slug doit être :

  • kebab-case (add-export-csv, pas addExportCsv ou add_export_csv),
  • dérivé du titre Idea source,
  • unique dans openspec/changes/.

Enregistrez-le pour les étapes suivantes :

SLUG="add-export-csv"

3.2 Créer l'échafaudage du dossier de changement

openspec new change "$SLUG" --description "<résumé idée d'une ligne>"

Cela crée openspec/changes/$SLUG/ avec README.md et .openspec.yaml. Puis authoritez à la main :

Fichier local Objectif Mirroir en tant que Document.type
proposal.md Pourquoi + Quels Changements + Capacités + Impact prd
design.md Architecture, contrats, risques tech_design
specs/<capability>/spec.md Delta spec (## ADDED Requirements + Scenarios) spec (un draft par capacité)
tasks.md Liste des tâches OpenSpec (non mirrorisé — les task drafts Chorus sont la source de vérité)

Utilisez openspec instructions <artifact> --change "$SLUG" (artifacts : proposal, specs, design, tasks) pour les templates.

3.3 Forme du fichier spec (vérifiée contre openspec instructions specs)

Une delta spec liste un ou plusieurs en-têtes de bloc — ## ADDED Requirements, ## MODIFIED Requirements, ## REMOVED Requirements, ## RENAMED Requirements — et dedans, des entrées ### Requirement:. Mélangez librement dans le même fichier ; n'incluez que les blocs dont vous avez vraiment besoin.

## ADDED Requirements

Ajoutez une Requirement toute neuve au spec long terme.

## ADDED Requirements

### Requirement: <nom>
<texte requirement — utilisez SHALL / MUST pour le comportement normatif>

#### Scenario: <nom>
- **WHEN** <condition>
- **THEN** <résultat attendu>

## MODIFIED Requirements

Remplacement du bloc entier, pas fusion. Tout ce que vous écrivez ici remplace complètement la Requirement de même nom existante dans le spec long terme — titre, description, et tous les scénarios. La moitié-écrire supprime le reste.

## MODIFIED Requirements

### Requirement: <nom existant>
<texte requirement complet mis à jour>

#### Scenario: <nom>
- **WHEN** <condition>
- **THEN** <résultat attendu>

#### Scenario: <autre nom>
- **WHEN** <condition>
- **THEN** <résultat attendu>

Incluez toujours chaque scénario que vous voulez que le spec post-archive ait, même ceux qui étaient déjà présents et inchangés.

## REMOVED Requirements

Supprimez une Requirement du spec long terme. Le bloc sous l'en-tête est juste le(s) nom(s) requirement que vous supprimez — pas de scénarios nécessaires.

## REMOVED Requirements

### Requirement: <nom existant>

## RENAMED Requirements

Renommez le titre d'une Requirement. Le corps et les scénarios sont préservés tels quels dans le spec long terme ; utilisez MODIFIED à la place si vous avez besoin de changer autre chose que le titre.

## RENAMED Requirements

### Requirement: <ancien nom> -> <nouveau nom>

Règles de formatage critiques (vérifiées) :

  • Les Scénarios DOIVENT utiliser exactement 4 dièses (#### Scenario:). 3 dièses ou une liste à puces échouent silencieusement la validation.
  • Chaque ### Requirement: sous ADDED ou MODIFIED DOIT avoir au moins un #### Scenario:.
  • Les blocs MODIFIED DOIVENT inclure le contenu complet mis à jour — ils écrasent, ne corrigent pas.
  • Utilisez SHALL / MUST pour les requirements normatives ; évitez should / may.
  • La fusion dans openspec/specs/<capability>/spec.md se fait au moment de openspec archive (§3.9), pas au moment de proposal. Tant que la proposal est en vol, Chorus voit uniquement le fichier delta comme un Document spec — il n'y a pas d'état semi-fusionné pour le skill de raisonner.

Optionnel :

openspec validate "$SLUG"

3.4 Helper : json_encode_file

Définissez une fois au début de la session authoring. Avec jq disponible il fait circuler le fichier dans une chaîne JSON ; le fallback correspond à l'échappement propre de chorus-api.sh quand jq manque.

json_encode_file() {
  local _path="$1"
  if command -v jq >/dev/null 2>&1; then
    jq -Rs '.' < "$_path"
  else
    local _content
    _content=$(cat "$_path")
    _content=${_content//\\/\\\\}
    _content=${_content//\"/\\\"}
    _content=${_content//$'\n'/\\n}
    printf '"%s"' "$_content"
  fi
}

Aller-retour : le backend Chorus ajoute un \n unique au contenu draft en écriture, donc content serveur est byte-égal modulo une newline finale. Les reviewers diffant fichier local vs serveur devraient ignorer cet unique octet.

3.5 Créer le conteneur proposal avec la ligne de provenance slug

Utilisez le tool MCP régulier chorus_pm_create_proposal (pas de wrapper requis pour cet appel unique — la description est courte, la version émise par le LLM est fine). La description doit porter exactement une ligne :

OpenSpec change slug: <slug>
  • sur sa propre ligne (pas autre texte sur cette ligne),
  • préfixe littéral OpenSpec change slug: (O majuscule, S majuscule, espace unique après deux-points),
  • pas de ponctuation finale,
  • la valeur correspond au slug passé à openspec new change.

Cette ligne est grepp-able en machine par les exécutions futures de ce skill et par le déclencheur archive de §3.9.

3.6 Mirroir chaque document draft via le wrapper

Rappel Règle 1 : ces appels passent par chorus-api.sh, pas MCP direct. L'agent ne doit pas retaper le corps du document.

Assurez-vous que CHORUS_URL et CHORUS_API_KEY sont exportés (note Règle 1). Définissez le helper halt-on-error de §6 une fois au sommet, puis exécutez un appel par fichier :

# Draft PRD
CONTENT=$(json_encode_file "openspec/changes/$SLUG/proposal.md")
PAYLOAD=$(cat <<JSON
{
  "proposalUuid": "$PROPOSAL_UUID",
  "type": "prd",
  "title": "PRD: $HUMAN_TITLE",
  "content": $CONTENT
}
JSON
)
RESULT=$(chorus-api.sh mcp-tool chorus_pm_add_document_draft "$PAYLOAD")
RC=$?
chorus_check_response "chorus_pm_add_document_draft (prd)" "$RC" "$RESULT"
PRD_DRAFT_UUID=$(printf '%s' "$RESULT" | grep -o '"draftUuid"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')

Répétez avec type: "tech_design" pour design.md, et un appel par capacité avec type: "spec" pour chaque specs/<capability>/spec.md. Ne mirroisez pas tasks.md — les task drafts Chorus (créés via le tool MCP chorus_pm_add_task_draft, pas de wrapper requis) sont la source de vérité pour les tâches.

Pourquoi le parsing utilise printf '%s' "$RESULT" | grep et pas echo "$RESULT" | jq : echo interprète les séquences antislash dedans le JSON capturé, transformant \n embarqué en vraie newline. jq abandonne alors avec Invalid string: control characters from U+0000 through U+001F must be escaped. printf '%s' émet les octets capturés verbatim. Le même pattern s'applique à tout parsing de résultat wrapper dans ce skill.

3.7 Éditer un draft après le premier mirroir

Les changements de fichier local se propagent via chorus_pm_update_document_draft — même wrapper, même json_encode_file, même vérification halt.

CONTENT=$(json_encode_file "openspec/changes/$SLUG/proposal.md")
PAYLOAD=$(cat <<JSON
{
  "proposalUuid": "$PROPOSAL_UUID",
  "draftUuid": "$PRD_DRAFT_UUID",
  "content": $CONTENT
}
JSON
)
RESULT=$(chorus-api.sh mcp-tool chorus_pm_update_document_draft "$PAYLOAD")
RC=$?
chorus_check_response "chorus_pm_update_document_draft" "$RC" "$RESULT"

3.8 Éditer un Document après approbation de proposal

Une fois la proposal approuvée, les drafts se matérialisent en Documents avec leurs propres UUIDs. Pour garder openspec/changes/$SLUG/ et le Document Chorus synchronisés, mirrorez les édits de fichier via chorus_pm_update_document :

CONTENT=$(json_encode_file "openspec/changes/$SLUG/specs/<capability>/spec.md")
PAYLOAD=$(cat <<JSON
{
  "documentUuid": "$SPEC_DOCUMENT_UUID",
  "content": $CONTENT
}
JSON
)
RESULT=$(chorus-api.sh mcp-tool chorus_pm_update_document "$PAYLOAD")
RC=$?
chorus_check_response "chorus_pm_update_document" "$RC" "$RESULT"

Pour ré-dériver $SPEC_DOCUMENT_UUID depuis un shell frais, cherchez-le via chorus_get_documents pour le project de la proposal et faites correspondre par title + type. Ré-dérivez $SLUG en greppant la description de la proposal pour ^OpenSpec change slug:.

3.9 Archive après vérification de la dernière tâche

Différence OpenClaw : le plugin Claude Code a un hook PostToolUse (bin/on-post-verify-task.sh) qui se déclenche après chorus_admin_verify_task et injecte un rappel openspec archive <slug>. OpenClaw n'a pas de tel hook. Vous (l'agent) devez détecter le déclencheur vous-même : après chaque chorus_admin_verify_task, vérifiez si la tâche qui vient d'être vérifiée était la DERNIÈRE tâche de son idée mode OpenSpec (chaque Task de chaque Proposal approuvée de cette idée est maintenant done/closed, et la proposal description porte une ligne OpenSpec change slug: <slug>). Si oui, lancez le flux archive ci-dessous. Sinon, ne faites rien.

Quand le déclencheur tient, vous effectuez l'archive :

  1. Exécutez l'archive localement. Utilisez --yes pour le mode non-interactif. Ne passez pas --skip-specs (annule le mirror-back) ou --no-validate (laisse les deltas malformés corrompre les specs cumulatives).

    openspec archive "$SLUG" --yes

    Cela déplace openspec/changes/$SLUG/ sous openspec/changes/archive/<date>-<slug>/ et émet/met à jour openspec/specs/<capability>/spec.md pour chaque capacité. (Lancez openspec archive --help contre votre version installée pour confirmer le set de drapeaux courant — les drapeaux peuvent dériver entre les versions.)

  2. Mirrorez chaque openspec/specs/<capability>/spec.md mis à jour vers le Document Chorus post-approbation correspondant (contrat §3.8). chorus_get_documents ne supporte que les filtres serveur projectUuid + type ; filtrez par title client-side. Un appel chorus_pm_update_document par capacité.

  3. Halte sur toute erreur de openspec archive ou chorus_pm_update_document. Affichez stderr verbatim, postez un commentaire sur la proposal enregistrant l'échec (chorus_add_comment avec targetType: "proposal", targetUuid: <proposalUuid>), puis arrêtez. Pas de retry. Correspond à §6 "pas d'erreurs silencieuses." (Commentez sur la proposal, pas l'idée : l'échec est dans l'archivage des specs dérivées de proposal, et les proposals peuvent être inputType: "document" sans idée attachée.)

  4. Confirmez succès. Listez les fichiers openspec/specs/<capability>/spec.md et vérifiez qu'ils font aller-retour byte-égal (modulo newline finale) avec leurs homologues Document Chorus.

Opt-in strict : si la tâche vérifiée n'est pas la dernière de son idée, OU la description de la proposal ne porte pas de ligne OpenSpec change slug: <slug>, OU le shell local n'a pas de CLI openspec, ne faites rien — pas d'archive. Le comportement free-form existant est préservé.


§4. Authoring fallback (sans openspec)

Quand la détection de §1 place l'agent en mode fallback (CHORUS_OPENSPEC_ACTIVE=0), ce skill est un no-op. Retournez au chemin free-form du skill appelant :

  • Aucun dossier openspec/changes/ n'est créé ou référencé.
  • Aucune ligne OpenSpec change slug: … n'est ajoutée à la description de proposal.
  • Les document drafts sont authorisés via des appels MCP chorus_pm_add_document_draft directs avec content inline — comme avant que ce skill existe.
  • La Règle 1 (mirroir wrapper-only) ne s'applique pas — il n'y a pas de source de vérité fichier local.
  • Le flux archive de §3.9 ne fait rien (pas de slug → pas d'archive).

§5. Mappage de type de document (tableau de référence)

Fichier local Chorus Document.type Mirrorisé ?
openspec/changes/<slug>/proposal.md prd oui
openspec/changes/<slug>/design.md tech_design oui
openspec/changes/<slug>/specs/<capability>/spec.md spec oui (un draft par capacité)
openspec/changes/<slug>/tasks.md (non mappé) non — les task drafts Chorus sont la source de vérité

prd, tech_design, spec sont des valeurs Document.type valides pré-existantes — aucun changement de schema requis.


§6. Visibilité des défaillances — le helper chorus_check_response

Il y a un cas limite connu du wrapper : quand le serveur retourne HTTP 4xx (ex. 401 d'une mauvaise CHORUS_API_KEY), chorus-api.sh mcp-tool capture le corps d'erreur JSON-RPC en interne, le pipe à travers un filtre jq .result.content[]? qui ne produit aucune sortie quand .result est absent, et sort 0 avec stdout vide. Une vérification RC=$? seule ne halterait pas cela — le mode d'échec runtime le plus courant serait invisible. (Si vous aviez reproduit le wrapper inline via curl par la note Règle 1, la même vérification à trois signaux s'applique au corps HTTP brut.)

Définissez ce helper une fois au top de la session authoring et utilisez-le après chaque appel wrapper :

chorus_check_response() {
  local _tool="$1"
  local _rc="$2"
  local _body="$3"
  local _has_error=0
  local _is_empty=0

  local _trimmed
  _trimmed=$(printf '%s' "$_body" | tr -d ' \t\n\r')
  [ -z "$_trimmed" ] && _is_empty=1

  if [ "$_is_empty" -eq 0 ]; then
    if command -v jq >/dev/null 2>&1; then
      if printf '%s' "$_body" | jq -e 'try ([.. | objects | has("error")] | any) catch false' >/dev/null 2>&1; then
        _has_error=1
      fi
    else
      printf '%s' "$_body" | grep -qE '"error"[[:space:]]*:' && _has_error=1
    fi
  fi

  if [ "$_rc" -ne 0 ] || [ "$_has_error" -eq 1 ] || [ "$_is_empty" -eq 1 ]; then
    echo "ERROR: $_tool failed (exit=$_rc, error_in_body=$_has_error, empty_body=$_is_empty)" >&2
    echo "Output: $_body" >&2
    [ "$_rc" -ne 0 ] && exit "$_rc" || exit 1
  fi
}

Anti-patterns — ne pas :

  • Réduire à || true.
  • Rediriger stderr vers /dev/null.
  • Enterrer l'appel wrapper dedans une pipeline (masque $?).
  • Passer de capturer $RESULT dans une variable ; le helper a besoin du corps.
  • Utiliser uniquement if [ "$RC" -ne 0 ]; then ... — cela manque le chemin erreur-HTTP.

Forme d'appel site minimale :

RESULT=$(chorus-api.sh mcp-tool <tool_name> "$PAYLOAD")
RC=$?
chorus_check_response "<tool_name>" "$RC" "$RESULT"
# ...si nous atteignons ici, l'appel a réussi ; parsez RESULT et continuez.

C'est la politique projet-wide : pas d'erreurs silencieuses.


§7. Checklist de référence rapide

Quand invoqué depuis un skill de scène (proposal / develop / yolo) :

  1. Exécutez la détection à trois vérifications inline de §1 vous-même (pas de hook SessionStart sur OpenClaw). Calculez CHORUS_OPENSPEC_ACTIVE de : CHORUS_OPENSPEC_MODE != off + répertoire openspec/ présent + CLI openspec sur PATH.
  2. Si CHORUS_OPENSPEC_ACTIVE=0 → retournez au chemin free-form du caller (§4).
  3. Sinon : a. Choisissez $SLUG (§3.1). b. openspec new change "$SLUG" (§3.2). c. Authorisez proposal.md, design.md, specs/<capability>/spec.md (§3.2–§3.3). Mélangez les blocs ADDED / MODIFIED / REMOVED / RENAMED selon les besoins ; souvenez-vous que MODIFIED écrase la Requirement entière. d. Optionnel : openspec validate "$SLUG". e. chorus_pm_create_proposal (MCP direct) avec la ligne OpenSpec change slug: $SLUG en description (§3.5). f. Exportez CHORUS_URL / CHORUS_API_KEY ; définissez les helpers json_encode_file, chorus_check_response ; confirmez que chorus-api.sh est accessible (note Règle 1). g. Pour chaque ligne en §5 avec "oui" — mirrorez via chorus-api.sh mcp-tool chorus_pm_add_document_draft (§3.6). Enregistrez chaque $DRAFT_UUID. h. Sur toute chorus_check_response échouée — halte, surfacez l'erreur, ne procédez pas.
  4. Éditions avant approbation → §3.7. Éditions après approbation → §3.8.
  5. Dernière tâche vérifiée → détectez le déclencheur vous-même (pas de hook) → lancez le flux archive §3.9.

Skills similaires