CommunityRédaction et éditiongithub.com

buildinternet/releases

Registre de notes de version produit, conçu pour les agents. Obtenez les changelogs récents des produits via MCP et CLI.

Qu'est-ce que releases ?

Cette compétence permet à un agent Claude Code local d'ingérer des changelogs de produits sans passer par le pipeline d'extraction distant, réduisant ainsi les coûts d'inférence et évitant les appels API serveur coûteux. L'agent lui-même agit comme extracteur : il récupère les pages web, parse le contenu, structure les enregistrements et les publie sur un endpoint de batch qui effectue une insertion upsert déterministe, sans aucune IA. Idéale pour intégrer de nouvelles sources de changelogs, pour contourner les échecs d'extraction distante (par exemple, lorsque `max_tokens` est atteint), ou pour effectuer un backfill de sources existantes sans déclencher de coûts de traitement serveur. Elle prend en charge l'extraction parallèle via l'outil `Agent` et fonctionne exclusivement sur un système de fichiers local avec les variables d'environnement `RELEASES_API_*`. La compétence inclut également une étape de pré-vérification obligatoire pour respecter les politiques de consentement des éditeurs (via `robots.txt`), et offre des fonctionnalités d'enrichissement manuel après l'ingestion initiale.

Compatible avec✓Claude Code~Codex CLI~Cursor
npx skills add buildinternet/releases

Installed? Explore more Rédaction et édition skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Ingeste locale

Intégrez un changelog sans déclencher le chemin de récupération à distance. Le chemin distant (releases admin source fetch → POST /v1/workflows/update → le DeterministicUpdateWorkflow du worker API, #1946) exécute l'extraction corps→enregistrements côté serveur (Haiku incrémental ou la boucle d'outils) et facture cette inférence — même lorsqu'il écrit zéro publications. Un agent Claude Code local est déjà un modèle capable avec des outils de récupération web + fichiers, il peut donc effectuer l'extraction par page en ligne (intégrée dans la session déjà en cours) et n'a besoin que d'un chemin d'écriture déterministe.

Principe fondamental : l'agent est l'extracteur. Vous récupérez, vous analysez, vous structurez les enregistrements, et vous les POSTez sur le endpoint batch, qui effectue un upsert idempotent simple sans IA lors de l'insertion. La boucle d'extraction distante ne s'exécute jamais.

Claude Code local uniquement. Cette compétence utilise l'outil Agent pour le fan-out de sous-agents et suppose un système de fichiers local persistant et les variables d'environnement RELEASES_API_* de la CLI. Elle se place à côté de seeding-playbooks comme compétence d'opérateur local uniquement — elle n'est pas déployée sur la flotte d'agents gérés.

Contrat de coûts

Énoncez-le explicitement à l'opérateur avant une exécution importante, et respectez-le :

  • Dépenses : vos propres jetons de l'agent pour la récupération + l'extraction, plus ceux des sous-agents que vous déployez (votre choix de modèle — sonnet pour la qualité, haiku pour le volume).
  • NE dépense PAS : aucune extraction côté serveur (les passes Haiku incrémental / boucle d'outils du workflow de mise à jour), aucune extraction web_fetch à distance. POST /v1/workflows/update n'est jamais appelé.
  • Le endpoint batch n'exécute aucune IA lors de l'insertion — pas de résumé, pas de classifieur marketing, pas d'enrichissement de flux. Il effectue une insert plus un waitUntil pour un embed vectoriel. Les champs IA (title_generated / title_short / summary) sont intentionnellement laissés vides ; voir Enrichissement manuel ci-dessous pour les chemins de suivi délibérés.

Le signal négatif qui confirme qu'aucune exécution à distance n'a eu lieu : dans Axiom releases-cloudflare-logs, il n'y a pas de POST .../fetch?sessionId=det-… et pas d'événement extract-deps-worker pour la source.

Quand l'utiliser

  • Intégrer une nouvelle source (ou remonter une source existante) de manière interactive dans Claude Code et vous souhaitez éviter le coût d'extraction à distance.
  • Une récupération à distance a brûlé une boucle d'extraction complète et a écrit 0 publications (le mode d'échec Conductor / grand corps / max_tokens) — extraire en ligne contourne entièrement la boucle.
  • La page est accessible par vos propres outils web (le refus ci-dessous est un choix de politique, pas une limite de récupération).

Quand NE PAS l'utiliser :

  • Vous êtes dans une session d'agent géré. Ceci est local uniquement (fan-out d'outil Agent + système de fichiers local + environnement CLI). Les sessions MA doivent continuer à utiliser le chemin de récupération normal.
  • La source a un flux propre et riche en contenu. Ajoutez-la simplement (managing-sources) et laissez le pipeline cron la récupérer. L'ingeste locale est destinée aux cas où l'agent effectuant l'extraction en ligne est le bon outil, pas un remplacement pour les adaptateurs de flux/GitHub.
  • L'éditeur refuse (la pré-vérification refuse — voir Étape 1).

Workflow

Étape 1 — Pré-vérification (barrière de sécurité obligatoire)

Exécutez la vérification d'opt-out avant de récupérer ou d'écrire quoi que ce soit :

bun .claude/skills/local-ingest/preflight.ts <url> --json

Elle récupère robots.txt (en suivant les redirections apex→www), analyse la politique Cloudflare Content Signals, et se verrouille sur le code de sortie :

SortieVerdictAction
0proceedPermission ou absence — continuez.
1refuseai-input=no déclaré. ARRÊTEZ. Informez l'utilisateur. Ne récupérez ni n'écrivez.
2unknownImpossible de récupérer/analyser robots.txt. Réessayez une fois ; si toujours inconnu, informez l'utilisateur — ne supposez pas "proceed".

La barrière s'applique à ai-input uniquement. L'ingeste locale est une ingestion de recherche/d'entrée — alimenter un index de recherche — ce qui est exactement ce que ai-input régit. ai-train=no refuse l'utilisation pour l'entraînement du modèle, un usage que ce chemin n'effectue pas, donc ai-train=no seul (par ex. vercel.com) continue correctement.

C'est la leçon du Conductor. conductor.build sert Content-Signal: ai-train=no, search=yes, ai-input=no — son org/source est déjà en pause dans le registre exactement pour cette raison (releases admin playbook conductor). Une intégration locale ne doit pas silencieusement dépenser des jetons ou ingérer du contenu en violation d'un refus de l'éditeur. conductor.build doit être refusé (via son ai-input=no) — c'est la cible de régression pour cette barrière.

Le refus est un choix de politique, pas une limitation technique : web_fetch et CF Browser Rendering peuvent toujours récupérer ces pages (seul le endpoint /crawl de Cloudflare a appliqué le signal pour Conductor). Respectez le refus quand même. Ne passez outre qu'avec une autorisation explicite de l'éditeur documentée par l'opérateur — il n'y a pas de contournement silencieux.

La pré-vérification imprime également toutes les URL Sitemap: de robots.txt — conservez-les pour l'Étape 3.

Étape 2 — Résoudre la cible

Assurez-vous que l'org et la source existent avant d'écrire — les publications sont attachées à un source_id. Référez-vous à managing-sources pour les règles de création/nomination/primaire et à finding-changelogs pour déterminer si un flux structuré existe (si c'est le cas, préférez le pipeline normal). Ensuite :

  • Lisez le playbook de l'org pour les indices d'analyse : releases admin playbook <org> (voir managing-sources → Playbooks).
  • Capturez le sourceSlug et l'ID src_… de la ligne créée/résolue — vous avez besoin de l'un des deux pour l'URL du batch.

Si la source est entièrement nouvelle, créez-la (par ex. releases admin source create "<name>" --url <url> --org <org> [--primary]) mais ne déclenchez pas de récupération — cela enverrait le workflow de mise à jour à distance que vous essayez d'éviter.

Étape 3 — Cartographier les pages

Classez la forme de la page (releases admin discovery evaluate <url> --json signale pageStructure: single-page | index | unknown) :

  • Changelog sur une seule page → une récupération à l'Étape 4.
  • Index → détail → énumérez les URL par publication. Préférez /sitemap.xml (la pré-vérification l'a récupéré) filtré sur le chemin du changelog ; sinon, analysez le HTML de l'index pour les liens par publication.
  • Index rendu côté client → l'index est une coquille JS dont les cartes se chargent via un fetch côté client, donc curl/le scraping sans JS renvoie zéro entrée. Énumérez en rendant : pilotez l'index avec Claude dans Chrome (ou déployez un sous-agent de rendu), paginez si la liste est fenêtrée, et collectez les liens par publication. Vérifiez ensuite si les pages de détail sont rendues statiquement — elles le sont souvent même lorsque l'index ne l'est pas — et si c'est le cas, récupérez-les en masse avec curl directement (HTML→markdown localement, pas de rendu, pas de facture IA). Les help.harvey.ai/release-notes de Harvey sont exactement cela : index rendu côté client, pages de détail statiques. Ne concluez pas "aucune publication" à partir d'un scraping JS vide de l'index.

Limitez-vous à une fenêtre récente (par défaut ~25–50 publications les plus récentes) et enregistrez ce que vous avez sauté — ne tronquez jamais silencieusement. Les ré-exécutions sont idempotentes, donc un backfill plus profond plus tard ajoute simplement des lignes.

Étape 4 — Récupération + extraction

Réutilisez les conventions de parsing-changelogs pour le type/version/date/média (voir Parité ci-dessous).

  • Quelques pages : récupérez et extrayez en ligne dans cet agent.
  • Beaucoup de pages : déployez des sous-agents parallèles — une tranche d'URL chacun, chacun retournant des enregistrements dans le schéma du batch. Utilisez le modèle de distribution par outil Agent de seeding-playbooks (et superpowers:dispatching-parallel-agents pour la discipline de fan-out). Ne pas récupérer les URL des publications dans l'agent parent — déléguez pour garder le contexte du parent propre.
  • Mise en garde de sauvegarde par le parent : il peut être interdit aux sous-agents d'effectuer l'écriture batch (limites de permissions). Prévoyez-le — demandez à chaque sous-agent de retourner ses enregistrements dans son résultat, et laissez le parent (vous) effectuer les écritures à l'Étape 5. Même modèle que seeding-playbooks pour la sauvegarde de notes.
  • Extrayez les médias — n'expédiez pas que du texte. Récupérez les images/vidéos de chaque corps de publication et remplissez media, en dépaquetant les wrappers d'optimiseur _next/image / _vercel/image vers l'URL CDN sous-jacente (normalizeMediaUrl de packages/rendering/src/media-url.ts). Un premier passage uniquement textuel est un oubli courant qui n'est détecté qu'à la révision de l'opérateur (Harvey a expédié du texte uniquement, puis a dû réextraire _next/image → cdn.sanity.io et réensemencer) ; le re-POST d'enrichissement est mode: "upsert-content" (Étape 5).

Chaque enregistrement doit correspondre au schéma du batch (voir Étape 5) et aux règles de parité.

Optionnel — extraction déterministe (Approche A, monorepo uniquement) : au lieu d'extraire manuellement, vous pouvez appeler les bibliothèques d'extraction de production dans packages/adapters/src/extract/ — extractFromBody, extractWithTools, runDirectFetchExtraction. ExtractDeps.cloudflare est nullable et repo peut être un noop, donc elles s'exécutent avec juste une clé Anthropic (preuve : scripts/smoke-toolloop.ts). Cela échange les jetons d'agent contre des jetons d'API Anthropic directs mais reproduit exactement la boucle d'outils de production. Ces packages sont private: true — accessibles uniquement depuis le monorepo, jamais depuis la CLI légère. La valeur par défaut reste l'Approche B (l'agent est l'extracteur).

Étape 5 — Écriture (upsert par batch)

POSTez les enregistrements vers le endpoint batch par morceaux (~50/requête maintient les requêtes limitées ; le handler effectue le découpage du binding D1 côté serveur) :

# Utilisez le chemin limité à l'org. La forme bare /v1/sources/:slug n'accepte QU'UN ID
# typé `src_…` — un slug humain renvoie 400 bare_slug_rejected (les slugs sont
# limités à l'org, #690). Avec un ID typé, la forme bare fonctionne aussi :
#   POST $RELEASES_API_URL/v1/orgs/<orgSlug>/sources/<sourceSlug>/releases/batch
#   POST $RELEASES_API_URL/v1/sources/<src_…>/releases/batch
curl -sS -X POST "$RELEASES_API_URL/v1/orgs/<orgSlug>/sources/<sourceSlug>/releases/batch" \
  -H "Authorization: Bearer $RELEASES_API_KEY" \
  -H "Content-Type: application/json" \
  -d @chunk.json   # { "releases": [ … ] }

Champs par publication (la forme acceptable réelle — workers/api/src/routes/sources.ts, postReleasesBatchHandler) :

{
  "version": "1.4.0", // optionnel ; omettez/null pour les sources datées ou les blogs
  "title": "Mode sombre", // obligatoire
  "content": "Markdown body…", // obligatoire
  "url": "https://…/changelog/dark-mode", // optionnel mais TOUJOURS à remplir — c'est la clé de déduplication
  "contentHash": "…", // optionnel
  "publishedAt": "2026-05-30", // optionnel ; ISO-8601 (voir Parité)
  "media": "https://…/shot.png", // optionnel
  "type": "feature", // optionnel ; "feature" | "rollup"
  "prerelease": false, // optionnel
}
  • Idempotent. Upserts sur UNIQUE(source_id, url), remplit sans écraser : en cas de collision, il ne remplit le contenu/média que lorsque la ligne stockée est vide, donc une ré-exécution n'écrase jamais une bonne ligne. Sans danger pour les ré-exécutions et les pages — la même url met à jour plutôt que de dupliquer.
  • Re-POST d'enrichissement (mode: "upsert-content"). Parce que la valeur par défaut est "remplir uniquement", re-POSTer un contenu plus riche pour une URL qui a déjà du contenu est un no-op — le stub reste. Pour un deuxième passage délibéré (d'abord les résumés d'index, puis les corps de page de détail complets + médias), ajoutez "mode": "upsert-content" au niveau supérieur du corps du batch : cela écrase le contenu/média lors d'une collision sur la même URL et saute le pré-filtre de déduplication de titre de scraping (#1526). Utilisez-le uniquement pour un enrichissement intentionnel ; le chemin par défaut reste "remplir uniquement" afin qu'une récupération de routine ne puisse pas écraser. L'url doit être identique à la ligne ensemencée — l'enrichissement s'appuie sur l'URL, donc un changement de schéma d'URL insère un doublon à la place. Si le schéma d'URL a changé, réensemencez : releases release delete --source <src_…> --hard puis un nouveau /batch (mode par défaut).
  • Auth. Nécessite la portée write ; la RELEASES_API_KEY statique (root) la satisfait. .env charge automatiquement RELEASES_API_URL + RELEASES_API_KEY dans ce dépôt.
  • Pas d'IA lors de l'insertion (voir Contrat de coûts).

Aide au découpage (lit un tableau records.json, POSTe par lots de 50) :

bun -e '
const orgSlug = process.argv[1], sourceSlug = process.argv[2], file = process.argv[3];
const all = JSON.parse(await Bun.file(file).text());
const base = process.env.RELEASES_API_URL, key = process.env.RELEASES_API_KEY;
for (let i = 0; i < all.length; i += 50) {
  const releases = all.slice(i, i + 50);
  const res = await fetch(`${base}/v1/orgs/${orgSlug}/sources/${sourceSlug}/releases/batch`, {
    method: "POST",
    headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
    body: JSON.stringify({ releases }),
  });
  console.log(`chunk ${i / 50}: ${res.status} ${(await res.text()).slice(0, 200)}`);
}
' <orgSlug> <sourceSlug> records.json

Étape 6 — Valider

releases tail <slug> --json   # ou : get_latest_releases (outil typé)

Confirmez que les publications ont des titres, dates, contenu et médias non vides. Si une ligne est vide, corrigez l'extraction et réexécutez — l'upsert par défaut remplit les lignes vides sans risque. Si une ligne a déjà un contenu fin mais non vide (par exemple un stub de résumé d'index que vous souhaitez maintenant remplacer par le corps complet), une réexécution simple est un no-op — utilisez le re-POST d'enrichissement mode: "upsert-content" de l'Étape 5.

Vous récupérez une source, pas en intégrer une nouvelle ? Cette étape confirme seulement ce qui a été écrit — elle ne dit rien sur ce qui ne l'a pas été. Avant de dire à quelqu'un qu'une source est "récupérée" ou "rien ne manque", consultez backfilling-sources → Avant de déclarer une source "propre" ou "récupérée" pour la vérification ponctuelle des pages en direct requise.

Étape 7 — Note du playbook

Enregistrez comment cette source a été ingérée afin que les futurs agents n'aient pas à le redériver. Via manage_playbook (action update_notes) ou releases admin playbook <org> --notes-file -. Notez que la source a été ingérée localement (pas encore sur le chemin cron), sa cadence observée, et toute particularité d'extraction. Suivez les règles de rédaction du playbook de managing-sources (voix impérative, "keep-test", ### Instructions de récupération / ### Pièges / ### Couverture). Ne ...

[le contenu était tronqué pour la traduction]

Skills associés