← Retour au site Chargement…

note this well

notabene

Documentation complète

Généré le 1 août 2026

Guide

Qu'est-ce que notabene ?

Qu’est-ce que notabene ?

nota bene — la marque de marge qui signifie « à bien noter ».

notabene rend le Markdown/MDX de votre repo comme un site navigable avec des commentaires de revue directement sur la page, et fournit le protocole de revue humain↔agent qui transforme ces commentaires en éditions. Le viewer est le support — le protocole est le produit.

démo notabene : commenter un passage, l'agent applique l'édition, validation du vrai diff

Le problème qu’il résout

Corriger ou rédiger de la doc avec un agent IA oblige à mettre chaque changement en mots : citer le passage, nommer la section — « revoir la formulation de la section 3 » — puis espérer que l’agent retrouve l’endroit exact dans la source. Passé deux ou trois changements, c’est un pavé d’instructions dans un seul champ de chat. L’instruction réelle a toujours été plus simple : ce passage — le changer comme ça.

notabene en fait l’interface. Sélectionnez le texte exact sur la page rendue — ou un diagramme ou une image en entier — et laissez un commentaire à cet endroit. Le commentaire ancré est l’instruction : situé, sans ambiguïté, rien à citer. L’agent lit les commentaires, édite la source fidèlement, marque chaque commentaire résolu et journalise ce qui a changé & pourquoi.

La boucle, en 30 secondes

  1. npx notabene dev → ouvrir le site, sélectionner du texte → laisser un commentaire (ou commenter une page, un diagramme ou une image en entier). Fils, résolution, mise en attente, une vue globale /comments.
  2. Demander à l’agent : « traite les commentaires de la doc ».
  3. L’agent lit .notabene/, édite les docs fidèlement, marque chaque commentaire résolu et ajoute une entrée de journal (quoi / pourquoi / quels commentaires).
  4. Relire la trace sur /journal — ou valider chaque diff vous-même en mode approve.

Tout est dans votre git

Commentaires et journal sont des fichiers JSON sous .notabene/ — pas de SaaS, pas de base de données, pas de compte. Ils voyagent avec le repo, diffent dans les PR, et sont lisibles par n’importe quel agent : le protocole de revue est file-I/O-first (ni serveur, ni port, ni MCP requis).

Où aller ensuite

Plutôt les tableaux exhaustifs ? Direction l’espace Référence : la CLI, chaque clé de config, le frontmatter, le contrat du store et le modèle de sécurité.

Installation

Installation

notabene, c’est deux briques installables : le plugin Claude Code (mise en place clé en main + la boucle de revue) et le renderer (un paquet npm + une CLI). Elles s’installent indépendamment — le plugin n’a pas besoin du paquet npm : il récupère le renderer tout seul.

Le plugin Claude Code — mise en place + revue

Dans Claude Code :

/plugin marketplace add z29k/notabene
/plugin install notabene@z29k

C’est toute l’installation — aucun npm install requis. Le plugin récupère et exécute lui-même une version épinglée du renderer via npx (le premier lancement la télécharge, ~30 s) ; rien n’est scaffoldé dans votre repo, qui n’a même pas besoin d’un package.json. Le seul prérequis est Node (voir prérequis plus bas).

Puis dites simplement « configure notabene » (repo vierge) ou « traite les commentaires de la doc » (déjà configuré) — la bonne skill se déclenche d’elle-même.

Vous préférez une installation manuelle ? Copiez packages/claude-plugin/skills/notabene/ dans le .claude/skills/ de votre projet. Vous utilisez un tout autre agent ? Le plugin ne vous sert à rien : notabene init écrit le même protocole dans <store>/protocol.md et le référence depuis AGENTS.md (voir l’utiliser depuis n’importe quel agent).

Le renderer — paquet npm (sans Claude)

Pour piloter la CLI vous-même — à la main, en CI, ou depuis un autre outil — installez le paquet npm :

npm install -D @z29k/notabene   # or: pnpm add -D @z29k/notabene · bun add -d @z29k/notabene
npx notabene init               # writes notabene.config.mjs + creates the .notabene store
npx notabene dev                # → http://localhost:3009

Le paquet npm est scopé (@z29k/notabene) ; la commande CLI qu’il installe est simplement notabene, donc npx notabene … fonctionne tel quel.

init est la seule chose qui touche votre repo — il écrit notabene.config.mjs et crée le store .notabene/ (init --detect préremplit roots[] à partir des dossiers de doc qu’il trouve). Le renderer lui-même s’exécute depuis le paquet : rien n’est scaffoldé ni copié dans votre repo, et la mise à jour se résume à npm update.

La surface de commandes complète (build, pdf, status, migrate, comments, journal…) est dans la référence CLI.

Installer les deux ne pose aucun problème : le plugin exécute toujours sa propre version épinglée du renderer (alignée sur la version du plugin), indépendante de celle de votre package.json — les deux ne se marchent jamais dessus.

Prérequis

Les deux voies partagent les mêmes prérequis :

  • Node ≥ 22.12 et npx dans votre PATH (les deux sont fournis avec Node).
  • N’importe quel repo avec des fichiers Markdown ou MDX — voir MDX et CommonMark pour la façon dont les deux formats sont gérés.

Ensuite : votre première revue.

Votre première revue

Votre première revue

Vous avez installé le renderer et lancé npx notabene dev. Voici la boucle complète, une fois, de bout en bout.

1 · Commenter la page rendue

Ouvrez http://localhost:3009, naviguez vers n’importe quelle page et sélectionnez un passage — une barre d’action apparaît ; laissez votre commentaire là, directement. Vous pouvez aussi :

  • commenter une page entière (la zone de commentaire en bas de chaque page) ;
  • commenter un diagramme ou une image en entier — survolez-le et utilisez le 💬 de la barre d’outils (le ⤢ juste à côté ouvre une lightbox pan/zoom) ;
  • répondre dans les fils, placer un commentaire en mise en attente (⏸ — l’agent le sautera), et tout voir, toutes pages confondues, sur /comments.

Sur téléphone ou tablette, la même boucle fonctionne d’abord au tactile : la nav se replie dans un tiroir, les commentaires deviennent des bottom sheets, et vous pouvez sélectionner du texte et commenter au pouce.

2 · Confier les commentaires à votre agent

Dites-le à votre agent — avec le plugin Claude Code, c’est simplement :

traite les commentaires de la doc

L’agent lit le store .notabene/ directement (aucun serveur requis), localise chaque passage commenté dans le fichier source, applique le retour fidèlement, marque le commentaire résolu, et ajoute une entrée de journal reliant ce qui a changé à pourquoi. Il vérifie ensuite : le build du renderer s’exécute toujours, plus les checks que vous listez dans verify[]. Il ne commite jamais sans demander.

3 · Relire la trace

  • /journal — chaque passe, avec quoi/pourquoi/quels commentaires, par page.
  • Chaque commentaire résolu pointe vers son entrée de journal.

Vous voulez valider chaque édition vous-même avant qu’elle compte comme résolue — avec le vrai diff git ? C’est le mode approve : voir la boucle de revue.

Travailler à plusieurs relecteurs

Les commentaires portent une identité (nom + email optionnel), définie par navigateur via la pastille 👤 de l’en-tête — les fils s’attribuent donc par personne, pas par machine. Le store, c’est un fichier JSON par commentaire, donc les branches parallèles fusionnent sans conflit.

Configuration

Configuration

notabene.config.mjs à la racine de votre repo est le seul câblage. Les chemins sont relatifs au repo. notabene init scaffolde un template commenté ; notabene init --detect préremplit roots[] à partir des dossiers de doc qu’il trouve. Chaque clé est optionnelle — cette page montre celles que vous toucherez vraiment ; la référence de config les liste toutes.

// notabene.config.mjs
export default {
  siteName: "My Project",
  tagline: "docs",
  locale: "en",

  // Format d'entrée : "commonmark" (tolérant, zéro MDX) ou "mdx" (.mdx strict + .md tolérant).
  format: "commonmark",

  // Espaces de doc. `key` = slug d'URL + espace du store ; `path` = dossier relatif au repo.
  roots: [
    { key: "docs", label: "Docs", path: "docs", exclude: [".notabene/**"] },
    { key: "adr", label: "Decisions", path: "docs/adr", description: "Architecture decision records" },
  ],

  store: "docs/.notabene",   // commentaires + journal (committez ce dossier)
  review: "auto",            // "approve" = vous validez chaque édition avec un diff
  verify: [],                // vos propres checks post-édition (le build du renderer s'exécute toujours)
};

Espaces (roots[])

Chaque entrée devient un espace : sa propre section dans la sidebar, sa propre carte sur la page d’accueil, son propre préfixe dans les ids de commentaires. Un root imbriqué (comme docs/adr ci-dessus) l’emporte sur son parent pour les pages qu’il contient — le chemin le plus spécifique fait loi. label et description acceptent une map par locale quand l’i18n est activée.

MDX et CommonMark/GFM

Le renderer choisit le processeur selon l’extension du fichier :

  • .md → CommonMark/GFM, tolérant. <email@x>, Promise<T>, {var}, le HTML brut et les tableaux GFM se rendent tous sans crash.
  • .mdx → MDX strict (JSX/expressions) — composants importables, mais les </{ hors blocs de code doivent être échappés.

format: "mdx" (le défaut de la config) active les deux, mélangeables dans un même repo. format: "commonmark" (ce que init scaffolde) supprime entièrement la dépendance MDX — le point de départ sûr et le plus tolérant pour un repo en Markdown pur.

Branding

Pointez l’en-tête, l’onglet du navigateur et les cartes sociales vers vos propres assets — des fichiers relatifs au repo, servis par le renderer (rien à copier nulle part) :

branding: {
  logo: "assets/logo.svg",           // image de la topbar, à côté du nom du site
  logoDark: "assets/logo-dark.svg",  // variante sombre optionnelle (sinon logo partout)
  favicon: "assets/favicon.svg",     // .svg / .ico / .png — non défini → une marque par défaut intégrée
  socialImage: "assets/og.png",      // og:image / twitter:image des builds PUBLICS
},

socialImage requiert publish.site — les crawlers exigent une URL absolue. Le favicon couvre aussi les vues print/PDF.

Liens de navigation

Une fois dans une page, plus rien ne ramène au repo, au produit ou aux releases. nav ajoute ces liens sortants à trois endroits — une seule forme d’entrée partout :

nav: {
  // Topbar, groupe de droite. Répliqué automatiquement dans le tiroir mobile.
  header: [
    { label: "GitHub", href: "https://github.com/vous/repo", icon: "github", iconOnly: true },
    { label: { en: "Product", fr: "Produit" }, href: "https://example.com" },
  ],
  // Un bloc titré sous l'arbre des espaces (le tiroir mobile l'affiche aussi).
  sidebar: {
    title: { en: "Resources", fr: "Ressources" },
    links: [
      { label: "Releases", href: "https://github.com/vous/repo/releases", icon: "star" },
      { label: "npm", href: "https://www.npmjs.com/package/votre-pkg", icon: "npm" },
    ],
  },
  // Le pied de page du site. Rien de configuré → aucun élément de pied de page.
  footer: {
    links: [{ label: "Licence", href: "/reference/licence" }],
    text: { en: "© 2026 vous — MIT", fr: "© 2026 vous — MIT" },
    poweredBy: false,
  },
},

liens de navigation notabene : l'icône du repo dans la topbar, un bloc Ressources sous l'arbre de la sidebar, un pied de page, et un lien de pied de page qui ramène à l'accueil

ChampSignification
labelObligatoire. Une chaîne, ou une map { <locale>: string } comme roots[].label. Sert aussi de nom accessible quand iconOnly
hrefObligatoire. Une URL https:///http:///mailto: (ouverte dans un nouvel onglet, rel="noopener"), ou un chemin du site /… (votre publish.base est appliqué pour vous)
iconAu choix : github, gitlab, npm, discord, slack, x, mastodon, rss, mail, book, home, star, download, external. Monochrome — l’icône suit la couleur du lien, donc votre thème
iconOnlyTopbar uniquement : n’affiche que l’icône (le label devient son aria-label/infobulle). Ignoré ailleurs
publishfalse garde le lien hors des builds publics — même idée que roots[].publish
  • Tout est validé au chargement de la config : une clé inconnue, une icône inconnue, un href en double ou une URL javascript: lèvent une erreur immédiatement plutôt que d’expédier un lien cassé — ou piégé — dans un site publié.
  • Ces liens relèvent de l’identité, pas de l’outillage de revue : contrairement à Commentaires/Revue/Journal, ils apparaissent en dev et dans les builds publics. Ils n’apparaissent jamais dans les vues print/PDF et restent hors de l’index de recherche.
  • Gardez trois ou quatre entrées en topbar — iconOnly existe précisément parce que cette rangée est chargée. Les listes longues vont dans le bloc sidebar ou le pied de page.

Page d’accueil personnalisée

Par défaut, la page d’atterrissage (/) affiche le nom du site et une carte par espace. Pointez home vers un fichier Markdown pour rendre votre propre accueil au-dessus de ces cartes — le classique est une intro façon README écrite pour le site, avec des liens relatifs qui deviennent des routes :

home: "docs/home.md",
  • Pipeline complet : Mermaid, coloration du code, et liens inter-docs réécrits vers les routes du site — liez directement dans vos espaces ([install](./guide/install.md)).
  • À garder de préférence hors de vos espaces (un doc dédié) : dans un espace, il se rendrait aussi comme une page normale de cet espace.
  • Avec l’i18n, passez une map par locale : home: { en: "docs/home.md", fr: "docs/home.fr.md" } — la page d’atterrissage de chaque locale (/, /fr) rend son propre fichier.
  • La page d’accueil de ce site, c’est exactement ça — voir docs/home.md.

Pied de page : lien d’édition & dernière mise à jour

Deux touches sans configuration sous chaque page :

  • Mis à jour le — la date d’auteur git de la page (un seul git log streamé par build ; une date lastUpdated en frontmatter la remplace ; silencieusement absente hors d’un repo git). Les builds publics l’émettent aussi en article:modified_time.
  • Modifier cette page — posez editPattern et chaque page pointe vers sa source :
editPattern: "https://github.com/vous/repo/edit/main/{path}",

Labels & ordre de la sidebar

Par défaut, l’entrée d’une page dans la sidebar est son nom de fichier humanisé, et les pages sœurs se trient alphabétiquement. Surchargez l’un ou l’autre par page via le frontmatter — aucun préfixe numérique dans les noms de fichiers requis :

---
title: Internal network map      # <title> de la page + fil d'Ariane (surcharge le H1)
sidebar:
  label: Network map             # texte de la sidebar (sinon title, sinon nom de fichier)
  order: 9                       # position parmi les pages sœurs (croissant)
---
  • order trie en croissant ; les entrées qui n’en ont pas continuent de se trier alphabétiquement, après celles qui sont ordonnées. Groupes et pages partagent un même ordre.
  • Un dossier est nommé et positionné par sa page d’atterrissage — <folder>/index.md (ou readme.md) — dont le frontmatter sidebar s’applique à tout le groupe ; cette page apparaît comme une entrée Aperçu localisée (renommable via sidebar.indexLabel).
  • Ces labels se propagent aux fils d’Ariane et à l’export PDF.

La surface complète du frontmatter (y compris description et publish pour les sites publics) est dans la référence du frontmatter.

Personnaliser le rendu

Personnaliser le rendu

notabene s’exécute depuis le paquet — vous ne forkez jamais son UI. Tout ce qui suit passe par la config à la place :

  1. Branding — logo, favicon, image sociale.
  2. Tokens — surchargez les custom properties --nb-* (ci-dessous).
  3. Votre propre feuille de style — un fichier CSS chargé après les styles du renderer.
  4. Polices et images — un dossier de votre repo, servi pour cette feuille de style.
  5. Code et diagrammes — un thème Shiki pour les blocs de code ; Mermaid suit les tokens.

Les liens sortants (topbar, sidebar, pied de page) n’en font pas partie : ce sont des données du dépôt, pas de l’apparence — voir liens de navigation.

theme: {
  // Surcharges rapides, aucun fichier requis. Une valeur simple s'applique aux DEUX schémas ;
  // une paire light-dark() personnalise chacun : clair à gauche, sombre à droite.
  tokens: { accent: "light-dark(#7c3aed, #b79bff)", radius: "4px" },
  css: "docs/notabene-theme.css",                 // ou/et une feuille de style complète
},

Les deux ciblent le même contrat ; un nom de token mal orthographié lève une erreur au démarrage (jamais un no-op silencieux). Les styles du renderer vivent dans des cascade layers CSS, donc votre CSS hors layer gagne toujours — pas de guerre de spécificité, pas de loterie d’ordre.

Tout suit cette palette — le chrome, les blocs de code et les diagrammes — et le sélecteur de schéma de l’en-tête la bascule en direct, sans rebuild :

théming notabene : un clic sur le sélecteur de schéma bascule la palette — le chrome, le bloc de code et le diagramme Mermaid suivent, sans rebuild

Diagrammes

Les diagrammes Mermaid suivent les tokens d’emblée : les nœuds sont remplis en accent-soft et bordés d’accent, les libellés utilisent la couleur du texte, les arêtes text-soft — et ils se re-rendent au changement de schéma, donc un diagramme en mode sombre est un vrai diagramme sombre, pas une image inversée. Rien à configurer.

Si un diagramme rend mieux avec la palette de Mermaid, désactivez :

theme: { mermaid: false },   // retour aux thèmes default/dark intégrés de Mermaid

Coloration syntaxique (theme.code)

Les blocs de code sont colorés au build : leurs couleurs sont donc figées — d’où le décalage habituel, un thème clair avec des blocs de code sombres. Nommez un thème Shiki et cela change :

theme: {
  code: "github-light",                            // même thème dans les deux schémas
  // ou un par schéma :
  code: { light: "github-light", dark: "vesper" },
},
  • Avec code, les deux palettes voyagent en variables CSS et le sélecteur de schéma recolore le code instantanément — sans rebuild, sans clignotement.
  • Le thème de code prend alors aussi la main sur le fond du bloc (les tokens d’un thème clair sur la dalle sombre par défaut seraient illisibles). --nb-code-bg reste le fond pour quiconque ne définit pas theme.code.
  • Le PDF/print force le schéma clair : c’est donc votre thème de code clair qui atterrit sur le papier.
  • Nom de thème inconnu → erreur au démarrage, comme pour chaque réglage de thème.

Polices et images (theme.assets)

Une feuille de style a généralement besoin de fichiers : une police web, un fond, une texture. Pointez theme.assets vers un dossier de votre repo : il est servi au chemin fixe /_nb/assets/… — en dev, dans les builds et dans les artefacts publics, pour qu’un site publié reste autonome (pas de CDN) :

theme: { css: "docs/theme/site.css", assets: "docs/theme/assets" },
/* docs/theme/site.css */
@font-face {
  font-family: "Inter";
  src: url("./assets/fonts/Inter.woff2") format("woff2");   /* ← relatif, toujours */
}
:root { --nb-sans: "Inter", system-ui, sans-serif; }
  • Écrivez les url() en relatif, jamais en absolu depuis la racine. Les URL se résolvent contre la feuille servie (/_nb/theme.css), pas contre votre fichier source — donc ./assets/… est la forme correcte, et elle absorbe gratuitement un sous-chemin base (site projet GitHub Pages), là où /_nb/… casserait.
  • Déclarez un dossier dédié, pas docs/ : tout ce qui y est servable est émis, référencé ou non.
  • Seuls des assets sont servis — polices (woff2, woff, ttf, otf), images (svg, png, jpg, webp, avif, gif, ico) et css. Tout le reste (.md, .env, scripts), chaque fichier caché et tout lien symbolique pointant hors du dossier sont refusés.

Le contrat de tokens (--nb-*)

Ces custom properties sont la surface de theming publique — stable d’une version à l’autre. Les défauts de couleur sont donnés sous leur paire light-dark(light, dark) :

TokenDéfaut (clair / sombre)Rôle
bg#ffffff / #0e1116Fond de page
bg-soft#f6f7f9 / #151a21Panneaux, champs de saisie
bg-elev#ffffff / #161b22Surfaces élevées (topbar, popovers)
border#e4e7ec / #272e38Filets
text#1c2024 / #e7ebf0Premier plan principal
text-soft#5b6470 / #aab2bdPremier plan secondaire
text-faint#8a929e / #768091Premier plan tertiaire
accent#2f6feb / #6ea0ffLiens, focus, surlignages
accent-soft#e8f0ff / #182539Fonds d’accent
ref / work#2f6feb / #6ea0ff · #b5651d / #e0a060Pastilles d’espace dans les résultats de recherche
code-bg#0d1117 / #0b0e13Fond des blocs de code — le défaut, quand aucun theme.code n’est défini (les couleurs Shiki sont alors figées clair-sur-sombre, donc le code reste sombre dans les deux schémas)
topbar-h / sidebar-w / toc-w / content-max52px / 290px / 320px / responsiveMise en page (invariante au schéma)
radius8pxArrondi des coins
sans / monopiles systèmeFamilles de polices

Chaque token de couleur est une paire light-dark() — une seule déclaration couvre les deux schémas, et le sélecteur clair/sombre de l’en-tête (auto / clair / sombre, persisté par navigateur) les bascule tous d’un coup. Un thème qui change les couleurs fait de même :

/* docs/notabene-theme.css */
:root {
  --nb-accent: light-dark(#7c3aed, #b79bff);
  --nb-accent-soft: light-dark(#f1e9ff, #241a3d);
}

Le sélecteur fonctionne via color-scheme + un attribut data-scheme que le renderer pose sur <html> — un thème ne doit pas définir cet attribut (ni color-scheme sur :root) ; surchargez des tokens, et le sélecteur continue de fonctionner sans effort. light-dark() ne couvre que les couleurs ; pour le rare style par schéma hors couleur, sélectionner sur :root[data-scheme="dark"] dans votre feuille de style est très bien — c’est poser l’attribut qui est réservé.

Au-delà des tokens

Votre feuille de style peut aussi cibler un petit ensemble de points d’accroche stables : .topbar, .brand, .sidebar, .prose (le contenu rendu), .home-cards, .rail, plus les liens de navigation.nb-nav-link (tout lien sortant), .nb-sidebar-links (le bloc sous l’arbre des espaces) et .site-footer. Tout le reste — et chaque variable CSS sans préfixe — est interne et peut changer d’une version à l’autre.

Un thème style ces liens ; il n’en déclare jamais. Les entrées de nav, le texte du pied de page et les assets de branding sont des données du dépôt (nav, branding au premier niveau de la config), pas de l’apparence : une feuille de style installée ne doit pas pouvoir injecter des liens sortants dans un site publié, ni porter des libellés dans des langues qu’elle ne peut pas connaître.

Deux règles vous gardent en sécurité :

  • Ne surchargez que les tokens --nb-* et les points d’accroche ci-dessus. En particulier, ne touchez jamais aux variables sans préfixe : les vues print/PDF forcent une palette claire à travers elles, donc un thème limité aux tokens peut restyler tout le site sans jamais casser l’export PDF.
  • Vérifiez les deux schémas de couleur — le sélecteur de l’en-tête en fait un test à deux clics.

Les thèmes s’appliquent partout : dev, builds normaux, sites publics et les vues print (couleurs exceptées, à dessein).

La boucle de revue

La boucle de revue

La boucle est le produit : des commentaires en entrée, des éditions fidèles en sortie, le tout journalisé. Elle est conçue pour que n’importe quel agent puisse l’exécuter — le protocole est un fichier de skill en texte brut qui lit et écrit le store directement.

Comment l’agent travaille

Tout est découvert depuis notabene.config.mjs — rien de codé en dur, ni serveur ni port requis :

  1. Lire les commentaires ouverts et non mis en attente depuis <store>/ (un fichier JSON par commentaire).
  2. Localiser la page source via roots[], puis résoudre l’ancre textuelle avec tolérance (l’ancre stocke le texte cité + le contexte environnant + le titre le plus proche).
  3. Éditer les docs fidèlement — un commentaire est une décision de l’utilisateur.
  4. Marquer le commentaire résolu (ou addressed en mode approve) et compléter le journal : une entrée par passe, un enregistrement de changement par page touchée, relié aux ids des commentaires.
  5. Vérifier : le build du renderer tourne toujours, puis notabene lint (les liens inter-docs validés contre les routes que le build vient d’émettre), puis vos checks verify[].
  6. Rendre compte et demander avant de committer — jamais de commit silencieux, jamais de suppression en masse.

Les commentaires qu’un relecteur met en attente (⏸) sont ignorés — ce sont vos travaux en cours.

Les étapes 4 et 5 ont des primitives CLI, pour qu’un agent n’édite jamais le JSON du store à la main : notabene comments done <id…> --note … --journal <entryId> choisit le bon statut selon votre mode review, et notabene comments verify audite ce qu’il a écrit — statuts, liens commentaire↔journal dans les deux sens, disposition des fichiers. Il sort en code non nul sur un vrai problème : c’est donc aussi un garde-fou de CI sur le store que vos agents committent.

Mode approve : un humain valide chaque édition

Par défaut (review: "auto"), l’agent résout les commentaires directement. Passez review: "approve" pour un flux avec humain dans la boucle :

  • l’agent édite et marque chaque commentaire addressed au lieu de le résoudre ;
  • vous validez sur /review (ou via le filtre À valider de /comments) ;
  • vous voyez le vrai diff git de tout ce qui a changé pour ce commentaire — cascades comprises (un commentaire peut toucher plusieurs pages) ;
  • approuver → résolu, ou rejeter → rouvert avec votre raison, que l’agent lit à sa prochaine passe ;
  • le diff s’affiche en vue unifiée ou côte à côte, et un badge Review dans l’en-tête compte ce qui attend.

mode approve : l'agent propose, vous validez le vrai diff

L’utiliser depuis n’importe quel agent

Le protocole est une spec en texte brut, et notabene init l’installe dans votre repo pour qu’aucun agent n’ait à la chercher :

  • <store>/protocol.md — la spec complète, committée à côté des commentaires qu’elle décrit. Hors ligne, sans npm, sans réseau. C’est ce fichier que vous montrez à un agent.
  • AGENTS.md — un bloc borné <!-- notabene:begin -->…<!-- notabene:end --> qui indique aux agents qui le lisent au démarrage (Codex CLI, Cursor, Gemini CLI, Zed, Amp…) où vivent les commentaires et le protocole. Rien hors des marqueurs n’est touché ; désactivable via init --no-agents-md.

Les deux se rafraîchissent en relançant notabene init (idempotent) — à faire après avoir déplacé le store ou renommé un espace, et notabene doctor signale la dérive. Ce texte, c’est la page protocole agent de ce site — la version canonique, avec un jumeau Markdown dans les builds publics pour les agents qui naviguent ; npx -y @z29k/notabene@latest protocol l’imprime hors ligne.

Dans Claude Code, la skill du plugin est ce protocole : elle se déclenche sur « traite les commentaires de la doc » et n’a besoin d’aucun AGENTS.md. La forme du store est elle-même un contrat public versionné — voir la référence du store.

Doc multilingue

Doc multilingue (i18n)

Ajoutez i18n pour servir les mêmes docs en plusieurs langues avec des URL propres préfixées (la locale par défaut sans préfixe, les autres en /<locale>/…), un sélecteur de langue dans l’en-tête, des alternates hreflang et un chrome par page — une page française affiche nav, boutons et dates en français.

i18n: { locales: ["en", "fr"], defaultLocale: "en", strategy: "directory" },

i18n notabene : choisir une langue dans le sélecteur de l'en-tête, docs et chrome basculent

Deux organisations des fichiers

Choisissez l’organisation des fichiers avec strategy :

  • directory (par défaut) — un dossier par locale : docs/en/guide.md · docs/fr/guide.md.
  • suffix — une seule arborescence, traduite fichier par fichier : docs/guide.md (défaut) · docs/guide.fr.md. Idéal pour ajouter des langues à une doc existante : les fichiers de la langue par défaut ne bougent pas, donc leurs URL et leurs fils de commentaires sont préservés.

Préférence de langue & repli

Le sélecteur enregistre la langue choisie par le visiteur ; dès lors, arriver sur une page écrite dans une autre langue qui a une traduction redirige vers celle-ci — suivre n’importe quel lien vous garde dans votre langue. Une page sans traduction se replie sur la langue source et affiche un bandeau discret. Les pages non liées à une langue de contenu — /comments, /journal, /review, la page d’accueil et la 404 — suivent votre langue courante côté client et embarquent le même sélecteur.

Tout est par langue

  • Les commentaires sont par langue — un commentaire sur la page FR est un fil à part, relié au fichier source FR.
  • La recherche et l’export PDF (notabene pdf --locale fr) sont limités à une langue ; un site public embarque des llms.txt et des doubles Markdown par locale.
  • Toute chaîne humaine de la config accepte une map par locale : les label/description d’un espace (label: { en: "Docs", fr: "Documentation" }), la page d’accueil personnalisée (home: { en: …, fr: … }), et chaque libellé de lien de navigation, titre du bloc sidebar et ligne de pied de page. Non défini pour une locale → repli sur celle par défaut.

Omettez i18n pour une seule langue — le comportement est inchangé.

Export PDF

Export PDF

Transformez n’importe quelle page, n’importe quel dossier, n’importe quel espace ou la doc entière en un document soigné. Deux chemins, un même rendu optimisé pour l’impression (page de couverture + table des matières cliquable, palette forcée en clair pour que les diagrammes en mode sombre restent lisibles sur papier).

menu Export PDF : choisir une portée, obtenir une vue prête à imprimer avec couverture et TOC cliquable

Dans le navigateur — zéro dépendance

Le menu Export PDF de l’en-tête propose la page courante, son dossier, son espace ou la doc entière. Il ouvre une vue /print dans un nouvel onglet et déclenche automatiquement l’Enregistrer au format PDF de votre navigateur. Les routes /print sont statiques — elles existent en dev et dans tout build (y compris les sites publics).

notabene pdf — l’artefact haute fidélité

notabene pdf --scope space:docs --out docs.pdf

Construit le site, pilote un Chromium headless et écrit un PDF avec un vrai plan de signets (le panneau latéral navigable) et des numéros de page en pied. Flags : --scope doc|space:K|folder:K/P|page:K/I, --locale, --out, --chrome.

Requiert la peer dependency optionnelle puppeteer (ou puppeteer-core plus --chrome <path> / PUPPETEER_EXECUTABLE_PATH pointant vers un Chrome système) :

npm i -D puppeteer

Réglages

pdf: { enabled: true, pageSize: "A4", margin: "18mm" },

enabled: false masque le menu Export et retire les routes /print. pageSize/margin alimentent la boîte CSS @page. Couvertures et titres de section réutilisent les labels et l’ordre de la sidebar, si bien que le PDF se lit dans le même ordre que le site.

Publier un site public

Publier un site public

L’app de revue est un outil local de dev — mais les docs qu’elle rend méritent souvent un foyer public. build --public produit un artefact purement statique, en lecture seule fait pour ça :

notabene build --public --site https://you.github.io --base /your-repo --out ./_site

Ce site même est construit ainsi — les docs de notabene, rendues et publiées par notabene.

Ce qui reste, ce qui part

  • Tout l’interactif disparaît — structurellement. Pas d’UI de commentaires, pas d’invite d’identité, pas de /comments / /review / /journal, pas d’API, et rien du store .notabene. Les routes ne sont pas verrouillées ; elles ne sont pas construites.
  • Ce qui reste est l’expérience de lecture complète : nav, recherche, Mermaid + lightbox d’images, mode sombre, i18n (pages par locale, sélecteur, hreflang), routes print/PDF, 404.
  • Né lisible par les agents. Chaque page embarque un double Markdown à <page>/index.md (annoncé via <link rel="alternate" type="text/markdown">), le site embarque /llms.txt (un index machine de chaque page, par locale) et /llms-full.txt (la doc entière en un seul document Markdown dans l’ordre de lecture), plus robots.txt, un sitemap, des URL canoniques, des meta OpenGraph/Twitter et du JSON-LD. Essayez ici : /llms.txt.

Recherche plein texte (optionnelle)

Le site public hérite de la recherche intégrée telle quelle. Installez Pagefind en dépendance de dev et build --public la promeut en recherche plein texte statique :

npm i -D pagefind

Le build indexe l’artefact final : stemming par langue (un visiteur de /fr/ cherche dans un index français, avec les formes fléchies du français), extraits surlignés sous chaque résultat, et un poids réseau qui reste faible quand la doc grandit — le navigateur ne télécharge que les morceaux d’index utiles à la requête. Zéro configuration, même champ de recherche. Non installé → le site public garde la recherche JSON intégrée. Et le contenu privé ne peut pas fuiter dans l’index : l’indexation s’exécute sur l’artefact, où les pages hors périmètre n’existent pas.

La même dépendance améliore aussi l’app de revue notabene dev : son index est construit en direct depuis vos sources Markdown et rafraîchi quand elles changent — aucun build en jeu. Deux différences propres au dev : le site de dev (donc son index) inclut vos pages privées, et les résultats profonds par section (ancres de titres) restent réservés au public.

Où aller ensuite

La boucle de dev est intacte : notabene dev et le simple notabene build se comportent exactement comme avant — la publication est opt-in, par build.

Configurer publish

Configurer publish

Tout vit sous une seule clé de config optionnelle — les flags CLI (--site/--base) la surchargent, et --out copie l’artefact vers un chemin stable (il refuse d’écraser quoi que ce soit qu’il n’a pas généré) :

// notabene.config.mjs
export default {
  // …
  publish: {
    // ORIGINE déployée. Grave des URL absolues dans canonical, og:url, JSON-LD,
    // hreflang, llms.txt, le sitemap et la ligne Sitemap de robots.txt.
    // OPTIONNELLE — omettez-la pour garder le domaine hors du repo (voir
    // « Domaine géré côté serveur »). Origine seule, sans chemin : un sous-chemin va dans `base`.
    site: "https://you.github.io",

    // Sous-chemin quand le site est servi sous un préfixe (site de projet
    // GitHub Pages → "/<repo>"). Préfixe chaque lien et URL d'asset — contrairement
    // au domaine, un sous-chemin affecte toujours le rendu, il ne peut pas être côté serveur.
    base: "/your-repo",

    // Sous-arborescences à garder hors des builds publics — globs comparés à
    // `<space key>/<page id>` (indépendant de la locale : un motif masque chaque
    // traduction d'une page). `*` = un segment de chemin, `**` = toute profondeur.
    exclude: ["docs/internal/**", "docs/*/draft"],
  },
};

Trois configurations typiques

publish: { site: "https://you.github.io", base: "/my-repo" }  // GitHub Pages, site de projet
publish: { site: "https://docs.example.com" }                 // domaine personnalisé à la racine
publish: { exclude: ["docs/internal/**"] }                    // domaine gardé hors du repo

La troisième est le mode agnostique à l’origine — le même artefact derrière n’importe quel domaine.

Métadonnées par page

Deux clés de frontmatter alimentent les surfaces publiques (voir la référence complète du frontmatter) :

---
description: One-line summary — becomes the meta description / OpenGraph.
publish: false   # cette page ne part jamais dans un build public
---

Exclure du contenu du build a sa propre page : garder du contenu privé.

Garder du contenu privé

Garder du contenu privé

Trois niveaux, du plus grossier au plus fin — chacun vit là où vit ce qu’il délimite.

Un espace entier — marquez l’entrée roots[] :

roots: [
  { key: "docs",  label: "Docs",  path: "docs" },
  { key: "notes", label: "Notes", path: "notes", publish: false },  // l'espace entier reste privé
],

Une sous-arborescence — des globs publish.exclude sur <space key>/<page id> (c’est le chemin d’URL de la page sans préfixe de locale, donc un seul motif masque toutes les traductions) :

publish: { exclude: ["docs/internal/**", "docs/*/draft"] },

Une seule page — son propre frontmatter :

---
publish: false   # cette page n'apparaît jamais dans un build public
---

Un lien de navigation — pas du contenu, mais la même idée : une entrée nav marquée publish: false reste en dev et n’atteint jamais l’artefact (un dashboard, un wiki interne) :

nav: { header: [{ label: "Dashboard ops", href: "https://ops.internal", publish: false }] },

La garantie

Le contenu privé n’est pas caché, il n’est pas construit — pas de route (l’URL renvoie un 404), pas d’entrée dans la sidebar, pas de résultat de recherche, pas de ligne llms.txt, pas de double .md, pas d’entrée de sitemap, pas d’inclusion print/PDF, et le nom et le chemin de l’espace n’atteignent jamais le HTML public.

notabene dev et les builds normaux montrent toujours tout — vous passez en revue vos docs privées exactement comme le reste. La notion de publish n’existe que pour les builds publics.

Une réserve : les liens vers du contenu privé

Si une page publique pointe vers une page privée, ce lien renvoie un 404 dans l’artefact public — le build ne le réécrit pas et n’émet aucun avertissement. notabene lint attrape exactement cela : lancez-le après build --public et chaque lien d’une page publique vers du contenu exclu par le scoping est signalé (la vérité des routes publique ne contient tout simplement pas ces pages — voir la référence CLI).

Domaine géré côté serveur

Domaine géré côté serveur ? Omettez site

Quand le domaine public est l’affaire du serveur — un vhost ou un reverse proxy en frontal, plusieurs miroirs, ou un domaine pas encore choisi — ne définissez simplement pas site (et ne passez pas de --site). L’artefact devient agnostique de l’origine : pas une seule URL absolue n’y est embarquée, la même sortie fonctionne donc derrière n’importe quel domaine, et changer de domaine n’exige jamais de rebuild.

Ce qui change

SurfaceAvec siteSans
llms.txt / llms-full.txt / doubles .mdURL absolueschemins relatifs à la racine (les agents les résolvent contre l’origine depuis laquelle ils les ont récupérés)
alternates hreflangabsoluespar chemins
canonical, og:url, JSON-LDémisnon émis — ils n’ont de sens qu’avec une origine
sitemap + ligne Sitemap: de robots.txtémisnon émis — les spécifications exigent des URL absolues

Ce que cela coûte, concrètement

La visibilité dans les moteurs de recherche — rien d’autre. Sans sitemap, URL canoniques ni hreflang valide, les crawlers ne découvrent les pages qu’en suivant les liens, rien ne consolide les doublons si la doc répond sur plusieurs domaines, et les pages multilingues n’envoient aucun signal de langue aux moteurs de recherche. Les aperçus de liens sociaux gardent leur titre/description mais perdent la carte d’URL. Les lecteurs humains et les agents IA ne perdent rien — chaque page, double et fichier llms fonctionne à l’identique.

Règle simple : hébergement interne, miroir, ou domaine pas encore arrêté → omettez site ; un site public dont le référencement compte → définissez site.

base reste indépendant

Définissez base dès que le site vit sous un sous-chemin, avec ou sans domaine — un sous-chemin affecte toujours les liens rendus, il ne peut donc pas être laissé au serveur.

Déployer via GitHub Pages

Déployer via GitHub Pages

Déployez sur n’importe quel hébergement statique. Pour GitHub Pages : Settings → Pages → Source = GitHub Actions, puis :

# .github/workflows/docs.yml
name: docs
on:
  push:
    branches: [main]
permissions:
  contents: read
  pages: write
  id-token: write
concurrency:
  group: pages
  cancel-in-progress: false
jobs:
  publish:
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: npm ci
      - run: npx notabene build --public
          --site https://${{ github.repository_owner }}.github.io
          --base /${{ github.event.repository.name }}
          --out ./_site
      - uses: actions/upload-pages-artifact@v3
        with: { path: ./_site }
      - id: deployment
        uses: actions/deploy-pages@v4

Notes :

  • Avec publish: { site, base } défini dans votre config, les flags --site/--base peuvent être omis entièrement.
  • Hébergement sur un domaine personnalisé ou un site racine utilisateur/organisation ? Retirez --base et pointez --site vers votre domaine.
  • L’artefact embarque un .nojekyll, donc les assets _astro/ survivent même à l’hébergement classique par branche gh-pages.
  • npm i -D pagefind dans votre repo et ce même workflow embarque aussi la recherche plein textenpm ci l’installe, le build la détecte. Rien à changer ici.
  • Ce site de documentation est déployé par exactement ce workflow — voyez-le dans le repo.

Référence

Référence

Référence

Le pendant exhaustif du Guide — tableaux et contrats, une page par surface :

CLI

CLI

Le package npm est scopé (@z29k/notabene) ; la commande installée est simplement notabene, donc npx notabene … fonctionne tel quel dès lors que le package est une dépendance de votre repo. Sans installation locale, utilisez toujours le nom scopé — npx -y @z29k/notabene@latest … — car notabene non scopé n’est pas notre package.

CommandeCe qu’elle fait
notabene doctorÉtat en lecture seule au format JSON : config/store/port + dossiers de doc détectés — --json
notabene initÉcrit notabene.config.mjs + crée le store (sans effet s’il existe déjà) ; --detect auto-détecte les dossiers de doc. Écrit aussi le point d’entrée agent : <store>/protocol.md + un bloc borné dans AGENTS.md — désactivables via --no-protocol / --no-agents-md. Idempotent : relancez-le pour rafraîchir les deux
notabene devDémarre le serveur de revue sur les docs de ce repo (rechargement à chaud) ; --detach le lance en démon d’arrière-plan. Avec la dépendance de dev optionnelle pagefind, sa recherche devient plein texte aussi
notabene statusLe serveur détaché tourne-t-il ? (pid, port, URL) — --json
notabene stopArrête le serveur détaché
notabene buildConstruit le site (Node standalone ; docs prérendues, pas d’API d’écriture dans l’artefact)
notabene build --publicSite statique en lecture seule pour hébergement public — voir le guide. [--site URL] [--base /sub] [--out DIR]. Avec la dépendance de dev optionnelle pagefind, l’artefact gagne une recherche plein texte statique
notabene previewSert le site construit
notabene lintValide les liens inter-docs contre les routes émises par le dernier build (suggestions « did you mean » ; --json). Après build --public, attrape aussi les liens de pages publiques vers du contenu privé. Exit 1 = liens cassés, 2 = pas encore de build
notabene pdfExporte un PDF via Chromium headless (sommaire de signets + numéros de page) ; --scope doc|space:K|folder:K/P|page:K/I, --locale, --out, --chrome. Nécessite la peer dep optionnelle puppeteer (ou puppeteer-core + --chrome)
notabene migrateConvertit le store vers la disposition un-fichier-par-commentaire (estampille schemaVersion 3)
notabene comments lsListe les commentaires — --open --json --page <p> (pour agents/scripts)
notabene comments doneMarque des commentaires traités : done <id…> [--note <texte>] [--journal <entryId>]. Le statut vient de review (auto → resolved, approve → addressed) — --status force, --force agit sur un commentaire en attente (hold). Écriture atomique ; tous les autres champs préservés
notabene comments reopenRenvoie des commentaires à open : reopen <id…> [--reply <texte>] [--author <nom>] — la raison devient une réponse du thread que l’agent lira à la passe suivante (le pendant CLI du rejet sur /review)
notabene comments verifyAudite le store : statuts, liens commentaire↔journal dans les deux sens, disposition des fichiers, doublons, pages disparues. --json ; exit 1 si erreurs, 2 sans store. À lancer après une passe d’agent, ou en CI
notabene journal addAjoute une entrée de journal JSON lue depuis stdin (écriture atomique ; --json renvoie { id } pour chaîner côté agent)
notabene protocolImprime le protocole agent sur stdout — --path affiche son chemin, --write rafraîchit <store>/protocol.md

Flags globaux

FlagSignification
--root <path>Racine du repo consommateur (défaut : cwd)
--config <path>Chemin de la config (défaut : <root>/notabene.config.mjs)
--port <n>Port du serveur de dev (sinon la config port, sinon un port libre)
--detachdev uniquement : démon d’arrière-plan (géré par status/stop)
--detectinit uniquement : préremplit roots[] avec les dossiers de doc trouvés
--no-protocol / --no-agents-mdinit uniquement : ne pas écrire la copie <store>/protocol.md / le bloc AGENTS.md
--hostExpose sur le LAN — réseaux de confiance uniquement (sécurité)
--public / --site / --base / --outbuild uniquement : l’artefact de site public

Clés de configuration

Clés de configuration

notabene.config.mjs est un module ES de données uniquement à la racine de votre repo ; chaque clé est optionnelle. La version narrative, avec exemples, se trouve dans le guide de configuration.

CléDéfautSignification
siteName / tagline"Docs" / "docs"Marque de l’en-tête
locale"en"Langue de l’UI + collation du tri de la nav
format"mdx""mdx" (.mdx strict + .md tolérant) ou "commonmark" (pas de MDX du tout). init génère "commonmark"
roots[][{docs}]Espaces de doc : { key, label, path, exclude, description, publish }. label/description acceptent une map par locale avec l’i18n ; publish: false garde l’espace hors des builds publics
store"docs/.notabene"Dossier commentaires + journal — committez-le (contrat)
homePage d’accueil personnalisée : un fichier Markdown relatif au repo (ou une map par locale) rendu au-dessus des cartes d’espaces sur /
brandingAssets d’identité : { logo, logoDark, favicon, socialImage }, fichiers relatifs au repo servis sous /_nb/…. Favicon non défini → une marque par défaut intégrée
themePersonnalisation du rendu : { tokens, css, assets, code } — surcharges de tokens --nb-* (validées ; une coquille lève une erreur), une feuille de style chargée après celle du renderer (sûre vis-à-vis des cascade layers), un dossier du repo servi sous /_nb/assets/… pour les polices/images (allow-list d’extensions, pas de traversée), un thème Shiki pour le code ("github-light" ou { light, dark } — les deux palettes suivent le sélecteur de schéma), et mermaid: false pour garder la palette de diagrammes propre à Mermaid
navLiens sortants : { header[], sidebar: { title, links[] }, footer: { links[], text, poweredBy } }. Une seule forme d’entrée — { label, href, icon, iconOnly, publish } ; label accepte une map par locale, publish: false garde un lien hors des builds publics. Validé au chargement (allow-list de schémas, noms d’icônes, doublons)
port3009Port d’astro dev
hostfalsetrue/NOTABENE_HOST=1/--host expose l’API d’écriture au LAN (sécurité)
verify[][]Vérifications post-édition que l’agent exécute (le build du renderer s’exécute toujours)
review"auto""auto" = l’agent résout les commentaires ; "approve" = l’agent propose (addressed), vous validez chacun sur /review avec un diff (boucle de revue)
authorgit user.nameAuteur de commentaire par défaut ; chaque navigateur le remplace par appareil via le dialogue d’identité
authorEmailgit user.emailE-mail d’auteur par défaut ; intégré façon git (Name <email>) pour garder les identités uniques
editPatternLien « Modifier cette page » sous chaque page : une URL avec un placeholder {path} (chemin source relatif au repo), ex. https://github.com/o/r/edit/main/{path}. Placeholder obligatoire — validé au chargement
pdf{ enabled: true, pageSize: "A4", margin: "18mm" }Export PDFenabled active le menu Export + les routes /print ; pageSize/margin définissent la boîte @page
i18nDoc multilingue : { locales, defaultLocale, strategy: "directory"|"suffix" }. Omettez pour une seule langue
publishCible du build public : { site, base, exclude }. site optionnel — omis = artefact agnostique de l’origine

Surcharges CLI/env

--site/--base surchargent publish.site/publish.base ; NOTABENE_HOST=1 équivaut à host: true ; la CLI passe l’identité git du repo comme repli pour author/authorEmail. Rien d’autre n’est configurable hors de ce fichier.

Frontmatter

Frontmatter

Du YAML optionnel tout en haut d’une page. Tout a un défaut raisonnable — un repo sans aucun frontmatter se rend très bien (noms de fichiers humanisés, ordre alphabétique).

---
title: Internal network map        # <title> de la page + fil d'Ariane (prime sur le premier H1)
description: Segments and VLANs.   # builds publics : meta description + OpenGraph + JSON-LD
publish: false                     # builds publics : exclut cette page entièrement
lastUpdated: 2026-05-04            # pied de page : prime sur la date git « Mis à jour le »
sidebar:
  label: Network map               # texte de la sidebar (sinon title, sinon nom de fichier humanisé)
  order: 9                         # position parmi les pages sœurs (croissant)
  indexLabel: Start here           # pages d'accueil de dossier : renomme l'entrée « Aperçu »
---
CléEffet
title<title> de la page, fil d’Ariane, titre du résultat de recherche. Se replie sur le premier # H1, puis sur le nom du fichier
descriptionBuilds publics : <meta name="description">, description OpenGraph/Twitter, JSON-LD
publish: falseBuilds publics : la page n’est pas construite — ni route, ni nav, ni recherche, ni llms, ni double, ni sitemap. Les builds dev/normaux la montrent toujours
lastUpdatedPrime sur la date dérivée de git dans le Mis à jour le du pied de page. Toute date que YAML sait parser. Utile quand l’historique git induit en erreur (contenu importé ou généré)
sidebar.labelTexte de l’entrée dans la sidebar. Résolution : sidebar.labeltitle → nom de fichier humanisé
sidebar.orderClé de tri parmi les pages sœurs, croissante. Les entrées sans valeur gardent l’ordre alphabétique, après celles qui sont ordonnées. Groupes et pages partagent un seul ordre
sidebar.indexLabelSur la page d’accueil d’un dossier : renomme son entrée Aperçu localisée

Dossiers

Un dossier est nommé et positionné par sa page d’accueil — <folder>/index.md (ou readme.md) : son frontmatter sidebar s’applique au groupe entier, et la page elle-même apparaît comme l’entrée Aperçu du groupe. Labels et ordre se propagent aux fils d’Ariane et aux couvertures PDF.

Les clés inconnues sont ignorées et préservées — les agents qui éditent une page doivent garder le frontmatter existant intact (le skill de revue le fait).

Le contrat du store .notabene

Le contrat du store .notabene

Le store est un contrat public versionné — committé dans votre repo, lu et écrit par les agents. Sa forme ne change jamais silencieusement : <store>/meta.json porte { "schemaVersion": n } (actuellement 3), et tout changement de forme s’accompagne d’un migrateur (notabene migrate).

Disposition

<store>/
  meta.json                # { "schemaVersion": 3 }
  journal.json             # un tableau d'entrées de journal
  protocol.md              # le protocole agent (écrit par `init` ; ce n'est pas de la donnée)
  <page>/<comment-id>.json # UN FICHIER PAR COMMENTAIRE → merges git sans conflit

meta.json, journal.json et protocol.md sont des noms réservés à la racine du store ; tout le reste est de la donnée de commentaire. Les lecteurs ne parsent que du .json, donc protocol.md est inerte — il voyage avec le store pour que la spec soit toujours à côté des commentaires.

<page> est le chemin logique de la page (docs/guide/setup — avec l’i18n c’est l’id brut encodant la locale, les commentaires sont donc par langue). Les anciens stores v1 (un tableau par page) sont toujours lus ; toute écriture migre la page concernée.

Un commentaire

{ "id", "space", "page", "scope",            // scope : "selection" | "page" | "block"
  "anchor": {                                 // selection : citation de texte façon W3C
    "quote", "prefix", "suffix", "section"    // texte rendu + contexte + titre le plus proche
  } | { "kind", "key", "label",               // block (diagramme/image) : clé dérivée du contenu
        "section", "index" } | null,
  "thread": [{ "author", "body", "ts" }],     // author peut être façon git "Name <email>"
  "status": "open" | "addressed" | "resolved",
  "hold": false,                              // true → l'agent l'ignore (WIP du relecteur)
  "resolution": { "note", "journalEntryId" } | null,
  "createdAt", "updatedAt" }

addressed est l’état de la revue en deux phases : proposé par l’agent, en attente de validation humaine sur /review.

Une entrée de journal

{ "id", "date",                               // YYYY-MM-DD
  "title", "summary",
  "changes": [{ "page", "commentIds": [], "what", "why" }] }

Un enregistrement changes[] par page réellement touchée — le diff de /review est construit en inversant le journal, donc une page qui n’y figure pas n’apparaîtra pas dans le diff du relecteur. Le resolution.journalEntryId de chaque commentaire résolu pointe vers son entrée.

Règles que les agents doivent honorer

  • Les écritures sont atomiques (fichier temporaire + rename) — ne jamais écrire du JSON partiel à la main. La CLI (comments done / reopen, journal add) s’en charge ; comments verify audite le résultat.
  • Ne jamais supprimer le store en masse ; supprimez un commentaire précis par id si on vous le demande.
  • Ne traiter que status: "open" et hold: false.
  • Le protocole complet vit dans <store>/protocol.md — écrit par notabene init, committé avec le store, rafraîchi en relançant init. Pointez-y n’importe quel agent.

Modèle de sécurité

Modèle de sécurité

L’API de commentaires écrit dans votre git — elle est donc cloisonnée, par construction :

  • Dev uniquement. Le chemin d’écriture n’existe que sous notabene dev. En build/preview les mutations renvoient 403, et un build public ne contient pas ces routes du tout.
  • Loopback par défaut. Le serveur se lie à 127.0.0.1 ; l’API d’écriture n’est pas joignable depuis votre réseau sans opt-in explicite via --host / NOTABENE_HOST=1 — réseaux de confiance uniquement.
  • Chaque écriture est gardée au-delà du bind : les requêtes cross-origin sont refusées (anti-CSRF), un en-tête Host non-loopback est refusé en mode loopback (anti-DNS-rebinding), et — quand vous définissez NOTABENE_TOKEN — chaque écriture doit porter un x-notabene-token correspondant. Définir un token est recommandé avec --host.
  • Une identité par personne. Sur un hôte non-loopback, chaque visiteur est invité à renseigner son nom (+ e-mail optionnel) avant de naviguer, pour que les commentaires soient attribués à de vraies personnes plutôt qu’au défaut git du propriétaire du repo.
  • L’agent ne committe jamais sans demander et ne supprime jamais le store en masse — cela fait partie du protocole.
  • La CLI est une surface distincte. Les règles ci-dessus encadrent l’API d’écriture HTTP. Les commandes qui écrivent dans le store (comments done / reopen, journal add) sont des commandes locales que vous — ou un agent dans votre terminal — lancez délibérément : ni serveur, ni port, ni réseau. Écriture atomique, un commentaire à la fois, et comments verify audite le résultat.

L’artefact public est l’image miroir : pas d’API d’écriture, pas de données du store, pas d’identité — rien à garder, parce que rien n’est construit.