From bc569dc7debb4bdd0b474806fb9e5c59be9c78f0 Mon Sep 17 00:00:00 2001 From: Mirko Brombin Date: Sun, 13 Sep 2026 13:24:43 +0200 Subject: [PATCH 1/3] ci: enforce AI disclosure Assisted-by: Codex:GPT-5 AI-Scope: Added repository instructions, README disclosure notice, pull request validation, workflow, and tests for the requested policy. --- .github/PULL_REQUEST_TEMPLATE.md | 8 ++ .github/copilot-instructions.md | 1 + .github/scripts/check-ai-disclosure.cjs | 81 ++++++++++++++++++++ .github/scripts/check-ai-disclosure.test.cjs | 71 +++++++++++++++++ .github/workflows/ai-disclosure.yml | 41 ++++++++++ AGENTS.md | 23 ++++++ CLAUDE.md | 1 + GEMINI.md | 4 + README.md | 18 +++++ 9 files changed, 248 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/copilot-instructions.md create mode 100644 .github/scripts/check-ai-disclosure.cjs create mode 100644 .github/scripts/check-ai-disclosure.test.cjs create mode 100644 .github/workflows/ai-disclosure.yml create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 GEMINI.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..0ba7ca99 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +## Generative AI assistance + +AI assistance: none + +Change `none` to `trivial` for exempt single-line completions, renames, or +formatting. Change it to `disclosed` when assisted commits contain the +`Assisted-by` and `AI-Scope` trailers required by +[AGENTS.md](https://github.com/Vanilla-OS/first-setup/blob/main/AGENTS.md). diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..dba71e97 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1 @@ +@../AGENTS.md diff --git a/.github/scripts/check-ai-disclosure.cjs b/.github/scripts/check-ai-disclosure.cjs new file mode 100644 index 00000000..438a6153 --- /dev/null +++ b/.github/scripts/check-ai-disclosure.cjs @@ -0,0 +1,81 @@ +const allowedModes = new Set(["none", "trivial", "disclosed"]); + +function disclosureMode(body) { + const lines = body + .split(/\r?\n/) + .filter((line) => /^\s*AI assistance:/i.test(line)); + + if (lines.length === 0) return { mode: "none" }; + if (lines.length > 1) { + return { error: "Keep one AI assistance declaration in the pull request." }; + } + + const mode = lines[0].split(":", 2)[1].trim().toLowerCase(); + if (!allowedModes.has(mode)) { + return { error: "AI assistance must be none, trivial, or disclosed." }; + } + return { mode }; +} + +function commitDisclosure(commit) { + const assistedLines = commit.message + .split(/\r?\n/) + .filter((line) => /^Assisted-by:/i.test(line)); + const scopeLines = commit.message + .split(/\r?\n/) + .filter((line) => /^AI(?:-| )Scope:/i.test(line)); + const validAssisted = assistedLines.every((line) => + /^Assisted-by:\s*[^:\r\n]+:[^\s\r\n]+\s*$/i.test(line), + ); + const validScope = scopeLines.every((line) => + /^AI-Scope:\s*\S(?:.*\S)?\s*$/i.test(line), + ); + const disclosed = assistedLines.length > 0 || scopeLines.length > 0; + + return { + disclosed, + valid: + disclosed && + assistedLines.length > 0 && + scopeLines.length > 0 && + validAssisted && + validScope, + }; +} + +function checkDisclosure(body, commits) { + const errors = []; + const declared = disclosureMode(body); + if (declared.error) errors.push(declared.error); + + let disclosed = 0; + for (const commit of commits) { + const result = commitDisclosure(commit); + if (!result.disclosed) continue; + disclosed++; + if (!result.valid) { + errors.push( + `${commit.sha.slice(0, 7)} must contain valid Assisted-by and AI-Scope trailers.`, + ); + } + } + + if (!declared.error && declared.mode === "disclosed" && disclosed === 0) { + errors.push( + "AI assistance is disclosed, but no commit contains the required trailers.", + ); + } + if ( + !declared.error && + declared.mode !== "disclosed" && + disclosed > 0 + ) { + errors.push( + "Set AI assistance to disclosed when a commit contains disclosure trailers.", + ); + } + + return errors; +} + +module.exports = { checkDisclosure }; diff --git a/.github/scripts/check-ai-disclosure.test.cjs b/.github/scripts/check-ai-disclosure.test.cjs new file mode 100644 index 00000000..06d219b8 --- /dev/null +++ b/.github/scripts/check-ai-disclosure.test.cjs @@ -0,0 +1,71 @@ +const test = require("node:test"); +const assert = require("node:assert/strict"); +const { checkDisclosure } = require("./check-ai-disclosure.cjs"); + +const plainCommit = { sha: "1111111", message: "fix: repair launch" }; +const assistedCommit = { + sha: "2222222", + message: + "docs: explain launch\n\nAssisted-by: Codex:gpt-5\nAI-Scope: Drafted the documentation from the issue description.", +}; + +test("accepts work without AI assistance", () => { + assert.deepEqual(checkDisclosure("", [plainCommit]), []); + assert.deepEqual( + checkDisclosure("AI assistance: none", [plainCommit]), + [], + ); +}); + +test("accepts exempt trivial completions", () => { + assert.deepEqual( + checkDisclosure("AI assistance: trivial", [plainCommit]), + [], + ); +}); + +test("accepts a disclosed assisted commit", () => { + assert.deepEqual( + checkDisclosure("AI assistance: disclosed", [plainCommit, assistedCommit]), + [], + ); +}); + +test("requires trailers for disclosed assistance", () => { + assert.match( + checkDisclosure("AI assistance: disclosed", [plainCommit])[0], + /no commit contains/, + ); +}); + +test("requires the pull request to match its trailers", () => { + assert.match( + checkDisclosure("AI assistance: none", [assistedCommit])[0], + /Set AI assistance to disclosed/, + ); +}); + +test("rejects incomplete and legacy trailers", () => { + const commit = { + sha: "3333333", + message: "docs: update\n\nAssisted-by: Codex:gpt-5\nAI scope: docs", + }; + assert.match( + checkDisclosure("AI assistance: disclosed", [commit])[0], + /must contain valid/, + ); +}); + +test("rejects invalid or repeated declarations", () => { + assert.match( + checkDisclosure("AI assistance: sometimes", [plainCommit])[0], + /must be none, trivial, or disclosed/, + ); + assert.match( + checkDisclosure( + "AI assistance: none\nAI assistance: disclosed", + [plainCommit], + )[0], + /Keep one/, + ); +}); diff --git a/.github/workflows/ai-disclosure.yml b/.github/workflows/ai-disclosure.yml new file mode 100644 index 00000000..a4459515 --- /dev/null +++ b/.github/workflows/ai-disclosure.yml @@ -0,0 +1,41 @@ +name: AI disclosure + +on: + pull_request_target: + types: [opened, synchronize, reopened, edited] + +permissions: + contents: read + pull-requests: read + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: ${{ github.event.pull_request.base.sha }} + persist-credentials: false + + - name: Test disclosure policy + run: node --test .github/scripts/check-ai-disclosure.test.cjs + + - name: Check pull request + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd + with: + script: | + const { checkDisclosure } = require("./.github/scripts/check-ai-disclosure.cjs"); + const commits = await github.paginate(github.rest.pulls.listCommits, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + per_page: 100, + }); + const errors = checkDisclosure( + context.payload.pull_request.body || "", + commits.map((commit) => ({ + sha: commit.sha, + message: commit.commit.message, + })), + ); + if (errors.length > 0) core.setFailed(errors.join("\n")); diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..93051c13 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,23 @@ +# Repository instructions + +## Generative AI disclosure + +Read this section before changing files, creating a commit, or opening a pull +request. + +When generative AI contributes more than a trivial completion, add both of +these trailers to every assisted commit: + +```plain +Assisted-by: : +AI-Scope: +``` + +Single-line completions, renames, and formatting changes do not need trailers. +Work completed without generative AI does not need trailers. + +Before opening a pull request, set `AI assistance` in the pull request template +to `none`, `trivial`, or `disclosed`. Use `disclosed` when at least one commit +has the trailers above. Review every assisted change before submission. + +Do not create the commit or pull request until its disclosure matches the work. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 00000000..61a8dc8e --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,4 @@ +# Repository instructions + +Read and follow [AGENTS.md](AGENTS.md) before changing files, creating a commit, +or opening a pull request. diff --git a/README.md b/README.md index 8096df39..03218c64 100644 --- a/README.md +++ b/README.md @@ -144,3 +144,21 @@ If you are using a different desktop than GNOME, you will have to adjust `/usr/s This session is only used to create a new user account. It should be a restricted shell to prevent the user from making changes to the system that will be lost when logging into their own user account. If your Desktop doesn't offer this feature, just copy the session of your desktop to firstsetup.desktop. + +## Use of Generative AI + +Maintainers may use generative AI tools as assistants while working on first-setup. Non-trivial assisted commits disclose the tool, model, and scope of the work. + +AI tools may assist with code comments, documentation, repetitive code, and issue triage. Maintainers make project decisions and review every assisted change before it is merged. + +Use these trailers for non-trivial assisted commits: + +```plain +Assisted-by: : +AI-Scope: +``` + +Single-line completions, renames, and formatting changes do not need trailers. + +Coding agents must also follow [AGENTS.md](AGENTS.md) before changing files, +creating commits, or opening pull requests. From 7c92550dc525507613fc9f2d81075e79e777ff12 Mon Sep 17 00:00:00 2001 From: Yael abargil Date: Tue, 15 Sep 2026 14:51:33 +0200 Subject: [PATCH 2/3] l10n/weblate: update Hebrew translation Currently translated at 53.7% (50 of 93 strings) Co-authored-by: Yael abargil Translate-URL: https://hosted.weblate.org/projects/vanilla-os/first-setup/he/ Translation: Vanilla OS/Vanilla OS First Setup --- po/he.po | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/po/he.po b/po/he.po index bdb03027..e5f7419c 100644 --- a/po/he.po +++ b/po/he.po @@ -4,13 +4,14 @@ # Daniel Toubul , 2023. # guylamar2006 , 2024. # aharon don , 2025. +# Yael abargil , 2026. msgid "" msgstr "" "Project-Id-Version: vanilla-first-setup\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-03-07 10:11+0100\n" -"PO-Revision-Date: 2025-03-05 21:37+0000\n" -"Last-Translator: aharon don \n" +"PO-Revision-Date: 2026-07-23 01:01+0000\n" +"Last-Translator: Yael abargil \n" "Language-Team: Hebrew \n" "Language: he\n" @@ -19,7 +20,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n == 1) ? 0 : ((n == 2) ? 1 : ((n > 10 && " "n % 10 == 0) ? 2 : 3));\n" -"X-Generator: Weblate 5.10.3-dev\n" +"X-Generator: Weblate 2026.8.dev0\n" #: data/org.vanillaos.FirstSetup.desktop.in:2 msgid "Vanilla OS First Setup" @@ -44,11 +45,11 @@ msgstr "הפעל כמשתמש חדש" #: vanilla_first_setup/gtk/logout.ui:23 msgid "Login Now" -msgstr "" +msgstr "התחבר" #: vanilla_first_setup/gtk/done.ui:13 msgid "Your user account is ready to use" -msgstr "חשבון המשתמש שלך מוכן לשימוש." +msgstr "חשבונך שלך מוכן לשימוש." #: vanilla_first_setup/gtk/done.ui:29 msgid "Take a tour" From 54fa9fa8ffac20c521b4d28d20bb8171358a546c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Stycze=C5=84?= Date: Tue, 15 Sep 2026 14:51:35 +0200 Subject: [PATCH 3/3] l10n/weblate: update Polish translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 97.8% (91 of 93 strings) Co-authored-by: Wiktor Styczeń Translate-URL: https://hosted.weblate.org/projects/vanilla-os/first-setup/pl/ Translation: Vanilla OS/Vanilla OS First Setup --- po/pl.po | 58 +++++++++++++++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/po/pl.po b/po/pl.po index 7aaebd4d..9f76e926 100644 --- a/po/pl.po +++ b/po/pl.po @@ -7,13 +7,14 @@ # Dawid Harwig , 2024. # Piotr Wieczorek , 2024, 2025. # Eryk Michalak , 2025. +# Wiktor Styczeń , 2026. msgid "" msgstr "" "Project-Id-Version: vanilla-first-setup\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-03-07 10:11+0100\n" -"PO-Revision-Date: 2025-12-23 09:00+0000\n" -"Last-Translator: Tomasz Dyńka \n" +"PO-Revision-Date: 2026-09-15 12:51+0000\n" +"Last-Translator: Wiktor Styczeń \n" "Language-Team: Polish \n" "Language: pl\n" @@ -22,7 +23,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" -"X-Generator: Weblate 5.15.1\n" +"X-Generator: Weblate 2026.10.dev0\n" #: data/org.vanillaos.FirstSetup.desktop.in:2 msgid "Vanilla OS First Setup" @@ -198,7 +199,7 @@ msgstr "Obecna strefa czasowa:" #: vanilla_first_setup/gtk/timezone.ui:34 msgid "not set" -msgstr "" +msgstr "Nieustawione" #: vanilla_first_setup/gtk/keyboard.ui:10 msgid "Keyboard Layout" @@ -227,7 +228,7 @@ msgstr "" #: vanilla_first_setup/gtk/layout-applications.ui:45 msgid "Customize Core Applications" -msgstr "" +msgstr "Dostosowywanie podstawowych aplikacji" #: vanilla_first_setup/gtk/layout-applications.ui:55 msgid "Browsers" @@ -243,7 +244,7 @@ msgstr "Programy narzędziowe" #: vanilla_first_setup/gtk/layout-applications.ui:81 msgid "Useful utilities to enhance your desktop experience" -msgstr "" +msgstr "Przydatne narzędzia poprawiające komfort pracy z komputerem" #: vanilla_first_setup/gtk/layout-applications.ui:95 msgid "Customize Utilities" @@ -255,11 +256,11 @@ msgstr "Aplikacje biurowe" #: vanilla_first_setup/gtk/layout-applications.ui:106 msgid "The LibreOffice suite" -msgstr "" +msgstr "Pakiet LibreOffice" #: vanilla_first_setup/gtk/layout-applications.ui:120 msgid "Customize Office Applications" -msgstr "" +msgstr "Dostosowywanie aplikacji pakietu Office" #: vanilla_first_setup/gtk/applications-dialog.ui:8 msgid "Select Applications" @@ -271,38 +272,37 @@ msgstr "Zatwierdź" #: vanilla_first_setup/gtk/applications-dialog.ui:30 msgid "Select the applications you want to install" -msgstr "" +msgstr "Wybierz aplikacje, które chcesz zainstalować" #: vanilla_first_setup/window.py:74 msgid "Setup failed: " -msgstr "" +msgstr "Konfiguracja nie powiodła się: " #: vanilla_first_setup/window.py:79 msgid "Details" msgstr "Szczegóły" #: vanilla_first_setup/window.py:85 -#, fuzzy msgid "Error log" -msgstr "Błąd!" +msgstr "Log błędów" #: vanilla_first_setup/application.py:76 msgid "Don't make any changes to the system." -msgstr "" +msgstr "Nie wprowadzaj żadnych zmian w systemie." #: vanilla_first_setup/application.py:84 msgid "Force the configure system mode, independant of group." -msgstr "" +msgstr "Wymuś tryb konfiguracji systemowej, niezależnie od grupy." #: vanilla_first_setup/application.py:92 msgid "Force the regular mode, independant of group." -msgstr "" +msgstr "Wymuś tryb standardowy, niezależnie od grupy." #: vanilla_first_setup/application.py:100 msgid "" "Use the original equipment manufacturer mode with language, keyboard and " "timezone selection." -msgstr "" +msgstr "Użyj trybu OEM z wyborem języka, klawiatury i strefy czasowej." #: vanilla_first_setup/views/conn_check.py:72 msgid "No Internet Connection!" @@ -313,14 +313,12 @@ msgid "First Setup requires an active internet connection" msgstr "Pierwsza konfiguracja wymaga aktywnego połączenia internetetowego" #: vanilla_first_setup/views/conn_check.py:79 -#, fuzzy msgid "Connection available" -msgstr "Połączenie nieudane" +msgstr "Połączenie dostępne" #: vanilla_first_setup/views/conn_check.py:80 -#, fuzzy msgid "You have a working internet connection" -msgstr "Brak Połączenia z Internetem!" +msgstr "Masz działające połączenie z Internetem" #: vanilla_first_setup/views/keyboard.py:37 msgid "Keyboard" @@ -328,20 +326,20 @@ msgstr "Klawiatura" #: vanilla_first_setup/views/user.py:118 msgid "Username cannot be empty." -msgstr "" +msgstr "Nazwa użytkownika nie może być pusta." #: vanilla_first_setup/views/user.py:121 msgid "Username cannot be longer than 32 characters." -msgstr "" +msgstr "Nazwa użytkownika nie może przekraczać 32 znaków." #: vanilla_first_setup/views/user.py:124 msgid "Username cannot contain special characters or uppercase letters." msgstr "" +"Nazwa użytkownika nie może zawierać znaków specjalnych ani wielkich liter." #: vanilla_first_setup/views/user.py:128 -#, fuzzy msgid "This username is already in use." -msgstr "Twoje urządzenie jest gotowe do używania." +msgstr "Ta nazwa użytkownika jest już zajęta." #: vanilla_first_setup/views/locations.py:220 msgid "Region" @@ -353,7 +351,7 @@ msgstr "Kraj" #: vanilla_first_setup/views/locations.py:327 msgid "Search results" -msgstr "" +msgstr "Wyniki wyszukiwania" #: vanilla_first_setup/views/timezone.py:39 msgid "Timezone" @@ -361,11 +359,11 @@ msgstr "Strefa czasowa" #: vanilla_first_setup/views/welcome_user.py:45 msgid "Hello {}!" -msgstr "" +msgstr "Witaj {}!" #: vanilla_first_setup/views/progress.py:94 msgid "Setting up the system" -msgstr "" +msgstr "Konfiguracja systemu" #: vanilla_first_setup/views/progress.py:97 msgid "Installing" @@ -393,7 +391,7 @@ msgstr "Antarktyda" #: vanilla_first_setup/core/timezones.py:97 msgid "Pacific Ocean" -msgstr "" +msgstr "Ocean Spokojny" #: vanilla_first_setup/core/timezones.py:98 msgid "Australia" @@ -401,11 +399,11 @@ msgstr "Australia" #: vanilla_first_setup/core/timezones.py:99 msgid "Atlantic Ocean" -msgstr "" +msgstr "Ocean Atlantycki" #: vanilla_first_setup/core/timezones.py:100 msgid "Indian Ocean" -msgstr "" +msgstr "Ocean Indyjski" #: vanilla_first_setup/core/timezones.py:101 msgid "Arctic"