Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -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/Prometheus/blob/main/AGENTS.md).
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@../AGENTS.md
81 changes: 81 additions & 0 deletions .github/scripts/check-ai-disclosure.cjs
Original file line number Diff line number Diff line change
@@ -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 };
71 changes: 71 additions & 0 deletions .github/scripts/check-ai-disclosure.test.cjs
Original file line number Diff line number Diff line change
@@ -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/,
);
});
41 changes: 41 additions & 0 deletions .github/workflows/ai-disclosure.yml
Original file line number Diff line number Diff line change
@@ -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"));
23 changes: 23 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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: <tool>:<model-version>
AI-Scope: <what the tool generated and the prompt or a short prompt summary>
```

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.
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
4 changes: 4 additions & 0 deletions GEMINI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Repository instructions

Read and follow [AGENTS.md](AGENTS.md) before changing files, creating a commit,
or opening a pull request.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,21 @@ give it to humans, symbolizing the transmission of knowledge and technology.
The Prometheus package provides a simple and accessible solution for pulling
and mounting container images, making it easier to interact with OCI images
in other projects.

## Use of Generative AI

Maintainers may use generative AI tools as assistants while working on Prometheus. 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: <tool>:<model-version>
AI-Scope: <what the tool generated and the prompt or a short prompt summary>
```

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.
Loading