Aller au contenu

MonoSpecs qu'est-ce que c'est : pourquoi c'est une évolution et une extension supplémentaire d'OpenSpec

Modifier cette page
HagiCode for Windows Microsoft Store artwork
HagiCode for Windows is now on Microsoft Store
HagiCode for Windows is officially live on Microsoft Store. Windows users can install it directly from the storefront and stay on the store-managed update path. Open the listing and take a look.
Open Microsoft Store

MonoSpecs qu’est-ce que c’est : pourquoi c’est une évolution et une extension supplémentaire d’OpenSpec

Quand un système de produits s’étend à 40+ dépôts Git indépendants, où doivent se trouver les “spécifications” ? Cet article discute des deux étapes parcourues par HagiCode dans la gouvernance multi-dépôts : d’abord remonter OpenSpec au dépôt principal, puis développer sur cette base MonoSpecs, une solution de gestion multi-dépôts. Ce n’est pas grand-chose en fait, juste quelques obstacles rencontrés que je veux noter.

Contexte

Ceux qui ont travaillé sur des produits un peu plus grands ont probablement vécu cette expérience - au début, le code est dans un seul dépôt, bien organisé, tout va bien ; plus tard, frontend, backend, desktop, site de documentation, site officiel, outils de construction deviennent chacun des dépôts indépendants, le nombre de dépôts augmente rapidement, comme des mauvaises herbes impossibles à arrêter. Plus tard, quand vous voulez écrire un “document de spécification” pour une fonctionnalité qui traverse les dépôts, vous ne savez soudain plus où l’écrire, comment dire, c’est un peu comme l’argent de poche quand on était enfant, comment ça a disparu soudainement.

Notre propre HagiCode est justement un système de produits composé de 40+ dépôts Git indépendants. Au début, nous avons directement mis le répertoire openspec/ d’OpenSpec dans le sous-dépôt backend hagicode-core, en pensant que puisque le backend est le cœur, le mettre ici serait le plus stable. Résultat : les dépôts se sont divisés de plus en plus, et cette solution a révélé une série de problèmes qui donnent mal à la tête. Après tout, le monde du code ne devient jamais stable juste parce que vous “pensez que c’est stable”.

Premier point de douleur : les specs sont piégées dans un seul sous-dépôt. Si une fonctionnalité affecte à la fois le frontend web et le backend hagicode-core, je dois écrire la proposition dans hagicode-core, puis aller dans d’autres sous-dépôts pour exécuter les modifications de code. À quel dépôt la proposition devrait-elle appartenir est devenu en soi une controverse.

Deuxième point de douleur : les sous-dépôts ne sont pas purs. Chaque sous-dépôt porte son propre openspec/, les documents de spécification et le code du produit sont mélangés. Quelqu’un clone votre dépôt frontend et ramène une pile de documents de proposition backend, complètement perdu.

Troisième point de douleur : les AI Agents ont du mal à comprendre les relations entre les dépôts. Les sous-dépôts sont indépendants les uns des autres, il n’y a pas de “liste” lisible par machine pour dire à l’IA : de quels dépôts ce produit est composé, de quoi chacun est responsable, lequel est éditable, lequel est une référence en lecture seule.

Quatrième point de douleur : coût élevé d’édition inter-dépôts. Pour modifier une spec, il faut d’abord cd dans le sous-module correspondant, les chemins sautent de partout, la charge mentale de collaboration est énorme.

C’est dans ce contexte que nous avons d’abord fait une “OpenSpec Monorepo Migration”, en remontant les specs des sous-dépôts vers le répertoire racine du monorepo. Puis, sur cette base, nous avons développé MonoSpecs, cette solution de gestion multi-dépôts. Comprendre la relation progressive entre ces deux étapes est la clé pour comprendre “pourquoi on dit que monospec est une évolution et une extension supplémentaire d’openspec”.

À propos de HagiCode

La solution partagée dans cet article provient de notre expérience pratique dans le projet HagiCode. HagiCode est un projet d’assistant de code IA, avec un grand nombre de dépôts et une collaboration inter-langues fréquente, cette complexité structurelle nous oblige à faire un travail solide sur les “spécifications” et la “gouvernance des dépôts”. La solution MonoSpecs a été affinée petit à petit dans cette pratique multi-dépôts, il n’y a pas de coup de génie, juste quelques pas de plus.

OpenSpec résout “comment écrire et faire évoluer les spécifications”

Pour clarifier la relation entre les deux, il faut d’abord regarder ce que chacun fait.

OpenSpec est essentiellement un workflow de gestion des changements piloté par des specs. Ses produits principaux ressemblent à ceci :

openspec/
├── specs/ # Spécifications de capacités actuellement en vigueur (une spec.md par capacité)
├── changes/ # Propositions en cours
│ └── archive/ # Propositions historiques archivées
└── project.md

Il répond à la question : un changement doit passer par le cycle de vie de proposition (proposal), conception (design), tâches (tasks), archivage (archive), et lors de l’archivage, fusionner les deltas dans les specs. Ce mécanisme lui-même est indépendant de “combien de dépôts, où ils sont, qui les gère”, il se soucie seulement de l’organisation des fichiers spec.

Nous avons fait une proposition de migration pour remonter les 82+ fichiers spec dispersés dans hagicode-core/openspec/ vers le openspec/ à la racine du monorepo, rendant toutes les spec visibles en un seul endroit et sous contrôle de version unifié.

Mais cette migration était fondamentalement juste “déménager les fichiers spec”, et n’a pas répondu à une question plus fondamentale : de quels sous-dépôts ce monorepo est-il composé ? Quelles sont les relations entre ces sous-dépôts ? C’est ce que MonoSpecs doit combler.

MonoSpecs résout “comment gérer les multi-dépôts eux-mêmes”

Le cœur de MonoSpecs est un fichier manifeste lisible par machine : .hagicode/monospecs.yaml. Il fait quatre choses qu’OpenSpec ne touche pas du tout.

Première : déclarer la liste des sous-dépôts. Le path, l’url, le displayName, l’icon, les tags de chaque dépôt, et s’il doit être plié dans “More”, tout est écrit dans un YAML, clair d’un coup d’œil.

Deuxième : piloter le script de clone. scripts/clone-repos.mjs lit directement ce YAML, fait des git clone en lot, ne hardcode plus la liste des dépôts. Ajouter un dépôt demande juste une ligne dans le YAML, le script n’a besoin d’aucune modification.

Troisième : fournir le contexte de structure de projet à l’AI/IDE. Avec AGENTS.md, l’AI Agent peut voir d’un coup d’œil quel dépôt est éditable, lequel est reference-only, quelle est la stack technique.

Quatrième : ancrer les produits d’OpenSpec au dépôt principal. Les specs ne sont plus dispersées dans les sous-dépôts, mais sont unifiées dans le openspec/ à la racine du dépôt principal, les sous-dépôts restent donc purs.

Deux niveaux de signification, ne les confondez pas

Dans le guide officiel de MonoSpecs, un point très facile à confondre est clairement indiqué : MonoSpecs a en fait deux niveaux de signification.

Un niveau est le niveau du système de configuration, qui fait référence au fichier de configuration .hagicode/monospecs.yaml lui-même, ainsi qu’à ses mécanismes de chargement, validation et cache associés.

L’autre niveau est le niveau du type de dépôt, qui fait référence à un mode d’organisation de dépôts “dépôt principal + plusieurs sous-dépôts + specs centralisés”. Quand nous disons qu’un projet “est un projet MonoSpecs”, cela signifie qu’il adopte cette structure.

Ces deux niveaux superposés forment le MonoSpecs complet. Beaucoup de gens au premier contact ne voient que le niveau du fichier YAML, pensant que MonoSpecs est juste une liste de configuration, mais sa valeur est davantage dans le deuxième niveau - un paradigme clair de collaboration multi-dépôts. En fait, les belles choses ne sont souvent pas au premier coup d’œil, il faut les regarder plusieurs fois.

Pourquoi dire “évolution et extension”

En mettant les deux côte à côte pour comparaison, la relation devient claire :

DimensionOpenSpecMonoSpecs
FocusContenu et cycle de vie des fichiers specStructure organisationnelle et liste des dépôts
Produit principalopenspec/specs/*/spec.md.hagicode/monospecs.yaml
Dépend de l’autreNe dépend pas de MonoSpecsDépend d’OpenSpec, réutilise son openspec/ pour la gestion des changements
Problèmes résolusComment écrire et faire évoluer les spécificationsComment déclarer les multi-dépôts, comment cloner, comment l’IA comprend
PortéePeut être utilisé dans n’importe quel dépôtConçu spécifiquement pour les structures multi-dépôts “un principal plusieurs sous”

Pour le dire simplement, MonoSpecs ne remplace pas OpenSpec, mais ajoute une couche de “gouvernance des dépôts” au-dessus. Utiliser monospecs.yaml pour décrire la topologie des dépôts, utiliser openspec/ centralisé pour découpler les specs des sous-dépôts, utiliser commit_when_archive pour que l’archivage soit automatiquement commité dans le dépôt principal.

Si on fait une analogie : OpenSpec fournit la “syntaxe de changement”, MonoSpecs fournit la “sémantique multi-dépôts”. Le premier est la précondition du second, le second est l’extension du premier. Tous les chemins mènent à Rome, mais cette fois, le chemin est un peu plus long qu’imaginé.

Comment mettre en œuvre : quatre étapes

Première étape : établir le dépôt principal et le fichier de configuration

Placez le fichier de configuration à la racine du monorepo et déclarez tous les sous-dépôts. Avec notre propre projet comme exemple, la structure est à peu près comme ceci :

.hagicode/monospecs.yaml
version: "1.0"
commit_when_archive: true
repositories:
- path: "repos/web"
url: "https://github.com/HagiCode-org/web.git"
displayName: "Frontend"
tags: [frontend, react, pcode-client]
- path: "repos/hagicode-core"
url: "https://github.com/newbe36524/pcode"
displayName: "Backend"
tags: [backend, dotnet, orleans]
- path: "repos/docs"
url: "https://github.com/HagiCode-org/docs.git"
displayName: "Documentation"
tags: [docs, astro, starlight]
ui:
collapseToMore: true # Plier dans "More" dans l'UI

Il y a quelques champs à particulièrement attention :

  • path est le chemin local relatif à la racine du dépôt principal, et aussi la clé unique de chaque enregistrement.
  • url est l’adresse Git distante, le script de clone s’y appuie pour tirer le code.
  • displayName / icon / tags n’affectent que l’affichage UI et le contexte IA, n’affectent pas le comportement de clone.
  • commit_when_archive: true fait que les propositions OpenSpec sont automatiquement commitées dans le dépôt principal lors de l’archivage.

Deuxième étape : remonter OpenSpec à la racine du dépôt principal

Comparaison avant et après migration :

Avant migration (specs piégées dans sous-dépôt) Après migration (specs centralisées dans dépôt principal)
hagicode-core/ . (racine dépôt principal)
└── openspec/ ├── .hagicode/monospecs.yaml
└── specs/ (82+ specs) ├── openspec/
│ ├── specs/ (gestion centralisée)
│ └── changes/
└── repos/
├── hagicode-core/ (pur, sans openspec)
├── web/
└── docs/

Les sous-dépôts ne portent plus openspec/, le dépôt principal devient la seule source de vérité des specs. Cette étape semble simple, mais apporte des bénéfices très réels - n’importe quel ingénieur debout à la racine du dépôt principal peut voir toutes les spécifications de tout le système de produits.

Troisième étape : faire lire la configuration au script de clone plutôt que hardcoder

La logique principale de scripts/clone-repos.mjs est de lire le YAML et cloner un par un :

const CONFIG_PATH = path.join(__dirname, '..', '.hagicode', 'monospecs.yaml');
// Parser le tableau repositories
// Pour chaque entrée exécuter git clone <url> <path>
// Si le répertoire cible existe déjà, sauter ou git pull

Quand on ajoute un dépôt, il suffit d’ajouter une ligne dans le YAML, sans toucher au script. Ce petit changement économise d’innombrables discussions sur “oublier de synchroniser la liste des dépôts”. Après tout, qui veut faire du travail répétitif ?

Quatrième étape : le backend fournit une couche de service MonoSpecs unifiée

Sans extraire une couche d’abstraction, la logique de parsing de configuration se disperse facilement dans GitAppService, ProjectAppService et divers coins. HagiCode a extrait IMonoSpecsService dans le module ClaudeHelper, exposant un ensemble clair de capacités :

public interface IMonoSpecsService
{
Task<MonoSpecsConfigDto> GetConfigAsync(string projectPath);
Task<List<RepositoryInfoDto>> GetSubRepositoriesAsync(string projectPath);
Task<MonoSpecsDataDto> GetMonoSpecsDataAsync(string projectPath);
Task<MonoSpecsManagementDto> GetManagementDocumentAsync(string projectPath);
Task<MonoSpecsManagementDto> InitializeManagementDocumentAsync(string projectPath);
Task ValidateManagementDocumentAsync(string projectPath, UpdateMonoSpecsManagementRequestDto request);
Task SaveManagementDocumentAsync(string projectPath, UpdateMonoSpecsManagementRequestDto request);
}

Ce service est responsable du chargement, de la validation, du cache de la configuration, et fournit la capacité “d’initialiser le modèle minimal” - générer en un clic monospecs.yaml, repos/, openspec/changes/archive/, openspec/specs/ pour un projet vide, et compléter automatiquement .gitignore. Le cache, c’est comme la mémoire, une fois mémorisé, la prochaine fois on n’a plus besoin de réfléchir.

Quelques pièges dans la pratique

Initialiser un nouveau projet MonoSpecs

Après avoir appelé InitializeManagementDocumentAsync, cette structure apparaîtra sur le disque :

my-project/
├── .gitignore # Nouvelle règle d'ignore repos/ (idempotent, pas d'ajout en double)
├── .hagicode/
│ └── monospecs.yaml # Modèle minimal : version / commit_when_archive / repositories: []
├── openspec/
│ ├── changes/archive/
│ └── specs/
└── repos/ # Répertoire vide, attendant le clone

Il y a quelques frontières à attentionner, toutes extraites de la spec :

  • Idempotent : les répertoires repos/, openspec/ existants seront préservés, pas d’erreur.
  • Pas de surcharge : si monospecs.yaml existe déjà et peut être analysé normalement, l’initialisation ne le touchera pas, seulement compléter les règles .gitignore manquantes et les répertoires openspec.
  • Refus des configurations sales : monospecs.yaml existant mais impossible à analyser sera directement refusé, retournant une information d’erreur diagnostique, absolument pas surchargé.
  • Pas de scan automatique : l’initialisation ne scannera pas automatiquement les répertoires du disque en entrées de dépôt, repositories est vide par défaut, doit être rempli manuellement ou via l’UI.

Piège de migration de l’emplacement du fichier de configuration

Historiquement, monospecs.yaml était placé à la racine du projet, puis forcé à migrer vers .hagicode/monospecs.yaml. Ce point est très clair dans la spec :

monospecs.yaml à la racine n’est plus détecté, ni utilisé comme repli de compatibilité. Le script de clone ne reconnaît que .hagicode/monospecs.yaml.

Donc lors de la mise à niveau des anciens projets, il faut manuellement exécuter mv monospecs.yaml .hagicode/monospecs.yaml, aucun chemin de compatibilité silencieux. À première vue un peu impitoyable, mais réfléchissez bien, c’est pour éliminer complètement l’ambiguïté “les deux positions peuvent toutes deux prendre effet” - une fois cette ambiguïté existe, le dépannage peut rendre fou, après tout qui veut chercher des réponses entre deux fichiers ?

Validation de sauvegarde : n’écrivez pas de configuration invalide

Avant d’écrire via SaveManagementDocumentAsync, le service fera une validation au niveau des champs. Quelques scénarios typiques de refus :

  • Deux entrées de dépôt avec path en double → refusé, retourne le champ en conflit.
  • N’importe quelle entrée sans path → refusé, retourne l’erreur champ obligatoire.
  • url non vide mais pas une URL absolue valide → refusé.

Seulement après validation passée sera sérialisé en YAML et écrit sur disque, en invalidant le cache de configuration du chemin de projet, garantissant que la prochaine lecture obtient le contenu le plus récent. Cette étape semble triviale, mais peut éviter d’innombrables tickets “pourquoi mes changements de configuration ne prennent pas effet”, après tout trop de ces tickets, personne ne peut supporter.

Mode workspace vs mode repositories manuel

Le fichier de configuration supporte deux façons de dériver la liste des dépôts.

L’une est le mode repositories manuel, lister directement chaque dépôt dans le YAML, le document de gestion marqué comme éditable.

L’autre est le mode workspace, déclarer un fichier .code-workspace, qui dérive la liste des dépôts. Dans ce mode le document de gestion est marqué comme lecture seule, interdisant de réécrire directement le tableau de dépôts, seulement modifier les champs de niveau supérieur supportés.

Notre propre HagiCode Mono a commenté le mode workspace, adoptant le mode manuel. La raison est simple : le mode manuel peut contrôler finement l’icon et les tags de chaque dépôt, l’affichage UI est plus contrôlable. Comment dire, les choses qu’on peut maîtriser, le cœur est toujours plus tranquille.

Suggestions pratiques pour les AI Agents

Maintenant que la programmation IA est de plus en plus répandue, la solution MonoSpecs a en fait une valeur implicite : elle fournit à l’IA une carte de projet structurée.

Dans la collaboration multi-dépôts, AGENTS.md et monospecs.yaml sont deux contextes clés pour l’IA. Le workflow recommandé est le suivant :

  1. D’abord lire monospecs.yaml pour obtenir la topologie des dépôts, clarifier qui est éditable, qui est reference-only.
  2. Puis lire “Active Edit Scope” du AGENTS.md racine, confirmer la plage de modifications actuellement autorisée.
  3. Les changements inter-dépôts écrivent des propositions uniformément dans openspec/changes/ à la racine du dépôt principal, ne démarrez pas d’openspec séparé dans chaque sous-dépôt.

Cette convention permet à l’IA de comprendre stablement la division “le dépôt principal gère les specs, les sous-dépôts gèrent le code”, sans écrire par erreur des specs dans les sous-dépôts - cette mauvaise opération nous a causé plusieurs problèmes. Ce n’est pas la faute de l’IA, après tout les sous-dépôts et le dépôt principal se ressemblent tellement, qui peut les distinguer d’un coup d’œil ?

Résumé

En un mot : OpenSpec définit “comment écrire les changements”, MonoSpecs définit “comment organiser les dépôts”.

Le premier est la base syntaxique du second, le second étend le premier du contexte de dépôt unique au contexte multi-dépôts, et converge en une seule fois la topologie des dépôts, le flux de clone, le contexte IA, et l’appartenance des specs avec une liste YAML. C’est la véritable signification de “monospec est une évolution et une extension supplémentaire d’openspec” - pas un remplacement, mais une couche de sémantique multi-dépôts au-dessus.

Si vous faites aussi des produits multi-dépôts d’échelle similaire, pensez si ces deux niveaux sont bien en place. Même si les spécifications sont écrites magnifiquement, sans une gouvernance claire des dépôts pour les soutenir, ça finira par devenir un chaos…

Références

Résumé

Autour de “MonoSpecs qu’est-ce que c’est : pourquoi c’est une évolution et une extension supplémentaire d’OpenSpec”, une façon plus sûre de progresser est d’abord faire fonctionner progressivement les configurations clés, les frontières de dépendances et les chemins de mise en œuvre, puis compléter les détails d’optimisation.

Quand les objectifs, les étapes et les points d’acceptation sont clairs, ce type de solution peut généralement entrer plus fluidement dans la livraison réelle.

开始使用 HagiCode

一次安装,几分钟上手

HagiCode for Windows 在 Microsoft Store 免费提供。打开商店即可安装并保持更新;也可以先对比各版本与定价,再决定从哪个渠道开始。