openspec-aware

Par chorus-aidlc · chorus

Création de contenu en mode OpenSpec pour les workflows Chorus PM dans Hermes. Valeur par défaut dès que OpenSpec est utilisable ; consomme le `## Spec Mode` résolu (sans nouvelle détection). Génère la structure `openspec/changes/<slug>/` sur le disque, et synchronise les fichiers Markdown vers des brouillons de documents Chorus via `chorus mcp call --arg-file` (l'outil Chorus MCP natif en fallback lorsque la CLI `chorus` est absente). Lecture obligatoire pour les skills proposal, develop et yolo. Lorsque OpenSpec n'est pas le mode résolu, ce skill ne fait rien et l'appelant suit le mode résolu (spec-lite ou free-form).

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

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=openspec ou non défini, et CHORUS_OPENSPEC_MODE différent de off, un répertoire openspec/ existe à la racine du projet, et la CLI openspec est sur PATH.
  • 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é — ou CHORUS_SPEC_MODE=lite — le mode bascule à spec-lite : un .chorus/specs/<slug>/spec.md local 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. Voir skill_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'outil terminal. Quand chorus n'est pas sur PATH, 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) avec content lu depuis le fichier via read_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 sous bash -lc (ou enregistrez-les comme script .sh avec un shebang Bash) si le shell terminal n'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 :

  1. OpenSpec n'est pas désactivé (CHORUS_OPENSPEC_MODE différent de off, CHORUS_ENABLE_OPENSPEC différent de false).
  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 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=1 pré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 le CHORUS_SPEC_MODE résolu (spec-lite ou libre-forme). Ne pas créer l'infrastructure openspec/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 Mode résolues verbatim dans votre context. 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_MODE explicitement (lite | openspec | off) et démarrez une session nouvelle (/reset, ou redémarrez hermes gateway pour 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 JSON content, octet-exact, donc aucun helper d'encodage n'est nécessaire. Voir §3.6. Nécessite chorus >= 0.17.0 (la sous-commande chorus mcp a é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 avec npm install -g @chorus-aidlc/chorus.
  • Le transport primaire s'exécute via terminal. La CLI chorus est un binaire Node sur PATH ; exécutez-la avec l'outil terminal Hermes pour que les octets du fichier ne passent jamais par le contexte du modèle.
  • Repli — l'outil MCP Chorus natif, quand chorus n'est pas sur PATH : lisez le fichier avec read_file et appelez mcp__chorus__chorus_pm_add_document_draft / mcp__chorus__chorus_pm_update_document_draft / mcp__chorus__chorus_pm_update_document avec content dé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 ? Voir skill_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 de chorus mcp.

Identité d'action — quel agent l'appel agit-il. chorus mcp call résout l'agent depuis, dans l'ordre : CHORUS_AGENT_PROFILE (un nom ou UUID) → CHORUS_URL + CHORUS_API_KEY dans 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 commande terminal si le shell ne les voit pas. Si un appel de miroir échoue avec Multiple 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ésultat chorus_checkin, et chorus agents liste 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) :

  1. 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-file de 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+.
  2. Égalité octet-par-octet. Le --arg-file de 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.
  3. Source de vérité unique. Avec un miroir rempli depuis le fichier, le openspec/changes/<slug>/*.md local 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, pas addExportCsv ou add_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: sous ADDED ou MODIFIED DOIT avoir au moins un #### Scenario:.
  • Les blocs MODIFIED DOIVENT inclure le contenu complet mis à jour — ils écrasent, ne patchent pas.
  • Utilisez SHALL / MUST pour les requirements normatifs ; évitez should / may.
  • La fusion dans openspec/specs/<capability>/spec.md se fait au moment de openspec 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 Document spec — 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 JSON content. C'est octet-fidèle, donc aucun helper d'encodage n'est nécessaire — passez le JSON de base sans champ content et laissez --arg-file le remplir.
  • Repli — l'outil MCP natif (§3.6 repli, utilisé seulement quand chorus n'est pas sur PATH). content est le texte read_file copié 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 : content vient 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" | grep au lieu de echo "$RESULT" | jq : echo interprète les séquences d'échappement à l'intérieur du JSON capturé, transformant les \n intégrés en vrai saut de ligne. jq échoue 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 à 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 :

  1. 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).

  2. 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>"
    })
  3. Traitez tout résultat d'erreur (ou vide) comme un arrêt (§6). Enregistrez le draftUuid retourné.

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 :

  1. Exécutez l'archivage localement. Utilisez --yes pour 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" --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é. (Exécutez openspec archive --help contre votre version installée pour confirmer l'ensemble d'indicateurs actuel — les indicateurs peuvent dériver entre les versions.)

  2. Reflétez 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 titre 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 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 être inputType: "document" sans idée attachée.)

  4. Confirmez le succès. Pour chaque capacité, vérifiez que openspec/specs/<capability>/spec.md se aller-retour octet-égal avec son Document Chorus. Le chemin de miroir --arg-file garantit déjà cela, donc une vérification ponctuelle suffit : récupérez le Document avec chorus mcp call chorus_get_document '{"documentUuid":"<uuid>"}', écrivez son content dans un fichier temp, et cmp (ou comparez SHA-256 de) les deux fichiers. Ne utilisez PAS jq -r pour 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). Utilisez jq -j (pas de saut de ligne final), et ne fiez jamais les dépendances à jq ré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-file la rend inutile.) Si le miroir s'est déroulé via le repli d'outil natif (pas de CLI chorus), récupérez avec chorus_get_document et 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 directs chorus_pm_add_document_draft avec content en 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 $RESULT dans 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) :

  1. Lisez la section ## Spec Mode injectée par le plugin Chorus Hermes au démarrage de la session (§1) — procédez seulement si elle porte la ligne CHORUS_OPENSPEC_ACTIVE=1. Si la section est absente, utilisez le repli du §1 (mode transmis-parent, ou définissez CHORUS_SPEC_MODE explicitement + /reset).
  2. Pas de ligne CHORUS_OPENSPEC_ACTIVE=1 → no-op ; retournez à l'appelant selon le CHORUS_SPEC_MODE résolu (spec-lite ou libre-forme) — voir §4.
  3. Sinon : a. Choisissez $SLUG (§3.1). b. openspec new change "$SLUG" (§3.2). c. Rédigez 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 MODIFIED écrase le Requirement entier. 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. Définissez le helper chorus_check_response. Utilisez chorus mcp call … --arg-file content=<file> via terminal pour les miroirs (§3.6). Seulement quand chorus n'est pas sur PATH, revenez à l'outil MCP natif avec le texte read_file verbatim (§2 Rule 1). g. Pour chaque ligne du §5 avec « oui » — reflétez via chorus mcp call chorus_pm_add_document_draft … --arg-file content=<file> (§3.6 ; repli = MCP natif mcp__chorus__chorus_pm_add_document_draft). Enregistrez chaque $DRAFT_UUID. h. Sur tout échec de chorus_check_response — arrêtez, signalez l'erreur, ne procédez PAS.
  4. Édits avant approbation → §3.7. Édits après approbation → §3.8.
  5. 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.

Skills similaires