diff --git a/.gitea/workflows/anno-apply-pr.yml b/.gitea/workflows/anno-apply-pr.yml index 7cb9e04..742e94b 100644 --- a/.gitea/workflows/anno-apply-pr.yml +++ b/.gitea/workflows/anno-apply-pr.yml @@ -41,7 +41,7 @@ jobs: run: | set -euo pipefail export EVENT_JSON="/var/run/act/workflow/event.json" - test -f "$EVENT_JSON" || { echo "❌ Missing $EVENT_JSON"; exit 1; } + test -f "$EVENT_JSON" || { echo "Missing $EVENT_JSON"; exit 1; } node --input-type=module - <<'NODE' > /tmp/anno.env import fs from "node:fs"; @@ -66,7 +66,10 @@ jobs: if (!owner || !repo) { const m = cloneUrl.match(/[:/](?[^/]+)\/(?[^/]+?)(?:\.git)?$/); - if (m?.groups) { owner = owner || m.groups.o; repo = repo || m.groups.r; } + if (m?.groups) { + owner = owner || m.groups.o; + repo = repo || m.groups.r; + } } if (!owner || !repo) throw new Error("Cannot infer owner/repo"); @@ -81,7 +84,6 @@ jobs: throw new Error("No issue number in event.json or workflow_dispatch input"); } - // label name: best-effort (non-bloquant) let labelName = "workflow_dispatch"; const lab = ev?.label; if (typeof lab === "string") labelName = lab; @@ -95,7 +97,7 @@ jobs: ? String(process.env.FORGE_API).trim().replace(/\/+$/,"") : origin; - function sh(s){ return JSON.stringify(String(s)); } + function sh(s) { return JSON.stringify(String(s)); } process.stdout.write([ `CLONE_URL=${sh(cloneUrl)}`, @@ -108,7 +110,7 @@ jobs: ].join("\n") + "\n"); NODE - echo "✅ context:" + echo "context:" sed -n '1,120p' /tmp/anno.env - name: Early gate (label event fast-skip, but tolerant) @@ -116,18 +118,16 @@ jobs: set -euo pipefail source /tmp/anno.env - echo "ℹ️ event label = $LABEL_NAME" + echo "event label = $LABEL_NAME" - # Fast skip on obvious non-approved label events (avoid noise), - # BUT do NOT skip if label payload is weird/unknown. if [[ "$LABEL_NAME" != "state/approved" && "$LABEL_NAME" != "workflow_dispatch" && "$LABEL_NAME" != "" && "$LABEL_NAME" != "[object Object]" ]]; then - echo "ℹ️ label=$LABEL_NAME => skip early" + echo "label=$LABEL_NAME => skip early" echo "SKIP=1" >> /tmp/anno.env echo "SKIP_REASON=\"label_not_approved_event\"" >> /tmp/anno.env exit 0 fi - echo "✅ continue to API gating (issue=$ISSUE_NUMBER)" + echo "continue to API gating (issue=$ISSUE_NUMBER)" - name: Fetch issue + hard gate on labels + Type env: @@ -135,9 +135,9 @@ jobs: run: | set -euo pipefail source /tmp/anno.env - [[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; } + [[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; } - test -n "${FORGE_TOKEN:-}" || { echo "❌ Missing secret FORGE_TOKEN"; exit 1; } + test -n "${FORGE_TOKEN:-}" || { echo "Missing secret FORGE_TOKEN"; exit 1; } curl -fsS \ -H "Authorization: token $FORGE_TOKEN" \ @@ -148,11 +148,12 @@ jobs: node --input-type=module - <<'NODE' >> /tmp/anno.env import fs from "node:fs"; - const issue = JSON.parse(fs.readFileSync("/tmp/issue.json","utf8")); - const title = String(issue.title || ""); + const issue = JSON.parse(fs.readFileSync("/tmp/issue.json", "utf8")); const body = String(issue.body || "").replace(/\r\n/g, "\n"); - const labels = Array.isArray(issue.labels) ? issue.labels.map(l => String(l.name || "")).filter(Boolean) : []; + const labels = Array.isArray(issue.labels) + ? issue.labels.map(l => String(l.name || "")).filter(Boolean) + : []; const hasApproved = labels.includes("state/approved"); function pickLine(key) { @@ -164,14 +165,12 @@ jobs: const typeRaw = pickLine("Type"); const type = String(typeRaw || "").trim().toLowerCase(); - const allowed = new Set(["type/media","type/reference","type/comment"]); - const proposer = new Set(["type/correction","type/fact-check"]); + const allowedAnno = new Set(["type/media", "type/reference", "type/comment"]); + const proposerTypes = new Set(["type/correction", "type/fact-check"]); const out = []; - out.push(`ISSUE_TITLE=${JSON.stringify(title)}`); out.push(`ISSUE_TYPE=${JSON.stringify(type)}`); - // HARD gate: must currently have state/approved (avoids depending on event payload) if (!hasApproved) { out.push(`SKIP=1`); out.push(`SKIP_REASON=${JSON.stringify("not_approved_label_present")}`); @@ -182,23 +181,23 @@ jobs: if (!type) { out.push(`SKIP=1`); out.push(`SKIP_REASON=${JSON.stringify("missing_type")}`); - } else if (allowed.has(type)) { + } else if (allowedAnno.has(type)) { // proceed - } else if (proposer.has(type)) { + } else if (proposerTypes.has(type)) { out.push(`SKIP=1`); - out.push(`SKIP_REASON=${JSON.stringify("proposer_type:"+type)}`); + out.push(`SKIP_REASON=${JSON.stringify("proposer_type:" + type)}`); } else { out.push(`SKIP=1`); - out.push(`SKIP_REASON=${JSON.stringify("unsupported_type:"+type)}`); + out.push(`SKIP_REASON=${JSON.stringify("unsupported_type:" + type)}`); } process.stdout.write(out.join("\n") + "\n"); NODE - echo "✅ gating result:" + echo "gating result:" grep -E '^(ISSUE_TYPE|SKIP|SKIP_REASON)=' /tmp/anno.env || true - - name: Comment issue if skipped (Proposer / unsupported / missing Type) + - name: Comment issue if skipped (unsupported / missing Type only) if: ${{ always() }} env: FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }} @@ -208,9 +207,13 @@ jobs: [[ "${SKIP:-0}" == "1" ]] || exit 0 - # IMPORTANT: do NOT comment for "not_approved_label_present" (avoid spam on other label events) if [[ "${SKIP_REASON:-}" == "not_approved_label_present" || "${SKIP_REASON:-}" == "label_not_approved_event" ]]; then - echo "ℹ️ skip reason=${SKIP_REASON} -> no comment" + echo "skip reason=${SKIP_REASON} -> no comment" + exit 0 + fi + + if [[ "${SKIP_REASON:-}" == proposer_type:* ]]; then + echo "proposer ticket detected -> anno stays silent" exit 0 fi @@ -219,15 +222,13 @@ jobs: REASON="${SKIP_REASON:-}" TYPE="${ISSUE_TYPE:-}" - if [[ "$REASON" == proposer_type:* ]]; then - MSG="ℹ️ Ticket #${ISSUE_NUMBER} détecté comme **Proposer** (${TYPE}).\n\n- Ce type est **traité manuellement par les editors**.\n✅ Aucun traitement automatique." - elif [[ "$REASON" == unsupported_type:* ]]; then - MSG="ℹ️ Ticket #${ISSUE_NUMBER} ignoré : Type non supporté par le bot (${TYPE}).\n\nTypes supportés : type/media, type/reference, type/comment." + if [[ "$REASON" == unsupported_type:* ]]; then + MSG="Ticket #${ISSUE_NUMBER} ignored: unsupported Type (${TYPE}). Supported types: type/media, type/reference, type/comment." else - MSG="ℹ️ Ticket #${ISSUE_NUMBER} ignoré : champ 'Type:' manquant ou illisible.\n\nAjoute : Type: type/media|type/reference|type/comment" + MSG="Ticket #${ISSUE_NUMBER} ignored: missing or unreadable 'Type:'. Expected: type/media|type/reference|type/comment" fi - PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")" + PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1] || ""}))' "$MSG")" curl -fsS -X POST \ -H "Authorization: token $FORGE_TOKEN" \ @@ -239,7 +240,7 @@ jobs: run: | set -euo pipefail source /tmp/anno.env - [[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; } + [[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; } rm -rf .git git init -q @@ -252,16 +253,16 @@ jobs: run: | set -euo pipefail source /tmp/anno.env - [[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; } + [[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; } npm ci --no-audit --no-fund - name: Check apply script exists run: | set -euo pipefail source /tmp/anno.env - [[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; } + [[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; } test -f scripts/apply-annotation-ticket.mjs || { - echo "❌ missing scripts/apply-annotation-ticket.mjs on $DEFAULT_BRANCH" + echo "missing scripts/apply-annotation-ticket.mjs on $DEFAULT_BRANCH" ls -la scripts | sed -n '1,200p' || true exit 1 } @@ -270,16 +271,16 @@ jobs: run: | set -euo pipefail source /tmp/anno.env - [[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; } + [[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; } npm run build test -f dist/para-index.json || { - echo "❌ missing dist/para-index.json after build" + echo "missing dist/para-index.json after build" ls -la dist | sed -n '1,200p' || true exit 1 } - echo "✅ dist/para-index.json present" + echo "dist/para-index.json present" - name: Apply ticket on bot branch (strict+verify, commit) continue-on-error: true @@ -290,10 +291,10 @@ jobs: run: | set -euo pipefail source /tmp/anno.env - [[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; } - test -d .git || { echo "❌ not a git repo (checkout failed)"; echo "APPLY_RC=90" >> /tmp/anno.env; exit 0; } + [[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; } + test -d .git || { echo "not a git repo (checkout failed)"; echo "APPLY_RC=90" >> /tmp/anno.env; exit 0; } - test -n "${FORGE_TOKEN:-}" || { echo "❌ Missing secret FORGE_TOKEN"; exit 1; } + test -n "${FORGE_TOKEN:-}" || { echo "Missing secret FORGE_TOKEN"; exit 1; } git config user.name "${BOT_GIT_NAME:-archicratie-bot}" git config user.email "${BOT_GIT_EMAIL:-bot@archicratie.local}" @@ -340,11 +341,11 @@ jobs: run: | set -euo pipefail source /tmp/anno.env || true - [[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; } + [[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; } RC="${APPLY_RC:-0}" if [[ "$RC" == "0" ]]; then - echo "ℹ️ no failure detected" + echo "no failure detected" exit 0 fi @@ -356,8 +357,8 @@ jobs: BODY="(no apply log found)" fi - MSG="❌ apply-annotation-ticket a échoué (rc=${RC}).\n\n\`\`\`\n${BODY}\n\`\`\`\n" - PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")" + MSG="apply-annotation-ticket failed (rc=${RC}).\n\n\`\`\`\n${BODY}\n\`\`\`\n" + PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1] || ""}))' "$MSG")" curl -fsS -X POST \ -H "Authorization: token $FORGE_TOKEN" \ @@ -374,9 +375,9 @@ jobs: source /tmp/anno.env || true [[ "${SKIP:-0}" != "1" ]] || exit 0 - [[ "${APPLY_RC:-0}" == "0" ]] || { echo "ℹ️ apply failed -> skip push"; exit 0; } - [[ "${NOOP:-0}" == "0" ]] || { echo "ℹ️ no-op -> skip push"; exit 0; } - test -d .git || { echo "ℹ️ no git repo -> skip push"; exit 0; } + [[ "${APPLY_RC:-0}" == "0" ]] || { echo "apply failed -> skip push"; exit 0; } + [[ "${NOOP:-0}" == "0" ]] || { echo "no-op -> skip push"; exit 0; } + test -d .git || { echo "no git repo -> skip push"; exit 0; } AUTH_URL="$(node --input-type=module -e ' const [clone, tok] = process.argv.slice(1); @@ -398,8 +399,8 @@ jobs: source /tmp/anno.env || true [[ "${SKIP:-0}" != "1" ]] || exit 0 - [[ "${APPLY_RC:-0}" == "0" ]] || { echo "ℹ️ apply failed -> skip PR"; exit 0; } - [[ "${NOOP:-0}" == "0" ]] || { echo "ℹ️ no-op -> skip PR"; exit 0; } + [[ "${APPLY_RC:-0}" == "0" ]] || { echo "apply failed -> skip PR"; exit 0; } + [[ "${NOOP:-0}" == "0" ]] || { echo "no-op -> skip PR"; exit 0; } PR_TITLE="anno: apply ticket #${ISSUE_NUMBER}" PR_BODY="PR auto depuis ticket #${ISSUE_NUMBER} (state/approved).\n\n- Branche: ${BRANCH}\n- Commit: ${END_SHA}\n\nMerge si CI OK." @@ -420,10 +421,10 @@ jobs: console.log(pr.html_url || pr.url || ""); ' "$PR_JSON")" - test -n "$PR_URL" || { echo "❌ PR URL missing. Raw: $PR_JSON"; exit 1; } + test -n "$PR_URL" || { echo "PR URL missing. Raw: $PR_JSON"; exit 1; } - MSG="✅ PR créée pour ticket #${ISSUE_NUMBER} : ${PR_URL}" - C_PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")" + MSG="PR created for ticket #${ISSUE_NUMBER}: ${PR_URL}" + C_PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1] || ""}))' "$MSG")" curl -fsS -X POST \ -H "Authorization: token $FORGE_TOKEN" \ @@ -431,7 +432,7 @@ jobs: "$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \ --data-binary "$C_PAYLOAD" - echo "✅ PR: $PR_URL" + echo "PR: $PR_URL" - name: Finalize (fail job if apply failed) if: ${{ always() }} @@ -439,11 +440,11 @@ jobs: set -euo pipefail source /tmp/anno.env || true - [[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; } + [[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; } RC="${APPLY_RC:-0}" if [[ "$RC" != "0" ]]; then - echo "❌ apply failed (rc=$RC)" + echo "apply failed (rc=$RC)" exit "$RC" fi - echo "✅ apply ok" \ No newline at end of file + echo "apply ok" \ No newline at end of file