openspec-aware-chorus

Par chorus-aidlc · chorus

Création de contenu en mode OpenSpec opt-in pour les workflows Chorus PM sur dsh. Exécute une détection à trois vérifications inline pour la CLI `openspec` locale, structure `openspec/changes/<slug>/` sur le disque, et synchronise les fichiers Markdown dans des brouillons de documents Chorus via le wrapper `chorus-mcp-call.mjs` local au package. Lecture obligatoire pour les skills proposal, develop et yolo lorsque l'utilisateur a la CLI `openspec` installée.

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

Authoring en connaissant OpenSpec (skill dsh plugin)

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

  • 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 sur PATH.
  • Sinon, le skill appelant revient à son comportement existant en mode libre.

Espace de noms des outils : Les outils Chorus MCP sont exposés avec un préfixe mcp__chorus__ sur dsh (ex. mcp__chorus__chorus_pm_create_proposal). Les noms nus sont utilisés dans le texte pour la lisibilité — préfixez avec mcp__chorus__ lors de l'invocation directe des outils MCP. Les appels miroir-document ne passent PAS du tout par le harness MCP — ils passent par le wrapper local chorus-mcp-call.mjs (voir §2 Règle 1), qui communique avec l'endpoint Chorus MCP par HTTP en utilisant votre clé API, indépendamment du préfixe mcp__chorus__.


§1. Détection — lire la valeur précomputée, sinon exécuter les trois vérifications en ligne

Différence dsh : le plugin Claude Code précompute CHORUS_OPENSPEC_ACTIVE dans un hook SessionStart. dsh n'a pas de hook SessionStart, mais le bundle chorus-dsh précompute la même valeur au chargement du plugin et l'exporte comme variable d'environnement CHORUS_OPENSPEC_ACTIVE — il exécute les trois vérifications ci-dessous contre le répertoire de travail du processus, avant la porte daemon-origin, de sorte que les sessions interactives et celles déclenchées par daemon en héritent. Lisez cette valeur quand elle est définie ; ne la recalculez en ligne que si elle est absente (ancien bundle, ou la variable a été explicitement effacée).

CHORUS_OPENSPEC_ACTIVE vaut 1 uniquement quand les trois vérifications sont vraies :

  1. CHORUS_OPENSPEC_MODE n'est pas défini à off (le refus explicite gagne).
  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 sur PATH.

Les deux signaux (2) et (3) sont obligatoires car le chemin authoring OpenSpec a besoin du répertoire de travail et de la CLI — en avoir un sans l'autre rend le workflow inutilisable. Si le signal (2) est vrai mais pas (3), affichez un conseil à l'utilisateur — « Repo OpenSpec détecté — installer avec : npm i -g @fission-ai/openspec » — plutôt que de choisir silencieusement le mode libre.

Bloc de détection (exécuter ceci)

# Préférer la valeur que le bundle chorus-dsh a précomputée au chargement ;
# revenir aux trois vérifications quand elle n'est pas définie. PROJECT_DIR est votre racine
# (dsh n'exporte pas CLAUDE_PROJECT_DIR — défaut à $PWD).
PROJECT_DIR="${PWD}"
if [ -n "${CHORUS_OPENSPEC_ACTIVE:-}" ]; then
  : # déjà précomputé par le bundle chorus-dsh — l'utiliser tel quel
elif [ "${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   # envisager d'afficher le conseil d'installation à l'utilisateur
else
  CHORUS_OPENSPEC_ACTIVE=1
fi
echo "CHORUS_OPENSPEC_ACTIVE=$CHORUS_OPENSPEC_ACTIVE"

Bifurquez sur le résultat :

  • CHORUS_OPENSPEC_ACTIVE=1 → suivre §3 (authoring OpenSpec).
  • CHORUS_OPENSPEC_ACTIVE=0 → revenir au chemin libre du skill appelant. Ne pas scaffolder openspec/changes/. Ne pas ajouter la ligne slug à la description de la proposition.

Exécutez ceci chaque fois que proposal / develop / yolo référence ce skill : lisez le CHORUS_OPENSPEC_ACTIVE précomputé s'il est présent, sinon recalculez les trois vérifications.


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

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

Règle 1 — Miroir via le wrapper, jamais retaper le contenu du document à partir de la sortie de l'agent

Les appels miroir document/brouillon (chorus_pm_add_document_draft, chorus_pm_update_document_draft, chorus_pm_update_document) DOIVENT passer par :

"$CHORUS_MCP_CALL" <tool_name> "$PAYLOAD"

avec $PAYLOAD construit avec json_encode_file (défini en §3.4). Appeler ces outils directement depuis le harness MCP de l'agent avec un champ content dactylographié à la main est une violation de protocole pour le mode OpenSpec et ne passera pas la revue. Raisons :

  1. Coût en tokens. Retaper un corps markdown multi-milliers de lignes via le LLM brûle des tokens d'entrée + sortie pour chaque brouillon. json_encode_file (§3.4) encode le fichier avec JSON.stringify de Node — le contenu n'entre jamais dans le contexte du LLM. Un miroir de proposition typique à 3 docs via le script coûte à peu près zéro token de contenu ; via MCP direct, cela coûte régulièrement 20k+.
  2. Égalité des octets. JSON.stringify des octets UTF-8 du fichier est un encodeur fidèle aux octets : les antislash, guillemets, sauts de ligne, contenu de barrière de code, caractères de largeur nulle survivent tous. La réémission LLM a un taux d'échec non nul sur le markdown long — l'alignement du tableau dérive, les échappements de barrière sont « corrigés », les long URLs s'enroulent. La garantie d'égalité des octets (modulo un \n final) ne tient que sur le chemin du wrapper.
  3. Source de vérité unique. Avec le wrapper, le fichier local openspec/changes/<slug>/*.md fait autorité et Chorus est un miroir. Avec la retape de l'agent, l'autorité se divise entre le fichier local et ce que le LLM a émis — un futur diff ne peut pas dire lequel est correct.

Disponibilité du wrapper sur dsh. Le bundle npm publie le chemin du wrapper dans CHORUS_MCP_CALL au chargement du plugin. Validez-le avant authoring :

if [ -z "${CHORUS_MCP_CALL:-}" ] || [ ! -x "$CHORUS_MCP_CALL" ]; then
  echo "ERROR: OpenSpec mirroring requires the package-local CHORUS_MCP_CALL wrapper; reload the Chorus dsh bundle." >&2
  exit 1
fi

Le wrapper lit CHORUS_URL et CHORUS_API_KEY depuis l'environnement du processus dsh. S'il est absent, arrêtez visiblement. Ne le reproduisez pas ad hoc et ne retapez pas le contenu du document via le modèle.

Règle 2 — Arrêtez en cas d'erreur 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 quitte 0 sur HTTP 401 (échec d'auth) avec un corps vide, donc une vérification à un seul signal manque silencieusement l'échec d'exécution le plus courant. Voir §6 pour la définition de la fonction d'aide.


§3. Authoring en mode OpenSpec

3.1 Choisir un slug

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

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

Enregistrez-le pour les étapes suivantes :

SLUG="add-export-csv"

3.2 Scaffolder le dossier de changement

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

Ceci crée openspec/changes/$SLUG/ avec README.md et .openspec.yaml. Ensuite, écrivez à la main :

Fichier local Objectif Miroir comme Document.type
proposal.md Pourquoi + Quels changements + Capacités + Impact prd
design.md Architecture, contrats, risques tech_design
specs/<capability>/spec.md Spec delta (## ADDED Requirements + Scénarios) spec (un brouillon par capacité)
tasks.md Liste des tâches OpenSpec (non mirrorisée — les brouillons de tâches Chorus font autorité)

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

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

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

## ADDED Requirements

Ajouter une nouvelle Requirement à la spec à long terme.

## ADDED Requirements

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

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

## MODIFIED Requirements

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

## MODIFIED Requirements

### Requirement: <nom existant>
<texte de 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 la spec post-archivage ait, même ceux qui étaient déjà présents et inchangés.

## REMOVED Requirements

Supprimer une Requirement de la spec à long terme. Le bloc sous l'en-tête est juste le(s) nom(s) de requirement que vous supprimez — aucun scénario nécessaire.

## REMOVED Requirements

### Requirement: <nom existant>

## RENAMED Requirements

Renommer le titre d'une Requirement. Le corps et les scénarios sont conservés tels quels dans la spec à long terme ; utilisez MODIFIED à la place si vous devez changer autre chose que le titre.

## RENAMED Requirements

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

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

  • Les Scenarios 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, ils ne corrigent pas.
  • Utilisez SHALL / MUST pour les requirements normatifs ; évitez should / may.
  • La fusion dans openspec/specs/<capability>/spec.md se fait à l'heure de openspec archive (§3.9), pas à l'heure de la proposition. Pendant que la proposition est en cours, Chorus ne voit que le fichier delta comme un seul Document spec — il n'y a pas d'état mi-fusionné pour que le skill raisonne.

Optionnel :

openspec validate "$SLUG"

3.4 Aide : json_encode_file

Définir une fois en haut de la session authoring. Il encode le fichier dans une chaîne JSON fidèle aux octets avec JSON.stringify de Node — Node est garanti présent sous dsh (c'est le runtime du harness), donc aucun jq n'est requis.

json_encode_file() {
  # Fidèle aux octets : JSON.stringify du contenu UTF-8 du fichier — guillemets,
  # antislash, sauts de ligne, contenu de barrière de code, et caractères de contrôle survivent tous.
  # Pas de jq, pas de curl.
  node -e 'const fs=require("fs");process.stdout.write(JSON.stringify(fs.readFileSync(process.argv[1],"utf8")))' "$1"
}

Aller-retour : le backend Chorus ajoute un unique \n au contenu du brouillon à l'écriture, donc le content serveur est byte-equal modulo une newline finale. Les relecteurs comparant le fichier local vs le serveur doivent ignorer cet unique octet.

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

Utiliser l'outil 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 correcte). La description doit porter exactement une ligne :

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

Cette ligne est searchable par grep-machine par les exécutions futures de ce skill et par le déclencheur d'archive §3.9.

3.6 Miroir chaque brouillon de document via le wrapper

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

Résolvez CHORUS_MCP_CALL comme spécifié en Règle 1. Définissez la fonction d'aide halt-on-error de §6 une fois en haut, puis exécutez un appel par fichier :

# Brouillon 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_MCP_CALL" 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 pas mirroiser tasks.md — les brouillons de tâches Chorus (créés via l'outil MCP chorus_pm_add_task_draft, pas de wrapper nécessaire) font autorité pour les tâches.

Pourquoi le parsing utilise printf '%s' "$RESULT" | grep et pas echo "$RESULT" | jq : echo interprète les séquences d'antislash à l'intérieur du JSON capturé, transformant \n imbriqué en une vraie newline. jq avorte 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 motif s'applique à tout parsing de résultat-wrapper dans ce skill.

3.7 Éditer un brouillon après le premier miroir

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_MCP_CALL" chorus_pm_update_document_draft "$PAYLOAD")
RC=$?
chorus_check_response "chorus_pm_update_document_draft" "$RC" "$RESULT"

3.8 Éditer un Document après l'approbation de la proposition

Une fois la proposition approuvée, les brouillons se matérialisent en Documents avec leurs propres UUID. Pour garder openspec/changes/$SLUG/ et le Document Chorus en sync, mirroir les éditions 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_MCP_CALL" chorus_pm_update_document "$PAYLOAD")
RC=$?
chorus_check_response "chorus_pm_update_document" "$RC" "$RESULT"

Pour re-dériver $SPEC_DOCUMENT_UUID d'un shell frais, cherchez-le via chorus_get_documents pour le projet de la proposition et appariez par title + type. Re-dérivez $SLUG en grepant la description de la proposition pour ^OpenSpec change slug:.

3.9 Archive après la dernière tâche vérifiée

Différence dsh : le plugin Claude Code a un hook PostToolUse (bin/on-post-verify-task.sh) qui s'exécute après chorus_admin_verify_task et injecte un rappel openspec archive <slug>. dsh 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 juste vérifiée était la DERNIÈRE tâche de son idea en mode OpenSpec (chaque Task sur chaque Proposal approuvée de cette idea est maintenant done/closed, et la description de la proposition porte une ligne OpenSpec change slug: <slug>). Si oui, exécutez le flux d'archive ci-dessous. Sinon, ne faites rien.

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

  1. Exécutez 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

    Ceci déplace openspec/changes/$SLUG/ sous openspec/changes/archive/<date>-<slug>/ et émet/met à jour openspec/specs/<capability>/spec.md pour chaque capacité. (Exécutez openspec archive --help contre votre version installée pour confirmer la série de flags actuelle — les flags peuvent dériver entre les versions.)

  2. Mirroir 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 côté serveur projectUuid + type ; filtrez par title côté client. Un appel chorus_pm_update_document par capacité.

  3. Arrêtez en cas d'erreur de openspec archive ou chorus_pm_update_document. Imprimez stderr verbatim, postez un commentaire sur la proposition enregistrant l'échec (chorus_add_comment avec targetType: "proposal", targetUuid: <proposalUuid>), puis stoppez. Pas de retry. Correspond à §6 « aucune erreur silencieuse ». (Commentez la proposition, pas l'idea : l'échec est dans l'archivage des specs dérivées de la proposition, et les propositions peuvent être inputType: "document" sans idea attachée.)

  4. Confirmez le succès. Listez les fichiers openspec/specs/<capability>/spec.md et vérifiez qu'ils font un aller-retour byte-equal (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 idea, OU la description de la proposition ne porte pas de ligne OpenSpec change slug: <slug>, OU le shell local n'a pas la CLI openspec, ne faites rien — pas d'archive. Le comportement libre existant est préservé.


§4. Authoring de fallback (pas d'openspec)

Quand la détection §1 met l'agent en mode fallback (CHORUS_OPENSPEC_ACTIVE=0), ce skill est un no-op. Revenez au chemin libre 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 la proposition.
  • Les brouillons de documents sont créés via les appels MCP chorus_pm_add_document_draft directs avec content en ligne — comme avant l'existence de ce skill.
  • La Règle 1 (mirror wrapper-only) ne s'applique pas — il n'y a pas de source de vérité de fichier local.
  • Le flux d'archive §3.9 ne fait rien (pas de slug → pas d'archive).

§5. Tableau de référence de mappage des types de documents

Fichier local Document.type Chorus Mirroisé ?
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 brouillon par capacité)
openspec/changes/<slug>/tasks.md (non mappé) non — les brouillons de tâches Chorus font autorité

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


§6. Visibilité des échecs — la fonction d'aide chorus_check_response

Le wrapper Node quitte non-zéro sur les échecs de transport, HTTP 4xx/5xx, et les corps JSON-RPC error (ex. un 401 d'une mauvaise CHORUS_API_KEY quitte 2, une erreur au niveau tool quitte 4). Vérifiez quand même les trois signaux ci-dessous en défense en profondeur : un simple RC=$? va bien pour les cas courants mais cette fonction d'aide attrape aussi un corps 200 bien formé qui porte néanmoins un objet "error", et un corps inopinément vide.

Définissez cette fonction d'aide une fois en haut de la session authoring et utilisez-la 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
    # Pas de jq requis : une correspondance de sous-chaîne sur une clé "error" suffit pour les
    # outils mirror, dont les corps de succès ne portent aucun champ top-level "error".
    printf '%s' "$_body" | grep -qE '"error"[[:space:]]*:' && _has_error=1
  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-motifs — ne pas :

  • Réduire à || true.
  • Rediriger stderr vers /dev/null.
  • Enterrer l'appel wrapper à l'intérieur d'un pipeline (masque $?).
  • Sauter la capture de $RESULT dans une variable ; la fonction d'aide a besoin du corps.
  • Utiliser seulement if [ "$RC" -ne 0 ]; then ... — cela manque le chemin d'erreur HTTP.

Forme minimale du site d'appel :

RESULT=$("$CHORUS_MCP_CALL" <tool_name> "$PAYLOAD")
RC=$?
chorus_check_response "<tool_name>" "$RC" "$RESULT"
# ...si nous arrivons ici, l'appel a réussi ; parser RESULT et continuer.

C'est une politique à l'échelle du projet : aucune erreur silencieuse.


§7. Liste de contrôle de référence rapide

Quand invoqué d'un skill de stage (proposal / develop / yolo) :

  1. Exécutez la détection §1 des trois vérifications vous-même (pas de hook SessionStart sur dsh). Calculez CHORUS_OPENSPEC_ACTIVE à partir de : CHORUS_OPENSPEC_MODE != off + répertoire openspec/ présent + CLI openspec sur PATH.
  2. Si CHORUS_OPENSPEC_ACTIVE=0 → revenir au chemin libre du caller (§4).
  3. Sinon : a. Choisir $SLUG (§3.1). b. openspec new change "$SLUG" (§3.2). c. Authorer proposal.md, design.md, specs/<capability>/spec.md (§3.2–§3.3). Mélangez les blocs ADDED / MODIFIED / REMOVED / RENAMED selon les besoins ; rappelez-vous que MODIFIED écrase la Requirement complète. d. Optionnel : openspec validate "$SLUG". e. chorus_pm_create_proposal (MCP direct) avec la ligne OpenSpec change slug: $SLUG dans la description (§3.5). f. Valider le chemin exécutable dans $CHORUS_MCP_CALL ; définir json_encode_file et chorus_check_response. g. Pour chaque ligne de §5 avec « oui » — miroir via "$CHORUS_MCP_CALL" chorus_pm_add_document_draft (§3.6). Enregistrer chaque $DRAFT_UUID. h. En cas d'échec de chorus_check_response — halte, exposer l'erreur, NE PAS continuer.
  4. Éditions avant approbation → §3.7. Éditions après approbation → §3.8.
  5. Dernière tâche vérifiée → détecter le déclencheur vous-même (pas de hook) → exécuter le flux d'archive §3.9.

Skills similaires