« Demander à une IA » : bouton, feuille du prompt à son adresse, copie
Le bouton, secondaire, dans la feuille du maître, sur le bilan, dans l'analyse et sur la fiche ouverte du glossaire. Il ouvre la feuille du prompt (…/ia, amendement de l'ADR 0013), où le texte se relit avant la copie : navigator.clipboard, repli par sélection du texte, bandeau « Prompt copié » (2 s). Gabarits copiés par make site (amendement de l'ADR 0008), scénario « Demander à une IA », 1f marquée faite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4f0611c24c
commit
2a1fe1b04c
+13
-5
@@ -11,10 +11,11 @@
|
||||
# Le conseil du maître (tests/browser/maitre.mjs), captures dans
|
||||
# build/browser-shots/maitre/. Le bilan de fin de partie et l'écran
|
||||
# d'analyse (tests/browser/bilan.mjs), captures dans
|
||||
# build/browser-shots/bilan/.
|
||||
# build/browser-shots/bilan/. « Demander à une IA » (tests/browser/prompts.mjs),
|
||||
# captures dans build/browser-shots/prompts/.
|
||||
#
|
||||
# BROWSERS=chromium et SCRIPTS=accueil (ou play, match, maitre, bilan)
|
||||
# restreignent le test.
|
||||
# BROWSERS=chromium et SCRIPTS=accueil (ou play, match, maitre, bilan,
|
||||
# prompts) restreignent le test.
|
||||
#
|
||||
# Outils : l'image de Playwright (navigateurs) et le paquet playwright,
|
||||
# installé une fois par npm dans build/browser-tools/ (réseau nécessaire la
|
||||
@@ -27,7 +28,7 @@ cd "$(dirname "$0")/.."
|
||||
image="${PLAYWRIGHT_IMAGE:?lancer via make test-browser}"
|
||||
browsers="${BROWSERS:-chromium firefox}"
|
||||
# Les scripts à lancer (tests/browser/<nom>.mjs), tous par défaut.
|
||||
scripts="${SCRIPTS:-play accueil match maitre bilan}"
|
||||
scripts="${SCRIPTS:-play accueil match maitre bilan prompts}"
|
||||
|
||||
unset MAKEFLAGS MFLAGS MAKELEVEL
|
||||
make -s package >/dev/null
|
||||
@@ -65,7 +66,7 @@ update_port="$(docker port "$update_container" 80/tcp | head -n1 | sed 's/.*://'
|
||||
rm -rf build/browser-shots
|
||||
mkdir -p build/browser-shots
|
||||
cp tests/browser/play.mjs tests/browser/accueil.mjs tests/browser/match.mjs tests/browser/recette.mjs \
|
||||
tests/browser/maitre.mjs tests/browser/bilan.mjs "$tools/"
|
||||
tests/browser/maitre.mjs tests/browser/bilan.mjs tests/browser/prompts.mjs "$tools/"
|
||||
status=0
|
||||
for browser in $browsers; do
|
||||
# Réseau de l'hôte : le site est sur 127.0.0.1. Le service worker
|
||||
@@ -105,5 +106,12 @@ for browser in $browsers; do
|
||||
-v "$PWD/$tools:/tools" -v "$PWD/build/browser-shots:/shots" -w /tools "$image" \
|
||||
node bilan.mjs "http://localhost:$port/" "$browser" /shots || status=1
|
||||
fi
|
||||
if [[ " $scripts " == *" prompts "* ]]; then
|
||||
# « Demander à une IA » : boutons, feuille du prompt, copie (relue
|
||||
# dans Chromium) ; captures dans build/browser-shots/prompts/.
|
||||
docker run --rm --init --ipc=host --network host --user "$(id -u):$(id -g)" -e HOME=/tmp \
|
||||
-v "$PWD/$tools:/tools" -v "$PWD/build/browser-shots:/shots" -w /tools "$image" \
|
||||
node prompts.mjs "http://localhost:$port/" "$browser" /shots || status=1
|
||||
fi
|
||||
done
|
||||
exit "$status"
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
// prompts.mjs -- « Demander à une IA » (étape 1f) par l'interface, dans un
|
||||
// vrai navigateur sans écran (Playwright), sur le site servi par l'image de
|
||||
// production (charte, ecrans-etape-1.md, « 1f. Demander à une IA »).
|
||||
//
|
||||
// Copyright (C) 2026 les auteurs d'Ustagammon.
|
||||
// Licence : GPLv3 ou ultérieure (voir LICENSE).
|
||||
//
|
||||
// Usage : node prompts.mjs <adresse du site> <chromium|firefox> <dossier des captures>
|
||||
// Lancé par tests/browser.sh. La fin de course courte de bilan.mjs : le
|
||||
// joueur a 6-1, deux pions sur 6 et 1, et joue 6/5/off (grosse erreur) ;
|
||||
// l'ordinateur double, le joueur passe. Vérifie le bouton « Demander à une
|
||||
// IA » dans la feuille du maître, sur le bilan, dans l'analyse et sur une
|
||||
// fiche du glossaire ; la feuille du prompt à son adresse (…/ia), lisible
|
||||
// avant la copie, rechargeable ; la copie (dans Chromium, le presse-papiers
|
||||
// est relu et comparé au texte de la feuille, et aux chiffres de la feuille
|
||||
// du maître) ; le bandeau « Prompt copié » ; la fermeture qui ramène à
|
||||
// l'écran d'en dessous ; aucune requête vers un autre site ; l'absence
|
||||
// d'erreur et de violation de la politique de sécurité. Captures (Chromium)
|
||||
// dans <dossier des captures>/prompts/, en 412×915 et 915×412.
|
||||
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { chromium, firefox } from "playwright";
|
||||
import { hiddenUnderActionBar, tallButtons } from "./recette.mjs";
|
||||
|
||||
const [base, browserName, shots] = process.argv.slice(2);
|
||||
const browserType = { chromium, firefox }[browserName];
|
||||
if (!base || !browserType || !shots) {
|
||||
console.error("usage : node prompts.mjs <adresse> <chromium|firefox> <dossier des captures>");
|
||||
process.exit(2);
|
||||
}
|
||||
const full = browserName === "chromium";
|
||||
const dir = `${shots}/prompts`;
|
||||
mkdirSync(dir, { recursive: true });
|
||||
const PORTRAIT = { width: 412, height: 915 };
|
||||
const LANDSCAPE = { width: 915, height: 412 };
|
||||
const SIZES = [PORTRAIT, LANDSCAPE];
|
||||
const BEAROFF = "BQAACAIAAAAAAA";
|
||||
|
||||
const problems = [];
|
||||
const log = (text) => console.log(`[${browserName}] ${text}`);
|
||||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
let captures = 0;
|
||||
|
||||
function check(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
const browser = await browserType.launch();
|
||||
const context = await browser.newContext({
|
||||
viewport: PORTRAIT,
|
||||
deviceScaleFactor: 1,
|
||||
locale: "fr-FR",
|
||||
reducedMotion: "reduce",
|
||||
});
|
||||
if (full) {
|
||||
// Chromium : le test relit le presse-papiers.
|
||||
await context.grantPermissions(["clipboard-read", "clipboard-write"], {
|
||||
origin: new URL(base).origin,
|
||||
});
|
||||
}
|
||||
await context.addInitScript(() => {
|
||||
window.__dice = [];
|
||||
let state = 5;
|
||||
Math.random = () => {
|
||||
const die = window.__dice.shift();
|
||||
if (die !== undefined) {
|
||||
return (die - 0.5) / 6;
|
||||
}
|
||||
state = (state * 1103515245 + 12345) % 2147483648;
|
||||
return state / 2147483648;
|
||||
};
|
||||
window.__csp = [];
|
||||
document.addEventListener("securitypolicyviolation", (e) =>
|
||||
window.__csp.push(`${e.effectiveDirective} ${e.blockedURI}`),
|
||||
);
|
||||
});
|
||||
const page = await context.newPage();
|
||||
page.on("pageerror", (error) => problems.push(`erreur de la page : ${error.message}`));
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
problems.push(`console : ${message.text()}`);
|
||||
}
|
||||
});
|
||||
// Aucun appel réseau hors du site : l'application n'appelle aucune IA.
|
||||
const origin = new URL(base).origin;
|
||||
page.on("request", (request) => {
|
||||
const url = request.url();
|
||||
if (!url.startsWith(origin) && !url.startsWith("data:") && !url.startsWith("blob:")) {
|
||||
problems.push(`requête hors du site : ${url}`);
|
||||
}
|
||||
});
|
||||
|
||||
/** Attend une condition dans la page (sans page.waitForFunction, qui passe
|
||||
* par eval, interdit par la politique de sécurité du site). */
|
||||
async function until(what, fn, arg, ms = 120_000) {
|
||||
for (let waited = 0; waited < ms; waited += 100) {
|
||||
if (await page.evaluate(fn, arg)) {
|
||||
return;
|
||||
}
|
||||
await sleep(100);
|
||||
}
|
||||
await page.screenshot({ path: `${dir}/${browserName}-bloque.png` }).catch(() => {});
|
||||
throw new Error(`attente trop longue : ${what}`);
|
||||
}
|
||||
|
||||
const idle = () => until("écran libre", () => !document.body.classList.contains("busy"));
|
||||
|
||||
/** Une capture de la recette dans les deux tailles (Chromium seulement),
|
||||
* après la règle commune : rien d'interactif sous une barre d'action, pas
|
||||
* de bouton étiré. before() est rejoué avant chaque taille (le bandeau
|
||||
* « Prompt copié » ne dure que 2 s). */
|
||||
async function capture(name, before = async () => {}) {
|
||||
if (!full) {
|
||||
return;
|
||||
}
|
||||
for (const size of SIZES) {
|
||||
await page.setViewportSize(size);
|
||||
await sleep(300);
|
||||
await before();
|
||||
const hidden = await hiddenUnderActionBar(page);
|
||||
check(
|
||||
hidden.length === 0,
|
||||
`${name} ${size.width}x${size.height} : caché sous la barre d'action : ${hidden.join(", ")}`,
|
||||
);
|
||||
const tall = await tallButtons(page);
|
||||
check(tall.length === 0, `${name} ${size.width}x${size.height} : boutons étirés : ${tall.join(", ")}`);
|
||||
await page.screenshot({ path: `${dir}/${name}-${size.width}x${size.height}.png` });
|
||||
captures++;
|
||||
}
|
||||
await page.setViewportSize(PORTRAIT);
|
||||
await sleep(300);
|
||||
}
|
||||
|
||||
/** La feuille du prompt, prête : son texte. */
|
||||
async function openPrompt(button, hashEnd) {
|
||||
await page.locator(button).scrollIntoViewIfNeeded();
|
||||
await page.click(button);
|
||||
await until(
|
||||
"feuille du prompt prête",
|
||||
(end) =>
|
||||
location.hash.endsWith(end) &&
|
||||
!document.getElementById("prompt-veil").hidden &&
|
||||
document.getElementById("prompt-sheet")?.dataset.ready === "true",
|
||||
hashEnd,
|
||||
);
|
||||
return page.textContent("#prompt-text");
|
||||
}
|
||||
|
||||
/** Ce que tout prompt doit contenir (cadrage, « IA générative en V1 »). */
|
||||
function checkPrompt(text, what) {
|
||||
for (const part of [
|
||||
"Tu es un coach de backgammon",
|
||||
"Je suis un joueur de niveau ",
|
||||
"Position ID : ",
|
||||
"Match ID : ",
|
||||
"https://ustagammon.sans.pub/#/position/",
|
||||
"X : mes pions",
|
||||
"GNU Backgammon (analyse à 2 plis)",
|
||||
"ne les contredis pas",
|
||||
"Réponds en français",
|
||||
]) {
|
||||
check(text.includes(part), `${what} : « ${part} » absent`);
|
||||
}
|
||||
check(!/\{[a-z_]+\}/.test(text) && !text.includes("undefined"), `${what} : repère vide`);
|
||||
}
|
||||
|
||||
/** Copie le prompt, attend le bandeau, et (Chromium) relit le
|
||||
* presse-papiers. */
|
||||
async function copyPrompt(what) {
|
||||
await page.click("#prompt-copy");
|
||||
await until(
|
||||
"copie faite",
|
||||
() =>
|
||||
!document.getElementById("prompt-banner").hidden ||
|
||||
document.getElementById("prompt-status").textContent !== "",
|
||||
null,
|
||||
10_000,
|
||||
);
|
||||
const shown = await page.evaluate(() => ({
|
||||
banner: !document.getElementById("prompt-banner").hidden,
|
||||
bannerText: document.getElementById("prompt-banner").textContent,
|
||||
status: document.getElementById("prompt-status").textContent,
|
||||
text: document.getElementById("prompt-text").textContent,
|
||||
}));
|
||||
if (full) {
|
||||
check(shown.banner && shown.bannerText.includes("Prompt copié"), `${what} : pas de bandeau`);
|
||||
const clipboard = await page.evaluate(() => navigator.clipboard.readText());
|
||||
check(clipboard === shown.text, `${what} : le presse-papiers diffère du texte affiché`);
|
||||
return clipboard;
|
||||
}
|
||||
// Firefox : la copie, ou son repli (texte sélectionné, et un message).
|
||||
check(shown.banner || shown.status.includes("sélectionné"), `${what} : ni copie ni repli`);
|
||||
log(`${what} : ${shown.banner ? "copié" : "repli par sélection"}`);
|
||||
return shown.text;
|
||||
}
|
||||
|
||||
/** Ferme la feuille du prompt : l'écran d'en dessous revient. */
|
||||
async function closePrompt(hashBelow) {
|
||||
await page.click("#prompt-done");
|
||||
await until(
|
||||
"feuille du prompt fermée",
|
||||
(below) => document.getElementById("prompt-veil").hidden && location.hash === below,
|
||||
hashBelow,
|
||||
);
|
||||
}
|
||||
|
||||
// --------------------------------------- Une erreur, et son explication
|
||||
|
||||
await page.goto(`${base}#/position/${encodeURIComponent(BEAROFF)}`);
|
||||
await until("position lue", () => {
|
||||
const play = document.getElementById("position-play");
|
||||
return !play.hidden && !play.disabled;
|
||||
});
|
||||
await page.click("#position-play");
|
||||
await until(
|
||||
"partie commencée",
|
||||
() => location.hash === "#/" && !document.body.classList.contains("busy") &&
|
||||
!document.getElementById("roll").hidden,
|
||||
);
|
||||
await page.evaluate(() => window.__dice.push(6, 1));
|
||||
await page.click("#roll");
|
||||
await idle();
|
||||
for (const [from, to] of [[6, 5], [5, 0]]) {
|
||||
await page.click(`#board .hit[data-place="p${from}"]`);
|
||||
await idle();
|
||||
const target = to === 0 ? "off-player" : `p${to}`;
|
||||
const land = page.locator(`#board .hit.can-land[data-place="${target}"]`);
|
||||
if ((await land.count()) > 0) {
|
||||
await land.click();
|
||||
await idle();
|
||||
}
|
||||
}
|
||||
await page.click("#confirm");
|
||||
await until("double de l'ordinateur", () => !document.getElementById("drop").hidden);
|
||||
await until("ligne du maître", () => !document.getElementById("advice-line").hidden);
|
||||
await page.click("#advice-line");
|
||||
await until("feuille du maître", () => document.getElementById("explain-ask-ai") !== null);
|
||||
const explainHash = await page.evaluate(() => location.hash);
|
||||
// Les écarts de « Voir les chiffres du moteur », pour les comparer au prompt.
|
||||
await page.click("#explain-numbers summary");
|
||||
const sheetNumbers = await page.evaluate(() =>
|
||||
[...document.querySelectorAll("#explain-numbers .explain-table span")].map((s) => s.textContent),
|
||||
);
|
||||
await capture("1-bouton-feuille-du-maitre", () =>
|
||||
page.locator("#explain-ask-ai").scrollIntoViewIfNeeded(),
|
||||
);
|
||||
const moveText = await openPrompt("#explain-ask-ai", "/ia");
|
||||
check((await page.evaluate(() => location.hash)) === `${explainHash}/ia`, "adresse de la feuille");
|
||||
checkPrompt(moveText, "prompt du coup");
|
||||
check(moveText.includes("J'ai joué 6/off : "), "prompt du coup : coup joué");
|
||||
check(moveText.includes("1. 6/off 1/off : équité"), "prompt du coup : meilleur coup");
|
||||
check(moveText.includes("« Grosse erreur »"), "prompt du coup : appréciation");
|
||||
// Chaque ligne du classement de la feuille (rang, coup, écart) est dans le
|
||||
// prompt, avec le même écart.
|
||||
for (let i = 0; i + 2 < sheetNumbers.length; i += 3) {
|
||||
const [rank, move, loss] = sheetNumbers.slice(i, i + 3);
|
||||
const notation = move.replace(" (vous)", "");
|
||||
if (Number(rank) <= 3 || move.includes("(vous)")) {
|
||||
check(
|
||||
moveText.includes(`${rank}. ${notation}`) && moveText.includes(`écart ${loss}`),
|
||||
`prompt du coup : ligne ${rank} ${notation} ${loss} absente`,
|
||||
);
|
||||
}
|
||||
}
|
||||
await capture("2-feuille-du-prompt");
|
||||
const copied = await copyPrompt("prompt du coup");
|
||||
await capture("3-prompt-copie", async () => {
|
||||
await page.click("#prompt-copy");
|
||||
await until("bandeau", () => !document.getElementById("prompt-banner").hidden);
|
||||
});
|
||||
log(`prompt du coup copié (${copied.length} caractères)`);
|
||||
// Échap ferme la feuille du prompt, pas l'explication dessous.
|
||||
await page.keyboard.press("Escape");
|
||||
await until(
|
||||
"retour à l'explication",
|
||||
(below) =>
|
||||
document.getElementById("prompt-veil").hidden &&
|
||||
!document.getElementById("explain-veil").hidden &&
|
||||
location.hash === below,
|
||||
explainHash,
|
||||
);
|
||||
// Rechargée, l'adresse de la feuille la rouvre.
|
||||
await page.goto(`${base}${explainHash}/ia`);
|
||||
await page.reload();
|
||||
await until(
|
||||
"feuille rouverte au rechargement",
|
||||
() => document.getElementById("prompt-sheet")?.dataset.ready === "true",
|
||||
);
|
||||
check((await page.textContent("#prompt-text")) === moveText, "le prompt rechargé diffère");
|
||||
await closePrompt(explainHash);
|
||||
await page.click("#explain-done");
|
||||
await until("feuille du maître fermée", () => document.getElementById("explain-veil").hidden);
|
||||
|
||||
// ------------------------------------------------------------- Le bilan
|
||||
|
||||
await page.click("#drop");
|
||||
await until(
|
||||
"bilan prêt",
|
||||
() =>
|
||||
/^#\/parties\/[a-z0-9-]+\/bilan$/.test(location.hash) &&
|
||||
document.getElementById("summary")?.dataset.ready === "true" &&
|
||||
document.getElementById("summary-ask-ai") !== null,
|
||||
);
|
||||
await until("mini-plateau du coup à revoir", () =>
|
||||
document.querySelector("#summary-board .board-frame") !== null,
|
||||
);
|
||||
const summaryHash = await page.evaluate(() => location.hash);
|
||||
const id = summaryHash.split("/")[2];
|
||||
await capture("4-bouton-bilan", () => page.locator("#summary-ask-ai").scrollIntoViewIfNeeded());
|
||||
const summaryText = await openPrompt("#summary-ask-ai", "/bilan/ia");
|
||||
checkPrompt(summaryText, "prompt du bilan");
|
||||
check(summaryText.includes("Résultat : l'ordinateur a gagné"), "prompt du bilan : résultat");
|
||||
check(
|
||||
summaryText.includes("j'ai joué 6/off, GNU Backgammon préférait 6/off 1/off"),
|
||||
"prompt du bilan : erreur",
|
||||
);
|
||||
check(summaryText.includes("Le coup à revoir, ma plus grosse erreur (coup 1)"), "coup à revoir");
|
||||
await capture("5-feuille-du-prompt-bilan");
|
||||
await copyPrompt("prompt du bilan");
|
||||
await closePrompt(summaryHash);
|
||||
check(
|
||||
await page.evaluate(() => document.getElementById("summary")?.dataset.ready === "true"),
|
||||
"le bilan n'est plus là après la feuille du prompt",
|
||||
);
|
||||
|
||||
// ----------------------------------------------------------- L'analyse
|
||||
|
||||
await page.click("#summary-revisit");
|
||||
await until(
|
||||
"analyse sur le coup à revoir",
|
||||
(game) =>
|
||||
location.hash === `#/parties/${game}/analyse/1` &&
|
||||
document.querySelector("#analysis-board .board-frame") !== null &&
|
||||
!document.getElementById("analysis-ask-ai").hidden,
|
||||
id,
|
||||
);
|
||||
await capture("6-bouton-analyse", () =>
|
||||
page.locator("#analysis-ask-ai").scrollIntoViewIfNeeded(),
|
||||
);
|
||||
const analysisText = await openPrompt("#analysis-ask-ai", `/parties/${id}/analyse/1/ia`);
|
||||
checkPrompt(analysisText, "prompt de l'analyse");
|
||||
// Le même coup, les mêmes chiffres que dans la feuille du maître (le niveau
|
||||
// du joueur, lui, compte maintenant toutes ses décisions de la partie).
|
||||
const numbersOf = (text) => text.slice(text.indexOf("J'ai joué"), text.indexOf("Ces évaluations"));
|
||||
check(
|
||||
numbersOf(analysisText) === numbersOf(moveText) && numbersOf(moveText).length > 50,
|
||||
"les chiffres du coup diffèrent entre la feuille du maître et l'analyse",
|
||||
);
|
||||
await closePrompt(`#/parties/${id}/analyse/1`);
|
||||
// Coup 2 : le joueur a passé le double, une bonne décision ; son prompt de
|
||||
// videau.
|
||||
await page.goto(`${base}#/parties/${id}/analyse/2`);
|
||||
await until("coup 2", () =>
|
||||
document.getElementById("analysis-turn")?.textContent.startsWith("Coup 2 sur 2") &&
|
||||
!document.getElementById("analysis-ask-ai").hidden,
|
||||
);
|
||||
const cubeText = await openPrompt("#analysis-ask-ai", "/analyse/2/ia");
|
||||
checkPrompt(cubeText, "prompt du videau");
|
||||
check(cubeText.includes("prendre ou passer le double de l'ordinateur"), "prompt du videau");
|
||||
check(cubeText.includes("J'ai choisi de passer"), "prompt du videau : décision");
|
||||
await closePrompt(`#/parties/${id}/analyse/2`);
|
||||
|
||||
// --------------------------------------------------------- Le glossaire
|
||||
|
||||
await page.goto(`${base}#/glossaire/videau`);
|
||||
await until(
|
||||
"fiche du videau",
|
||||
() => {
|
||||
const link = document.getElementById("glossary-ask-ai-videau");
|
||||
return link !== null && !link.hidden;
|
||||
},
|
||||
);
|
||||
check(
|
||||
(await page.locator(".term-card .ask-ai:not([hidden])").count()) === 1,
|
||||
"un seul bouton « Demander à une IA » dans le glossaire",
|
||||
);
|
||||
await capture("7-bouton-glossaire", () =>
|
||||
page.locator("#glossary-ask-ai-videau").scrollIntoViewIfNeeded(),
|
||||
);
|
||||
const conceptText = await openPrompt("#glossary-ask-ai-videau", "#/glossaire/videau/ia");
|
||||
checkPrompt(conceptText, "prompt du concept");
|
||||
check(conceptText.includes("le concept « Videau » (en anglais : Cube)"), "prompt du concept");
|
||||
check(/chances de gagner \d+ %/.test(conceptText), "prompt du concept : évaluation");
|
||||
await capture("8-feuille-du-prompt-glossaire");
|
||||
await copyPrompt("prompt du concept");
|
||||
await closePrompt("#/glossaire/videau");
|
||||
|
||||
const csp = await page.evaluate(() => window.__csp);
|
||||
for (const violation of csp) {
|
||||
problems.push(`politique de sécurité : ${violation}`);
|
||||
}
|
||||
await browser.close();
|
||||
if (problems.length > 0) {
|
||||
for (const problem of problems) {
|
||||
console.error(`[${browserName}] ${problem}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
log(`demander à une IA : tout est conforme (${captures} captures)`);
|
||||
+7
-1
@@ -87,7 +87,13 @@ cmp -s <(curl -s "$base/docs/glossaire/videau.md") docs/glossaire/videau.md \
|
||||
check "docs/tutoriel/regles.md" "text/markdown"
|
||||
cmp -s <(curl -s "$base/docs/tutoriel/regles.md") docs/tutoriel/regles.md \
|
||||
|| report "/docs/tutoriel/regles.md diffère de la source"
|
||||
for feature in apprendre-les-regles decouvrir-l-application installer-l-application; do
|
||||
# Prompts pour une IA : copie de leur source unique (ADR 0008, étape 1f).
|
||||
for template in coup videau bilan concept exercices; do
|
||||
check "docs/prompts/$template.md" "text/markdown"
|
||||
cmp -s <(curl -s "$base/docs/prompts/$template.md") "docs/prompts/$template.md" \
|
||||
|| report "/docs/prompts/$template.md diffère de la source"
|
||||
done
|
||||
for feature in apprendre-les-regles decouvrir-l-application installer-l-application demander-a-une-ia; do
|
||||
check "docs/scenarios/$feature.feature" "text/plain"
|
||||
done
|
||||
# Le service worker attend l'accord du joueur avant de prendre la main.
|
||||
|
||||
Reference in New Issue
Block a user