Capability : git

Implémentez l’hébergement git — les credentials avec lesquels une sandbox clone, les sélecteurs de dépôt, les push au nom du membre, et le statut de la pull request à côté d’une tâche.

git, c’est là où vivent les dépôts d’un projet. Elle est résolue par dépôt : un projet peut mélanger deux forges, et c’est le propriétaire de chaque dépôt (acme dans acme/api) qui décide quelle installation répond.

Seule cloneCredential est obligatoire. Une forge qui sait cloner et rien d’autre est une app git complète ; chaque autre opération débloque une chose de plus dans le dashboard.

OpérationObligatoireDébloqueAgit au nom de
cloneCredentialOuiLes sandboxes et les tâches clonent des dépôts privésL’organisation
listReposLe sélecteur de dépôt dans le formulaire du projetL’organisation
listBranchesChoisir une branche, connaître celle par défautL’organisation
userCredentialLes push depuis une sandbox attribués au membre qui la possèdeLe membre — exige un grant
registerUserSshKeyEnregistrer la clé SSH d’un membre sur son propre compteLe membre — exige un grant
pullRequestForBranchLa carte de la pull request à côté du diff d’une tâcheL’organisation

Les dépôts sont toujours nommés owner/name.

Une sandbox clone au nom de l’organisation : elle récupère donc son code même si le membre qui l’a démarrée n’a jamais rien connecté. Tout ce qui suit — fetch, push — se fait au nom du membre qui possède la sandbox : git interroge un credential helper dans la sandbox, qui interroge Spunto, qui appelle votre userCredential avec le userId de ce membre. Un push qui porte le nom de quelqu’un doit venir de sa propre autorisation ; un credential d’installation ne peut pas en tenir lieu.

C’est pourquoi userCredential et registerUserSshKey vont dans les userGrantOperations de votre manifeste, et échouent avec grant_required quand le membre n’a pas autorisé votre app.

Un credential de courte durée avec lequel une sandbox clone. Appelée une fois par propriétaire de dépôt à la mise en place d’une sandbox — le credential est réutilisé pour tous les dépôts de ce propriétaire dans le projet.

// input
{ "repo": "acme/api", "repos": ["acme/api", "acme/web"] }
 
// result
{ "scheme": "https-token", "username": "x-access-token", "token": "…", "expiresInSeconds": 3600 }
  • repos liste tous les dépôts du projet qui ont le même propriétaire. Restreignez le token à ceux-là, en lecture seule, si votre forge le permet : il finit dans le setup de la sandbox, lisible par quiconque peut ouvrir cette sandbox.
  • Si certains sont hors de portée de l’installation, émettez-en un pour ceux qui sont accessibles plutôt que de tous les faire échouer ; n’échouez avec not_found que si aucun ne l’est.
  • scheme vaut https-token — git s’authentifie avec username et token en HTTPS.

// input
{}
 
// result
{ "repos": [{ "fullName": "acme/api", "name": "api", "private": true, "defaultBranch": "main" }] }

Tous les dépôts que l’installation de l’organisation peut atteindre. Si elle a plusieurs installations et que l’une est injoignable, renvoyez les autres.

// input
{ "repo": "acme/api" }
 
// result
{ "branches": ["main", "feat/login"], "defaultBranch": "main" }

// input — userId in the envelope is the member to act as
{ "repo": "acme/api" }
 
// result
{ "username": "x-oauth-token", "password": "…", "expiresAt": "2026-10-28T15:00:00Z" }

Remis à git au moment où il le demande, jamais écrit sur le disque. expiresAt peut valoir null si le credential n’expire pas. Rafraîchissez vous-même l’autorisation du membre si les tokens de votre forge expirent ; si elle ne peut pas être rafraîchie, échouez avec grant_required pour qu’on lui demande d’autoriser à nouveau.

// input
{ "publicKey": "ssh-ed25519 AAAA…", "title": "Spunto" }
 
// result
{ "keyId": 81723 }

Enregistre la clé sur le propre compte du membre. Un nouvel appel doit remplacer la clé enregistrée précédemment, pas en ajouter une de plus.

Ce que regarde d’un coup d’œil la personne qui relit avant d’ouvrir la pull request — affiché à côté du diff d’une tâche.

// input
{ "repo": "acme/api", "branch": "task/fix-login-a1b2c3" }
 
// result
{
  "pullRequest": {
    "label": "#262",
    "url": "https://git.acme.dev/acme/api/pulls/262",
    "title": "Fix the login redirect",
    "state": "open",
    "branch": "task/fix-login-a1b2c3",
    "baseBranch": "main",
    "checks": "passing",
    "review": "pending",
    "comments": 3,
    "updatedAt": "2026-10-28T14:02:11Z"
  }
}

pullRequest vaut null quand rien n’est ouvert pour la branche. state vaut draft, open, merged ou closed ; checks, pending, passing ou failing ; review, approved, changes-requested ou pending. Chaque champ après state peut valoir null, et null signifie votre forge ne le dit pas — une forge sans CI renvoie "checks": null, que le dashboard affiche autrement que des checks qui n’ont pas encore tourné.

Elle rend compte, elle ne décide de rien

Ce que renvoie cette opération est affiché, jamais suivi d’effet. L’état d’une tâche vient de la session d’agent que Spunto a lancée et du Accept ou du Drop du membre — pas d’un merge sur votre forge. Une forge capable de clore une tâche pourrait clore un travail que personne n’a relu.