runward

RW™ · V0.29.0

Docs · Opérer · Le territoire des règles

Le territoire des règles.

64 règles, un diff de trois fichiers : lesquelles vous concernent ? `runward rules --for` répond par chemin, en citant la déclaration qui a mordu. Trois états déclarés, jamais le silence.

Le problème : 64 règles, un diff de trois fichiers

Le catalogue compte 64 règles de craft. Votre changement en touche trois fichiers. Lesquelles vous concernent ?

Sans réponse à cette question, il ne reste que deux comportements, tous les deux mauvais. Relire les 64 à chaque fois, ce que personne ne tient. Ou n'en relire aucune et se fier au vert de la porte, ce qui confond « la preuve tient » avec « le code est bon ». La porte vérifie qu'une décision est tracée vers une preuve qui résout. Elle n'a jamais prétendu lire votre code.

runward rules --for src/entry.serve.ts src/db/migrations/003_add_index.sql

La commande répond, par chemin, quelles règles ont ce fichier dans leur territoire, et pourquoi.

Trois états déclarés, jamais le silence

Une règle se trouve toujours dans l'un des trois états suivants. Le troisième est celui qui compte.

  • Territoire déclaré : la règle nomme les chemins qu'elle gouverne, par des globs (appliesTo) ou par une catégorie qu'une mission relie à ses fichiers (governs). 14 règles sur 64.
  • Sans territoire, déclaré comme tel : la règle affirme ouvertement qu'elle n'a pas de territoire de fichiers, et dit pourquoi. Une checklist de pré-production garde le système entier, pas une classe de fichiers. 50 règles sur 64.
  • Non revue : personne n'a encore tranché. Aujourd'hui : zéro.

Le point est là. Ne rien écrire n'est pas la même chose que déclarer qu'il n'y a rien à écrire. Un champ vide se lit « on ne sait pas », « on n'a pas eu le temps », ou « il n'y a rien » : les trois à la fois, donc rien. C'est pour cette raison que le catalogue affiche les trois états comme trois choses distinctes, et que le compteur de règles non revues est publié même quand il vaut zéro. Le jour où il ne vaudra plus zéro, ça se verra.

D'où vient un match, et pourquoi il est rendu

runward ne devine jamais à partir du contenu de vos fichiers. Un match vient d'une déclaration, et la déclaration est citée.

1. La règle elle-même. Elle porte ses globs. Un match affiche le glob qui a mordu, sur le modèle de git check-ignore -v : pas « ce fichier est ignoré », mais « cette ligne-là de ce fichier-là l'ignore ».

2. La dérivation depuis un manifeste de déploiement. Un wrangler.jsonc, wrangler.json ou wrangler.toml déclare une topologie d'exécution : un module d'entrée, un cron, un consommateur de file. runward la lit et en dérive une catégorie.

async-job-guardrails   HIGH   governs=background-work ← wrangler.serve.jsonc triggers.crons

C'est un manifeste que vous avez écrit, pas votre code. Un framework inconnu ne produit aucune dérivation, jamais une supposition.

3. La carte de mission. Parce qu'un manifeste de déploiement décrit une topologie et jamais la nature du code derrière. Un module d'entrée qui dérive un jeton depuis un secret d'environnement : aucun wrangler.toml ne peut le dire. Vous, si.

runward/territory.md

Quatre colonnes, dans une section ## Territory (n'importe quel niveau de titre convient).

## Territory

| Pattern | Category | Effect | Why |
|---|---|---|---|
| `src/entry.*.ts` | `secret-boundary` | declare | dérive un jeton depuis un secret d'environnement |
| `src/entry.browser.ts` | `background-work` | remove | le bundle navigateur du framework ne fait rien en tâche de fond |

La précédence est nommée en trois endroits plutôt que devinée : la dernière ligne qui matche gagne, par couple (chemin, catégorie) ; et l'ordre est dérivation < carte < appliesTo d'une règle, que la carte ne peut jamais rétrécir. La carte corrige ce que runward a supposé ; jamais ce que le mainteneur a décidé.

remove est une colonne et non un préfixe !, pour une raison précise : un caractère qui inverse le sens d'une ligne disparaît dans un diff.

Chaque ligne refusée est nommée avec son numéro de ligne. Une catégorie inventée, un why vide, un chemin qui sort du projet : c'est reporté, jamais écarté en silence. Une ligne que l'opérateur croit active et que runward a ignorée est le pire état possible. Et si le fichier entier est illisible, la sortie le dit en tête, avant tout le reste :

This map was not read
  ✗ runward/territory.md: no `Territory` heading — the map needs a `## Territory` section (any heading level) above its table
  Everything below is derivation only — your declarations had no effect.

runward n'écrit jamais cette carte. init ne la crée pas, update ne la rafraîchit pas. Un fichier que l'outil réécrit est un fichier dont l'opérateur cesse d'être propriétaire, et c'est cette propriété qui rend une ligne périmée visible dans un diff.

La carte pourrit : la mesure est dans status

Une carte tenue dans un dépôt se périme. C'est documenté, pas supposé : une étude FSE 2025 sur les suppressions d'alertes statiques mesure 50,8 % de suppressions inertes, une croissance monotone, et des suppressions périmées qui masquent les alertes futures.

runward status affiche donc la couverture, sans rien écrire :

Territory coverage
  1 of 109 walked file(s) carry a category · background-work 1 · secret-boundary 1
  runward/territory.md: 1 row(s) declared.
  ! 1 row(s) matched no walked file:
      territory.md:7  src/legacy/*.ts → startup
  A row that affects nothing today. Dead or merely early is your call, not the tool's.

Une ligne qui ne touche aucun fichier est signalée, avec son numéro de ligne. runward ne tranche pas entre « morte » et « simplement en avance » : ce jugement vous appartient. Une carte d'une ligne qui décrit une réalité vaut mieux qu'une carte de trente qui décrit une intention.

Ce que ça ne fait pas

  • Ça ne lit jamais le code de votre projet. Uniquement des déclarations : les globs d'une règle, un manifeste de déploiement que vous avez écrit, votre carte.
  • Ça ne change pas la porte. runward check --strict rend exactement le même verdict qu'avant. Le territoire sert à savoir quoi confronter au moment d'agir, pas à franchir une étape.
  • Une catégorie inconnue n'est pas une supposition. Le vocabulaire est fermé (huit catégories). Une règle qui gouverne une catégorie qu'aucune déclaration ne relie à vos fichiers ne remonte pas, et elle n'est pas non plus comptée comme évaluée : runward rules --for --json la range sous unresolved, distincte des règles réellement écartées. La différence entre « ne s'applique pas » et « impossible de trancher » est tenue, pas lissée.
← Docs