summaryrefslogtreecommitdiff
path: root/scripts/check_ai_disclosure.sh
blob: 5a08a44c3aeb48874a0abe611ae35ab572d19250 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#!/bin/bash
###############################################################################
# Copyright (c) 2026 Eclipse ThreadX contributors
#
# This program and the accompanying materials are made available under the
# terms of the MIT License which is available at
# https://opensource.org/licenses/MIT.
#
# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5).
# The AI-generated portions may be considered public domain (CC0-1.0)
# and not subject to the project's licence. The human contributor has
# reviewed and verified that the code is correct.
#
# SPDX-License-Identifier: MIT and CC0-1.0
###############################################################################
#
# Fail the build if a file's AI disclosure comment has drifted from the one
# accepted form.
#
# A file that was edited with AI assistance carries exactly one line:
#
#     Portions of this file were generated with AI assistance.
#
# written with the comment character that file already uses.  It names no
# product, no model and no version, and a file carries at most one of it, ever.
#
# The text is fixed for a reason that is easy to miss.  An earlier convention
# named the product and the model -- "Some portions generated by <product>
# (<model>)" -- and the result was not attribution but accumulation: each tool
# that touched a file failed to recognise the line another tool had left, and
# appended its own.  Files reached three stacked lines, and one product ended
# up spelled four different ways across the tree, which made the record
# unusable for the one question it was meant to answer.
#
# Precise attribution lives on the commit instead, where the Assisted-by
# trailer is dated and attached to the diff it describes:
#
#     scripts/count_assisted_by.sh --list <rev> -- <path>
#
# A file-level flag answers WHETHER; the history answers WHO.  A header line
# cannot hold the second honestly, because the code it names gets rewritten
# and the line stays.
#
# The AI Disclosure paragraph in a new file's copyright header is different and
# is not checked here: it keeps its product and model version, because a file
# is created once and that record cannot grow.
#
# This script is excluded from its own scan.  It has to spell the rejected
# forms in order to look for them.

set -euo pipefail

readonly ROOT="$(cd "$(dirname "$(realpath "$0")")/.." && pwd)"
readonly SELF='scripts/check_ai_disclosure.sh'
readonly FIXED='Portions of this file were generated with AI assistance.'

cd "${ROOT}"

# Tracked files only.  A build tree is not this repository's text to police,
# and scanning one would make the check depend on whether somebody had built.
mapfile -d '' -t FILES < <(git ls-files -z | grep -zZv "^${SELF}$")

status=0

report() {
    printf '%s\n\n' "$1" >&2
    printf '%s\n\n' "$2" >&2
    status=1
}

# 1.  The superseded per-edit form, which names a product and a model.
hits="$(grep -nI 'Some portions generated by' -- "${FILES[@]}" 2>/dev/null || true)"
if [ -n "${hits}" ]; then
    report "AI disclosure check FAILED: superseded per-edit form.

Replace each of these with the fixed line, keeping the file's comment
character:

    ${FIXED}" "${hits}"
fi

# 2.  A doubled comment marker, such as '; //' or '@ //'.  Assembly dialects
#     differ -- armasm and IAR use ';', GNU as uses '@' or '//' -- and writing
#     both is a symptom of a tool guessing rather than reading the file.
hits="$(grep -nIE '(//|[;@#])[[:space:]]*//[[:space:]]*Portions of this file were generated' \
        -- "${FILES[@]}" 2>/dev/null || true)"
if [ -n "${hits}" ]; then
    report "AI disclosure check FAILED: doubled comment marker.

Use the single comment character the rest of the file uses." "${hits}"
fi

# 3.  More than one disclosure line in a file.  This is the failure the fixed
#     text exists to prevent, so it is worth catching directly rather than
#     inferring it from the form.
hits="$(grep -cIF "${FIXED}" -- "${FILES[@]}" 2>/dev/null | awk -F: '$NF > 1' || true)"
if [ -n "${hits}" ]; then
    report "AI disclosure check FAILED: more than one disclosure line.

A file carries at most one, ever.  Keep the first and delete the rest; the
commit trailer, not the header, records which agents have touched the file." "${hits}"
fi

# 4.  A near miss.  A line that is clearly meant to be the disclosure but is
#     not spelled exactly right defeats every deduplication that follows it.
#
#     Markdown is exempt from this one check.  The contribution guide has to
#     quote the accepted text and explain when it applies, so matching the bare
#     phrase there reports prose rather than drift.  A superseded form in a
#     Markdown file is still caught by check 1.
mapfile -d '' -t SOURCES < <(printf '%s\0' "${FILES[@]}" | grep -zZv '\.md$' || true)
hits=''
if [ "${#SOURCES[@]}" -gt 0 ]; then
    hits="$(grep -nIF 'AI assistance' -- "${SOURCES[@]}" 2>/dev/null \
            | grep -vF "${FIXED}" || true)"
fi
if [ -n "${hits}" ]; then
    report "AI disclosure check FAILED: the text is not spelled exactly.

The accepted text, character for character, is:

    ${FIXED}" "${hits}"
fi

if [ "${status}" -eq 0 ]; then
    echo "AI disclosure check passed."
fi

exit "${status}"