Rédaction sensible à OpenSpec (plugin Hermes)
Cette compétence est une sous-procédure partagée invoquée par les compétences du stage Chorus (proposal, develop, yolo) quand le mode spec résolu est un OpenSpec exploitable — rédaction pilotée par spec via la CLI OpenSpec :
- S'active quand le mode spec résolu est un OpenSpec exploitable (voir §1) :
CHORUS_SPEC_MODE=openspecou non défini, etCHORUS_OPENSPEC_MODEdifférent deoff, un répertoireopenspec/existe à la racine du projet, et la CLIopenspecest surPATH. - Sinon, la compétence appelante suit le mode résolu — spec-lite (par défaut quand OpenSpec n'est pas disponible) ou libre-forme (
CHORUS_SPEC_MODE=off).
Voir aussi —
spec-lite(le repli léger) : OpenSpec (cette compétence) reste le mode par défaut quand il est exploitable. Quand OpenSpec est absent ou désactivé — ouCHORUS_SPEC_MODE=lite— le mode bascule à spec-lite : un.chorus/specs/<slug>/spec.mdlocal durable (jamais synchronisé) + dossiers datés par changement<slug>/<YYYY-MM-DD>-<change-slug>/de docs typés Chorus reflétés 1:1 dans Chorus via le même transport--arg-file. Voirskill_view("chorus:spec-lite").
Quand vous arrivez à un point dans proposal / develop / yolo où cette compétence est référencée, lisez le mode résolu depuis la section ## Spec Mode (voir §1) et branchez dessus. Ne re-dérivez pas la détection — le vérification de session pre_llm_call du plugin Hermes Chorus l'a déjà résolu une fois pour cette session.
Spécificités Hermes : le transport de miroir de document est la CLI
chorus(chorus mcp call … --arg-file content=<file>, §2 Rule 1), exécutée via l'outilterminal. Quandchorusn'est pas surPATH, le repli est l'outil MCP Chorus natif (mcp__chorus__chorus_pm_add_document_draft,mcp__chorus__chorus_pm_update_document_draft,mcp__chorus__chorus_pm_update_document) aveccontentlu depuis le fichier viaread_file. Il n'y a pas d'enveloppe shell groupée sur Hermes. Les snippets d'aide ci-dessous sont en Bash ; exécutez les blocs multi-lignes sousbash -lc(ou enregistrez-les comme script.shavec un shebang Bash) si le shellterminaln'est pas Bash.
§1. Mode spec — déjà résolu au démarrage de la session
Le hook pre_llm_call du plugin Chorus Hermes résout le mode spec au premier tour de chaque session (et à nouveau après /reset ou compression de contexte) via son spec_mode.py — l'unique source de vérité, un portage Python du résolveur canonique avec les mêmes règles — et écrit une section ## Spec Mode dans le contexte injecté indiquant CHORUS_SPEC_MODE=<lite|openspec|off> ; quand le mode résolu est un OpenSpec exploitable, il porte aussi une ligne CHORUS_OPENSPEC_ACTIVE=1. Cette ligne est présente seulement quand CHORUS_SPEC_MODE est openspec ou non défini, et les trois conditions suivantes tiennent :
- OpenSpec n'est pas désactivé (
CHORUS_OPENSPEC_MODEdifférent deoff,CHORUS_ENABLE_OPENSPECdifférent defalse). - La racine du projet contient un répertoire
openspec/(c.-à-d. quelqu'un a exécutéopenspec initici). - La CLI
openspecest surPATH.
Les deux signaux (2) et (3) sont obligatoires car le chemin de rédaction OpenSpec nécessite le répertoire de travail et la CLI. Si le signal (2) tient mais (3) non, la section ## Spec Mode porte un indice d'installation (npm i -g @fission-ai/openspec) ; transmettez-le si demandé.
Comment lire la valeur
Vous devriez déjà voir quelque chose comme ceci dans votre contexte (cherchez la section ## Spec Mode près du haut de la conversation) :
## Spec Mode
CHORUS_SPEC_MODE=openspec (default — openspec/ directory + openspec CLI both present)
CHORUS_OPENSPEC_ACTIVE=1 (openspec/ directory + openspec CLI both present)
ou (résolu à lite / off — pas de ligne CHORUS_OPENSPEC_ACTIVE=1) :
## Spec Mode
CHORUS_SPEC_MODE=lite (default — OpenSpec not usable: no openspec/ directory at /path/to/repo/openspec)
Branchez :
- Ligne
CHORUS_OPENSPEC_ACTIVE=1présente → suivez §3 (rédaction OpenSpec). - Pas de ligne
CHORUS_OPENSPEC_ACTIVE=1→ cette compétence est un no-op ; retournez à l'appelant, qui suit leCHORUS_SPEC_MODErésolu (spec-lite ou libre-forme). Ne pas créer l'infrastructureopenspec/changes/. Ne pas ajouter la ligne slug à la description de la proposition.
Si la section ## Spec Mode affiche un CHORUS_SPEC_MODE=openspec explicite qui ne peut pas être honoré (OpenSpec désactivé ou non installé — il porte une raison de conflit de config / non installé et pas de ligne CHORUS_OPENSPEC_ACTIVE=1), l'appelant doit arrêter et le signaler — ne pas basculer silencieusement.
Repli manuel
Le mode est résolu par le plugin Chorus Hermes (spec_mode.py) au démarrage de la session — il n'y a pas de résolveur pour vous à sourcer, et vous ne devez pas implémenter la règle manuellement (une vérification OpenSpec-only implémentée à la main ignore CHORUS_SPEC_MODE et les cas lite/off). Si la section ## Spec Mode est véritablement absente :
- Vous êtes un enfant
delegate_task(les enfants démarrent avec un contexte isolé) : le parent doit vous transmettre les lignes## Spec Moderésolues verbatim dans votrecontext. S'il ne l'a pas fait, signalez au parent que le mode spec est absent et arrêtez, plutôt que de deviner. - Sinon (p. ex. la vérification/connexion a échoué) : définissez
CHORUS_SPEC_MODEexplicitement (lite|openspec|off) et démarrez une session nouvelle (/reset, ou redémarrezhermes gatewaypour les sessions Chorus-woken) pour que le plugin le re-résolve — plutôt que de deviner.
§2. ⛔ Deux règles non-négociables
Les deux sont appliquées au moment de la révision. Les deux ont causé des incidents dans les versions antérieures.
Rule 1 — Remplir content depuis le fichier (CLI préférée, repli d'outil natif) ; ne jamais retaper le contenu du document à partir de la sortie de l'agent
Les appels de miroir de document/brouillon (chorus_pm_add_document_draft, chorus_pm_update_document_draft, chorus_pm_update_document) DOIVENT remplir le champ content depuis les octets du fichier local, jamais d'un corps retapé à la main. Appeler ces outils directement depuis les outils MCP natifs avec un champ content retapé à la main est une violation de protocole pour le mode OpenSpec et échouera à la révision. Utilisez le transport disponible, préféré en premier :
- Primaire — la CLI
chorus:chorus mcp call <tool_name> '<json-without-content>' --arg-file content=<file>.--arg-file content=<path>lit les octets bruts du fichier et les injecte comme chaîne JSONcontent, octet-exact, donc aucun helper d'encodage n'est nécessaire. Voir §3.6. Nécessite chorus >= 0.17.0 (la sous-commandechorus mcpa été ajoutée à ce moment ; une CLI plus ancienne échoue avec "unknown command") ; sur n'importe quelle version ou échec de commande inconnue, mettez à jour avecnpm install -g @chorus-aidlc/chorus. - Le transport primaire s'exécute via
terminal. La CLIchorusest un binaire Node surPATH; exécutez-la avec l'outilterminalHermes pour que les octets du fichier ne passent jamais par le contexte du modèle. - Repli — l'outil MCP Chorus natif, quand
chorusn'est pas surPATH: lisez le fichier avecread_fileet appelezmcp__chorus__chorus_pm_add_document_draft/mcp__chorus__chorus_pm_update_document_draft/mcp__chorus__chorus_pm_update_documentaveccontentdéfini au texte du fichier exactement tel que lu — pas d'édits, pas de reflow, pas de « correctifs », et aucun préfixe de numéro de ligne que le lecteur ajoute pour l'affichage. C'est un chemin dégradé : le corps passe par le contexte du modèle (coût en tokens) et perd la garantie octet-exact, donc après chaque appel vérifiez-le aller-retour (§3.9 étape 4) et préférez installer la CLI (npm install -g @chorus-aidlc/chorus) avant un miroir multi-document. Il n'y a pas d'enveloppe shell groupée sur Hermes.
Nouveau à la CLI
chorus? Voirskill_view("chorus:chorus-cli")pour l'installation, la configuration d'agents (chorus agents add|remove|list), les variables d'environnement de connexion, et les bases dechorus mcp.
Identité d'action — quel agent l'appel agit-il.
chorus mcp callrésout l'agent depuis, dans l'ordre :CHORUS_AGENT_PROFILE(un nom ou UUID) →CHORUS_URL+CHORUS_API_KEYdans l'environnement → l'agent unique configuré dans~/.chorus/daemon.json. Sur Hermes, les identifiants viennent de l'environnement (CHORUS_URL,CHORUS_API_KEY) ou~/.hermes/.env; exportez-les dans la commandeterminalsi le shell ne les voit pas. Si un appel de miroir échoue avecMultiple agents … specify --agent(plusieurs agents configurés et pas de profil/identifiants dans l'env), passez votre propre identité explicitement :chorus mcp call <tool> … --agent <your-agentUuid>— votre UUID est dans votre résultatchorus_checkin, etchorus agentsliste chaque nom/UUID configuré.
Raisons (c'est pourquoi le chemin CLI est préféré, et pourquoi le repli doit copier le texte du fichier verbatim) :
- Coût en tokens. Retaper un corps markdown multi-millier de lignes via l'LLM brûle les tokens d'entrée + sortie pour chaque brouillon. Le
--arg-filede la CLI diffuse les octets du fichier dans la chaîne JSON — le contenu n'entre jamais dans le contexte de l'LLM. Un miroir de proposition type à 3 documents coûte environ zéro token de contenu de cette manière ; via MCP direct avec un corps retype cela coûte régulièrement 20k+. - Égalité octet-par-octet. Le
--arg-filede la CLI est un encodeur octet-fidèle : les antislashs, guillemets, sauts de ligne, contenu de clôture de code, caractères de largeur zéro — tout survit. La ré-émission d'LLM a un taux d'échec non-zéro sur du markdown long — l'alignement des tables dérive, les échappements de clôture sont « corrigés », les longues URLs se replient. La garantie d'égalité octet-exact ne tient que sur le chemin--arg-file, jamais sur la ré-émission d'LLM — c'est pourquoi le repli d'outil natif a besoin d'une vérification aller-retour. - Source de vérité unique. Avec un miroir rempli depuis le fichier, le
openspec/changes/<slug>/*.mdlocal est autoritaire et Chorus est un miroir. Avec l'agent retype, l'autorité se scinde entre le fichier local et ce que l'LLM a émis — un diff futur ne peut pas dire lequel est correct.
Rule 2 — Arrêter en erreur via chorus_check_response
Chaque chorus mcp call doit vérifier trois signaux : code de sortie, "error": dans le corps, corps vide. Un RC=$? nu est défense en profondeur seulement — une défaillance d'auth ou une erreur de niveau outil peut apparaître dans le corps plutôt que le code de sortie, donc une vérification d'un seul signal peut silencieusement manquer l'échec runtime le plus courant. Voir §6 pour la définition du helper. Sur le repli d'outil natif, traitez tout résultat d'erreur (ou un résultat vide/inattendu) du l'outil comme un arrêt.
§3. Rédaction 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, pasaddExportCsvouadd_export_csv), - dérivé du titre Idea source,
- unique dans
openspec/changes/.
Enregistrez-le pour les étapes ultérieures :
SLUG="add-export-csv"
3.2 Créer l'infrastructure du dossier de changement
openspec new change "$SLUG" --description "<résumé d'une ligne de l'idée>"
Cela crée openspec/changes/$SLUG/ avec README.md et .openspec.yaml. Puis rédigez à la main :
| Fichier local | Objectif | Reflété comme Document.type |
|---|---|---|
proposal.md |
Pourquoi + 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 reflété — les brouillons de tâches Chorus sont source de vérité) |
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 énumère un ou plusieurs en-têtes de bloc — ## ADDED Requirements, ## MODIFIED Requirements, ## REMOVED Requirements, ## RENAMED Requirements — et en son sein, des entrées ### Requirement:. Mélangez librement dans le même fichier ; incluez seulement les blocs dont vous avez vraiment besoin.
## ADDED Requirements
Annexez un Requirement tout neuf au spec long-terme.
## ADDED Requirements
### Requirement: <name>
<texte du requirement — utilisez SHALL / MUST pour le comportement normatif>
#### Scenario: <name>
- **WHEN** <condition>
- **THEN** <résultat attendu>
## MODIFIED Requirements
Remplacement de bloc entier, pas fusion. Tout ce que vous écrivez ici remplace complètement le Requirement de même nom existant dans le spec long-terme — titre, description, et tous les scénarios. Ne l'écrire partiellement supprime le reste.
## MODIFIED Requirements
### Requirement: <existing name>
<texte du requirement complet mis à jour>
#### Scenario: <name>
- **WHEN** <condition>
- **THEN** <résultat attendu>
#### Scenario: <other name>
- **WHEN** <condition>
- **THEN** <résultat attendu>
Incluez toujours chaque scénario que vous voulez que le spec post-archivage ait, même ceux déjà présents et inchangés.
## REMOVED Requirements
Supprimez un Requirement du spec long-terme. Le bloc sous l'en-tête est juste le nom(les noms) du requirement que vous supprimez — pas de scénarios nécessaires.
## REMOVED Requirements
### Requirement: <existing name>
## RENAMED Requirements
Renommez le titre d'un Requirement. Le corps et les scénarios sont préservés tels quels dans le spec long-terme ; utilisez MODIFIED à la place si vous devez changer autre chose que le titre.
## RENAMED Requirements
### Requirement: <old name> -> <new name>
Règles de formatage critiques (vérifiées) :
- Les Scénarios DOIVENT utiliser exactement 4 hashtags (
#### Scenario:). 3 hashtags ou une liste à puces échouent silencieusement la validation. - Chaque
### Requirement:sousADDEDouMODIFIEDDOIT avoir au moins un#### Scenario:. - Les blocs
MODIFIEDDOIVENT inclure le contenu complet mis à jour — ils écrasent, ne patchent pas. - Utilisez
SHALL/MUSTpour les requirements normatifs ; évitezshould/may. - La fusion dans
openspec/specs/<capability>/spec.mdse fait au moment deopenspec archive(§3.9), pas au moment de la proposition. Tant que la proposition est en vol, Chorus voit seulement le fichier delta en tant qu'un Documentspec— il n'y a pas d'état semi-fusionné pour la compétence de raisonner sur.
Optionnel :
openspec validate "$SLUG"
3.4 Remplir le champ content octet-exact
Le content du document doit être inséré octet-par-octet depuis le fichier local — jamais retype par l'LLM. Deux mécanismes, préféré en premier :
- Primaire —
chorus mcp call … --arg-file content=<path>(§3.6). La CLI lit les octets bruts du fichier et les injecte comme chaîne JSONcontent. C'est octet-fidèle, donc aucun helper d'encodage n'est nécessaire — passez le JSON de base sans champcontentet laissez--arg-filele remplir. - Repli — l'outil MCP natif (§3.6 repli, utilisé seulement quand
chorusn'est pas surPATH).contentest le texteread_filecopié verbatim ; vérifiez le aller-retour ensuite.
La vérification aller-retour est exacte : une différence de saut de ligne à la fin est une dérive réelle et NE DOIT PAS être normalisée ou ignorée.
3.5 Créer le conteneur de proposition avec la ligne de provenance slug
Utilisez l'outil MCP normal chorus_pm_create_proposal (pas de CLI nécessaire pour cet appel unique — la description est courte, la version émise par l'LLM est bien). La description doit porter exactement une ligne :
OpenSpec change slug: <slug>
- sur sa propre ligne (aucun autre texte sur cette ligne),
- préfixe littéral
OpenSpec change slug:(O majuscule, S majuscule, un seul espace après le deux-points), - pas de ponctuation finale,
- la valeur correspond au slug passé à
openspec new change.
Cette ligne est cherchable machine-grep par les futures exécutions de cette compétence et par le déclencheur d'archivage §3.9.
3.6 Reflète chaque brouillon de document (CLI primaire, repli d'outil natif)
Rappel Rule 1 :
contentvient des octets du fichier, jamais d'un corps retapé à la main. L'agent ne doit pas retaper le corps du document.
Définissez une seule fois le helper d'arrêt-en-erreur du §6 en haut. Chemin primaire — la CLI chorus : passez le JSON de base sans champ content et laissez --arg-file content=<file> le remplir octet-exact. Un appel par fichier :
# Brouillon PRD — --arg-file remplit content octet-exact depuis le fichier ; aucun helper d'encodage nécessaire.
RESULT=$(chorus mcp call chorus_pm_add_document_draft \
"{\"proposalUuid\":\"$PROPOSAL_UUID\",\"type\":\"prd\",\"title\":\"PRD: $HUMAN_TITLE\"}" \
--arg-file content="openspec/changes/$SLUG/proposal.md")
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 reflétez pas tasks.md — les brouillons de tâches Chorus (créés via l'outil MCP chorus_pm_add_task_draft, pas de CLI nécessaire) sont la source de vérité pour les tâches.
Pourquoi l'analyse utilise
printf '%s' "$RESULT" | grepau lieu deecho "$RESULT" | jq:echointerprète les séquences d'échappement à l'intérieur du JSON capturé, transformant les\nintégrés en vrai saut de ligne.jqéchoue alors avecInvalid 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 à toute analyse CLI-result dans cette compétence.
Repli — quand la CLI chorus n'est pas sur PATH
Si command -v chorus échoue, reflétez via l'outil MCP Chorus natif à la place. Il n'y a pas d'enveloppe shell ou d'helper d'encodage sur Hermes. Par fichier :
-
read_file("openspec/changes/<slug>/proposal.md")— prenez le texte du fichier exactement tel que stocké (abandonnez tout préfixe de numéro de ligne display-only). -
Appelez l'outil natif avec ce texte comme
content:mcp__chorus__chorus_pm_add_document_draft({ proposalUuid: "<proposalUuid>", type: "prd", title: "PRD: <human title>", content: "<le texte du fichier, verbatim>" }) -
Traitez tout résultat d'erreur (ou vide) comme un arrêt (§6). Enregistrez le
draftUuidretourné.
Répétez pour design.md (tech_design) et chaque specs/<capability>/spec.md (spec). Parce que ce chemin ré-émet le corps, vérifiez-le aller-retour une fois que les brouillons se matérialisent (§3.9 étape 4), et installez la CLI chorus avant le prochain miroir si vous pouvez.
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 split primaire/repli que §3.6, même vérification d'arrêt. Primaire (CLI) :
RESULT=$(chorus mcp call chorus_pm_update_document_draft \
"{\"proposalUuid\":\"$PROPOSAL_UUID\",\"draftUuid\":\"$PRD_DRAFT_UUID\"}" \
--arg-file content="openspec/changes/$SLUG/proposal.md")
RC=$?
chorus_check_response "chorus_pm_update_document_draft" "$RC" "$RESULT"
Repli (pas de chorus sur PATH) — l'outil natif, mêmes règles que le repli §3.6 :
mcp__chorus__chorus_pm_update_document_draft({
proposalUuid: "<proposalUuid>",
draftUuid: "<prdDraftUuid>",
content: "<texte read_file de openspec/changes/<slug>/proposal.md, verbatim>"
})
3.8 Éditer un Document après approbation de la proposition
Une fois la proposition approuvée, les brouillons se matérialisent en Documents avec leurs propres UUIDs. Pour garder openspec/changes/$SLUG/ et le Document Chorus synchronisés, reflétez les édits du fichier via chorus_pm_update_document. Primaire (CLI) :
RESULT=$(chorus mcp call chorus_pm_update_document \
"{\"documentUuid\":\"$SPEC_DOCUMENT_UUID\"}" \
--arg-file content="openspec/changes/$SLUG/specs/<capability>/spec.md")
RC=$?
chorus_check_response "chorus_pm_update_document" "$RC" "$RESULT"
Repli (pas de chorus sur PATH) — l'outil natif, mêmes règles que le repli §3.6 :
mcp__chorus__chorus_pm_update_document({
documentUuid: "<specDocumentUuid>",
content: "<texte read_file de openspec/changes/<slug>/specs/<capability>/spec.md, verbatim>"
})
Pour re-dériver $SPEC_DOCUMENT_UUID depuis un shell nouveau, cherchez-le via chorus_get_documents pour le projet de la proposition et faites correspondre par title + type. Re-dérivez $SLUG en grepant la description de la proposition pour ^OpenSpec change slug:.
3.9 Archiver après vérification de la dernière tâche
Quand la DERNIÈRE tâche d'une idée en mode OpenSpec est admin-vérifiée via chorus_admin_verify_task, le hook transform_tool_result du plugin Hermes Chorus annexe un OpenSpec Archive Trigger (rappel branche A de ses rappels post-vérification) au résultat de l'outil. Il contient la sous-chaîne littérale openspec archive <slug> et les UUIDs du projet/de la proposition, pour que vous puissiez agir sans re-lire le slug.
Le rappel est seulement un rappel — le plugin n'archive rien ; vous (l'agent) effectuez l'archivage :
-
Exécutez l'archivage localement. Utilisez
--yespour le mode non-interactif. Ne passez pas--skip-specs(annule le miroir-retour) ou--no-validate(laisse les deltas malformés corrompre les specs cumulatifs).openspec archive "$SLUG" --yesCela déplace
openspec/changes/$SLUG/sousopenspec/changes/archive/<date>-<slug>/et émet/met à jouropenspec/specs/<capability>/spec.mdpour chaque capacité. (Exécutezopenspec archive --helpcontre votre version installée pour confirmer l'ensemble d'indicateurs actuel — les indicateurs peuvent dériver entre les versions.) -
Reflétez chaque
openspec/specs/<capability>/spec.mdmis à jour vers le Document Chorus post-approbation correspondant (contrat §3.8).chorus_get_documentsne supporte que les filtres côté serveurprojectUuid+type; filtrez par titre côté client. Un appelchorus_pm_update_documentpar capacité. -
Arrêtez en cas d'erreur de
openspec archiveouchorus_pm_update_document. Imprimez stderr verbatim, postez un commentaire sur la proposition enregistrant l'échec (chorus_add_commentavectargetType: "proposal",targetUuid: <proposalUuid>), puis arrêtez. Pas de retry. Correspond à §6 « pas d'erreurs silencieuses. » (Commentez sur la proposition, pas l'idée : l'échec est dans l'archivage des specs dérivées de la proposition, et les propositions peuvent êtreinputType: "document"sans idée attachée.) -
Confirmez le succès. Pour chaque capacité, vérifiez que
openspec/specs/<capability>/spec.mdse aller-retour octet-égal avec son Document Chorus. Le chemin de miroir--arg-filegarantit déjà cela, donc une vérification ponctuelle suffit : récupérez le Document avecchorus mcp call chorus_get_document '{"documentUuid":"<uuid>"}', écrivez soncontentdans un fichier temp, etcmp(ou comparez SHA-256 de) les deux fichiers. Ne utilisez PASjq -rpour extraire le contenu — il annexe un saut de ligne final et fabrique une dérive phantom de 1 octet (un spec qui s'est bien reflété lira 1 octet plus long sur le serveur). Utilisezjq -j(pas de saut de ligne final), et ne fiez jamais les dépendances àjqrécursif,head, substitution de commande, ou normalisation de saut de ligne. En cas de non-correspondance, signalez seulement les comptes d'octets et hashes. (Le plugin Hermes Chorus n'expédie pas de script de vérification aller-retour — la garantie d'égalité octet--arg-filela rend inutile.) Si le miroir s'est déroulé via le repli d'outil natif (pas de CLIchorus), récupérez avecchorus_get_documentet comparez le contenu contre le fichier local aussi étroitement que vous le pouvez ; signalez toute dérive visible et recommandez un re-miroir CLI.
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 proposition ne porte pas de ligne OpenSpec change slug: <slug>, OU le shell local n'a pas de CLI openspec, le plugin n'annexe pas de rappel d'archivage (la branche A reste silencieuse ; les rappels de révision de code et de rapport, branches C et B, sont indépendants). Le comportement libre-forme existant est préservé.
§4. Quand OpenSpec n'est pas le mode résolu
Quand le mode résolu n'est pas un OpenSpec exploitable (pas de ligne CHORUS_OPENSPEC_ACTIVE=1), cette compétence est un no-op — retournez à la compétence appelante, qui suit le CHORUS_SPEC_MODE résolu : spec-lite (le mode par défaut quand OpenSpec n'est pas disponible) ou libre-forme (=off). De ce côté de cette compétence :
- 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. - En spec-lite, l'appelant suit
skill_view("chorus:spec-lite")(durable.chorus/specs/<slug>/spec.md+ dossiers datés per-change reflétés via--arg-file) ; en libre-forme, les brouillons de document sont rédigés via des appels MCP directschorus_pm_add_document_draftaveccontenten ligne — comme avant l'existence de cette compétence. - Rule 1 (miroir rempli depuis fichier) s'applique toujours en spec-lite (reflétez depuis le fichier local) ; en libre-forme il n'y a pas de source de vérité fichier local.
- Le rappel d'archivage §3.9 ne se déclenche jamais (pas de slug → branche A silencieuse) ; spec-lite n'a aucune étape d'archivage du tout.
§5. Cartographie des types de document (tableau de référence)
| Fichier local | Chorus Document.type |
Reflété ? |
|---|---|---|
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âche Chorus sont source de vérité |
prd, tech_design, spec sont des valeurs Document.type valides pré-existantes — aucun changement de schéma requis.
§6. Visibilité des défaillances — le helper chorus_check_response
Ce helper garde chaque chorus mpc call sur le chemin primaire. chorus mcp call sort non-zéro en cas d'erreurs outil/transport, donc RC est généralement fiable — mais une erreur peut aussi arriver comme un objet "error" dans un corps par ailleurs réussi, et un corps vide signifie que l'appel n'a produit rien d'utilisable. Exécutez la vérification trois-signal après chaque appel, en défense en profondeur. (Sur le repli d'outil natif il n'y a pas de résultat shell à vérifier : arrêtez en cas d'erreur ou tout résultat d'outil vide.)
Définissez ce helper une seule fois au sommet de la session de rédaction et utilisez-le après chaque miroir chorus mcp call :
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-motifs — ne pas :
- Réduire à
|| true. - Rediriger stderr vers
/dev/null. - Enfouir le
chorus mcp callà l'intérieur d'un pipeline (masque$?). - Ignorer de capturer
$RESULTdans une variable ; le helper a besoin du corps. - Utiliser seulement
if [ "$RC" -ne 0 ]; then ...— cela rate le chemin HTTP-error.
Forme minimale du site d'appel :
RESULT=$(chorus mcp call <tool_name> '<json-without-content>' --arg-file content=<file>)
RC=$?
chorus_check_response "<tool_name>" "$RC" "$RESULT"
# ...si nous arrivons ici, l'appel a réussi ; parsez RESULT et continuez.
C'est une politique de projet : aucune erreur silencieuse.
§7. Liste de contrôle de référence rapide
Quand invoquée depuis une compétence de stage (proposal / develop / yolo) :
- Lisez la section
## Spec Modeinjectée par le plugin Chorus Hermes au démarrage de la session (§1) — procédez seulement si elle porte la ligneCHORUS_OPENSPEC_ACTIVE=1. Si la section est absente, utilisez le repli du §1 (mode transmis-parent, ou définissezCHORUS_SPEC_MODEexplicitement +/reset). - Pas de ligne
CHORUS_OPENSPEC_ACTIVE=1→ no-op ; retournez à l'appelant selon leCHORUS_SPEC_MODErésolu (spec-lite ou libre-forme) — voir §4. - Sinon :
a. Choisissez
$SLUG(§3.1). b.openspec new change "$SLUG"(§3.2). c. Rédigezproposal.md,design.md,specs/<capability>/spec.md(§3.2–§3.3). Mélangez les blocsADDED/MODIFIED/REMOVED/RENAMEDselon les besoins ; rappelez-vousMODIFIEDécrase le Requirement entier. d. Optionnel :openspec validate "$SLUG". e.chorus_pm_create_proposal(MCP direct) avec la ligneOpenSpec change slug: $SLUGdans la description (§3.5). f. Définissez le helperchorus_check_response. Utilisezchorus mcp call … --arg-file content=<file>viaterminalpour les miroirs (§3.6). Seulement quandchorusn'est pas surPATH, revenez à l'outil MCP natif avec le texteread_fileverbatim (§2 Rule 1). g. Pour chaque ligne du §5 avec « oui » — reflétez viachorus mcp call chorus_pm_add_document_draft … --arg-file content=<file>(§3.6 ; repli = MCP natifmcp__chorus__chorus_pm_add_document_draft). Enregistrez chaque$DRAFT_UUID. h. Sur tout échec dechorus_check_response— arrêtez, signalez l'erreur, ne procédez PAS. - Édits avant approbation → §3.7. Édits après approbation → §3.8.
- Dernière tâche vérifiée → le rappel post-vérification du plugin (branche A) apparaît → exécutez le flux d'archivage §3.9.