docs: apply three review suggestions, record Max's choices (#63)
All checks were successful
CI / verification (pull_request) Successful in 38s

- .claude/hooks/** joins the always-high row: a hook script runs on the
  workstation.
- Bas narrows to **/*.md and LICENSE: a script or an image added under
  docs/ or reports/ is no longer low.
- package.json: a package moved between devDependencies and dependencies,
  or any line of the dependencies block, is high; the script now tracks
  the block each package line belongs to.
- Calibration records the choices Max confirmed on 2026-10-19.

Replay unchanged: 20 haut out of 31.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
le king fu 2026-10-19 20:21:29 -04:00
parent 9547965c9d
commit 44d58970ad
2 changed files with 32 additions and 12 deletions

View file

@ -29,11 +29,11 @@ Motifs : chemins depuis la racine. `*` ne traverse jamais un `/` ; `**/` vaut z
| `.forgejo/workflows/**` | toujours : le runner `linux-builder` est un service de l'hôte de production, à portée instance, et le job `push` de `main` tourne pendant le déploiement | CI et gardes | | `.forgejo/workflows/**` | toujours : le runner `linux-builder` est un service de l'hôte de production, à portée instance, et le job `push` de `main` tourne pendant le déploiement | CI et gardes |
| `vitest.config.js`, `scripts/boot-from-copy.sh` | toujours : ils décident quels tests tournent et si le `COPY` est vérifié | CI et gardes | | `vitest.config.js`, `scripts/boot-from-copy.sh` | toujours : ils décident quels tests tournent et si le `COPY` est vérifié | CI et gardes |
| `__tests__/{auth,writes}.test.js` | le diff change ou retire une ligne existante hors commentaire ; un cas ajouté sans rien toucher d'autre reste normal | CI et gardes | | `__tests__/{auth,writes}.test.js` | le diff change ou retire une ligne existante hors commentaire ; un cas ajouté sans rien toucher d'autre reste normal | CI et gardes |
| `package.json` | le diff change autre chose que le champ `version` de l'app ou la version d'un paquet déjà listé : paquet ajouté ou retiré, bloc `dependencies` (le runtime est sans dépendance), `scripts`, `main`, `type`, `overrides` | Dépendances | | `package.json` | le diff change autre chose que le champ `version` de l'app ou la version d'un paquet déjà listé dans le même bloc : paquet ajouté, retiré ou déplacé entre `devDependencies` et `dependencies`, toute ligne du bloc `dependencies` (le runtime est sans dépendance), `scripts`, `main`, `type`, `overrides` | Dépendances |
| `package-lock.json` | le diff ajoute ou retire un paquet (une clé `"node_modules/<nom>"` qui apparaît ou disparaît), change l'origine d'un paquet (hôte ou schéma de `resolved`) ou un `integrity` sans nouvelle version | Dépendances | | `package-lock.json` | le diff ajoute ou retire un paquet (une clé `"node_modules/<nom>"` qui apparaît ou disparaît), change l'origine d'un paquet (hôte ou schéma de `resolved`) ou un `integrity` sans nouvelle version | Dépendances |
| `.gitignore` | le diff retire ou assouplit une ligne (`.env` et `.env.local` en dépendent) | Agents du poste, Dépendances | | `.gitignore` | le diff retire ou assouplit une ligne (`.env` et `.env.local` en dépendent) | Agents du poste, Dépendances |
| fichier à la racine du dépôt, hors `*.md` | ajouté, renommé ou supprimé, comme tout fichier d'un dossier caché nouveau : une configuration nouvelle peut changer le build Coolify, la CI ou le chargement de vitest sans motif qui la nomme | Image et démarrage, CI et gardes | | fichier à la racine du dépôt, hors `*.md` | ajouté, renommé ou supprimé, comme tout fichier d'un dossier caché nouveau : une configuration nouvelle peut changer le build Coolify, la CI ou le chargement de vitest sans motif qui la nomme | Image et démarrage, CI et gardes |
| `.claude/settings.json`, `.mcp.json` | toujours : hooks, permissions et serveurs MCP décident de ce que les agents exécutent sur le poste, sans CI pour le voir | CI et gardes | | `.claude/settings.json`, `.claude/hooks/**`, `.mcp.json` | toujours : hooks et leurs scripts, permissions et serveurs MCP décident de ce que les agents exécutent sur le poste, sans CI pour le voir | CI et gardes |
| `REVIEW.md` | le diff touche la section « Niveaux » | Règles de revue | | `REVIEW.md` | le diff touche la section « Niveaux » | Règles de revue |
Restent au moins normaux, et leurs règles de zone s'appliquent quand même : Restent au moins normaux, et leurs règles de zone s'appliquent quand même :
@ -47,7 +47,7 @@ Restent au moins normaux, et leurs règles de zone s'appliquent quand même :
### Bas ### Bas
Une PR est de niveau bas quand tous ses fichiers sont désignés par `**/*.md`, `docs/**`, `LICENSE` ou `reports/**`, hors des exceptions de la liste précédente. Exemples : `README.md`, `STATE.md`, `spec-*.md`. Une PR est de niveau bas quand tous ses fichiers sont désignés par `**/*.md` (ceux de `docs/` et de `reports/` compris) ou `LICENSE`, hors des exceptions de la liste précédente. Un fichier d'un autre type sous `docs/` ou `reports/` (un script, une image) reste normal. Exemples : `README.md`, `STATE.md`, `spec-*.md`.
### Normal ### Normal
@ -66,6 +66,8 @@ Niveau haut sur les 31 : routage et authentification (#9, #20, #28, #30, #52, #5
L'écart à la cible de 20 % du modèle de simpl-resultat est voulu et justifié dans la PR qui ferme #63 : l'historique du dépôt est celui de la construction de ses surfaces sensibles (quatre tokens, trois routes d'écriture, deux agents qui tiennent un secret ou la copie hors site), chacune livrée par la PR qui l'a créée, et le merge part en production sans attendre la CI. Les PR qui ne font qu'étendre une surface existante, une route de lecture ou un test restent normales. Le script qui calcule ces niveaux est `scripts/review-levels.py` (API Forgejo, lecture seule, plus le clone local pour trois PR au diff vide ; `python3 scripts/review-levels.py 60 62` pour quelques PR) ; `python3 scripts/review-levels.py --check` vérifie, sans réseau, que ses motifs sont ceux du tableau Haut. Il n'approche qu'une partie des conditions et ne sert qu'à la calibration. L'écart à la cible de 20 % du modèle de simpl-resultat est voulu et justifié dans la PR qui ferme #63 : l'historique du dépôt est celui de la construction de ses surfaces sensibles (quatre tokens, trois routes d'écriture, deux agents qui tiennent un secret ou la copie hors site), chacune livrée par la PR qui l'a créée, et le merge part en production sans attendre la CI. Les PR qui ne font qu'étendre une surface existante, une route de lecture ou un test restent normales. Le script qui calcule ces niveaux est `scripts/review-levels.py` (API Forgejo, lecture seule, plus le clone local pour trois PR au diff vide ; `python3 scripts/review-levels.py 60 62` pour quelques PR) ; `python3 scripts/review-levels.py --check` vérifie, sans réseau, que ses motifs sont ceux du tableau Haut. Il n'approche qu'une partie des conditions et ne sert qu'à la calibration.
Choix confirmés par Max le 2026-10-19, sur la PR qui ferme #63 : le taux de 65 % est gardé ; une montée de version d'un paquet déjà présent reste normale ; les agents du poste listés au tableau restent toujours hauts, même si leur merge ne déploie rien ; la liste des fichiers jamais bas est celle ci-dessus ; les gardes `auth.test.js` et `writes.test.js` sont hautes dès qu'une ligne existante change ; les nettoyeurs restent normaux, élargir une liste fermée relevant de la règle de zone. Tout écart à ces choix se demande à Max.
Une PR qui modifie cette section dit, pour chaque motif retiré ou assoupli, ce qu'il protégeait, met `scripts/review-levels.py` à jour dans le même diff et le rejoue sur les mêmes PR. Une PR qui modifie cette section dit, pour chaque motif retiré ou assoupli, ce qu'il protégeait, met `scripts/review-levels.py` à jour dans le même diff et le rejoue sur les mêmes PR.
## Ce qui compte par zone ## Ce qui compte par zone

View file

@ -55,11 +55,11 @@ HIGH_ALWAYS = [
"Dockerfile", "Dockerfile",
".forgejo/workflows/**", ".forgejo/workflows/**",
"vitest.config.js", "scripts/boot-from-copy.sh", "vitest.config.js", "scripts/boot-from-copy.sh",
".claude/settings.json", ".mcp.json", ".claude/settings.json", ".claude/hooks/**", ".mcp.json",
] ]
SHIPPED = ["{index,metrics,claude-usage,claude-usage-history,backups}.js"] SHIPPED = ["{index,metrics,claude-usage,claude-usage-history,backups}.js"]
GUARDS = ["__tests__/{auth,writes}.test.js"] GUARDS = ["__tests__/{auth,writes}.test.js"]
LOW = ["**/*.md", "docs/**", "LICENSE", "reports/**"] LOW = ["**/*.md", "LICENSE"]
NEVER_LOW = ["CLAUDE.md", "REVIEW.md", "agent/README.md", ".claude/**"] NEVER_LOW = ["CLAUDE.md", "REVIEW.md", "agent/README.md", ".claude/**"]
AUTH = re.compile(r"\b(ROUTES|TOKEN_KINDS|resolveRoute|checkAuth|tokenMatches|logRejection|" AUTH = re.compile(r"\b(ROUTES|TOKEN_KINDS|resolveRoute|checkAuth|tokenMatches|logRejection|"
@ -147,15 +147,33 @@ def touches(block, pattern):
def package_json_changes(block): def package_json_changes(block):
# any key/value change except "version" and a bump of a package already listed # anything but "version" and a bump of a package listed in the same block;
# any line of "dependencies" counts, since the runtime has none
before, after = {}, {} before, after = {}, {}
for l in changed(block): for hunk in re.split(r"(?m)^@@", block)[1:]:
m = re.match(r'^([+-])\s*"([^"]+)":\s*(.*?),?\s*$', l) section = None
if not m: for l in hunk.split("\n")[1:]:
return True if l.startswith(("+++", "---")) or l[:1] not in " +-":
(before if m.group(1) == "-" else after)[m.group(2)] = m.group(3) continue
m = re.match(r'^[ +-]\s*"([^"]+)":\s*\{', l)
if m:
section = m.group(1)
if l[:1] == " " or not meaningful(l[1:]):
if re.match(r"^[ +-]\s*\}", l):
section = None
continue
if section == "dependencies":
return True
m = re.match(r'^([+-])\s*"([^"]+)":\s*(.*?),?\s*$', l)
if not m:
return True
if m.group(3) == "{":
continue
(before if m.group(1) == "-" else after)[(section, m.group(2))] = m.group(3)
if re.match(r"^[+-]\s*\}", l):
section = None
for key in set(before) | set(after): for key in set(before) | set(after):
if key == "version": if key[1] == "version" and key[0] is None:
continue continue
if key in before and key in after and all(re.match(r'^"[\^~]?\d', v) for v in (before[key], after[key])): if key in before and key in after and all(re.match(r'^"[\^~]?\d', v) for v in (before[key], after[key])):
continue continue