@heroiclands/package-build 22.9.1 → 22.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 22.9.2
4
+
5
+ ### Patch Changes
6
+
3
7
  ## 22.9.1
4
8
 
5
9
  ### Patch Changes
@@ -95,6 +95,29 @@ precedence: a plain `git config` sets one clone, `--global` sets a machine.
95
95
  The defaults differ deliberately: a guard that costs nothing is on unless
96
96
  refused, and one that runs a container for minutes is off unless asked for.
97
97
 
98
+ The commit-message guard requires Node, like the rest of the toolchain. It checks
99
+ complete coauthor trailers and standalone credit clauses, with indentation,
100
+ bullets, emoji, Markdown emphasis and inline links permitted as decoration.
101
+ Supported identities are Codex, ChatGPT, OpenAI Codex, OpenAI ChatGPT, OpenAI,
102
+ Claude, Claude Code, Anthropic,
103
+ GitHub Copilot, Gemini, Cursor, and generic AI assistant credits. Signature forms
104
+ are generated by/with, assisted by, written by, AI-assisted and AI-generated.
105
+ Matching ignores case. Human coauthors such as Claudette Smith and Jean-Claude
106
+ Martin, explanatory prose, and ordinary mentions of these tools pass.
107
+
108
+ Specific assistant mailbox identities also identify a coauthor credit:
109
+ `codex@openai.com`, `chatgpt@openai.com`, `noreply@openai.com`,
110
+ `claude@anthropic.com` and `noreply@anthropic.com`. A provider's email domain
111
+ alone does not identify an assistant. This is an explicit set of credit forms,
112
+ not a classifier for arbitrary natural-language attribution.
113
+
114
+ Git comment lines and the verbose diff after a scissors comment are excluded.
115
+ Findings report the original message line and column. This local hook examines
116
+ commit messages only; the shared No Attribution workflow also checks pull
117
+ request titles and bodies. Neither examines repository files, issues, comments,
118
+ or Git author/committer metadata. The package-build repository's own hook uses
119
+ the same matching code and remains enabled independently of the consumer switch.
120
+
98
121
  The branch guard is the one you meet first:
99
122
 
100
123
  ```console
@@ -0,0 +1,82 @@
1
+ /*
2
+ * Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
3
+ * SPDX-License-Identifier: GPL-3.0-or-later
4
+ */
5
+
6
+ /** Bounded identities and standalone credit forms shared with the commit hooks. */
7
+ export const identities = [
8
+ "Codex",
9
+ "ChatGPT",
10
+ "OpenAI",
11
+ "OpenAI Codex",
12
+ "OpenAI ChatGPT",
13
+ "Claude",
14
+ "Claude Code",
15
+ "Anthropic",
16
+ "GitHub Copilot",
17
+ "Gemini",
18
+ "Cursor",
19
+ "AI",
20
+ "AI assistant",
21
+ "an AI assistant",
22
+ ];
23
+ export const phrases = ["Generated by", "Generated with", "Assisted by", "Written by"];
24
+ export const mailboxes = [
25
+ "codex@openai.com",
26
+ "chatgpt@openai.com",
27
+ "noreply@openai.com",
28
+ "claude@anthropic.com",
29
+ "noreply@anthropic.com",
30
+ ];
31
+ const identity = new RegExp(`^(?:${identities.join("|")})$`, "i");
32
+ const signature = new RegExp(`^(?:${phrases.join("|")})\\s+(.+?)[.!]?$`, "i");
33
+
34
+ /** Normalize decoration while keeping each character's original UTF-16 offset. */
35
+ function normalize(line) {
36
+ let chars = Array.from({ length: line.length }, (_, offset) => ({
37
+ text: line[offset],
38
+ offset,
39
+ }));
40
+ const replace = (pattern, keep) => {
41
+ const text = chars.map((char) => char.text).join("");
42
+ const matches = [...text.matchAll(pattern)];
43
+ for (const match of matches.reverse()) {
44
+ const retained = keep(match);
45
+ chars.splice(
46
+ match.index,
47
+ match[0].length,
48
+ ...chars.slice(match.index + retained.start, match.index + retained.end),
49
+ );
50
+ }
51
+ };
52
+ replace(/\[([^\]]+)\]\([^\s)]*\)/g, (match) => ({ start: 1, end: 1 + match[1].length }));
53
+ replace(/[*_`~]/g, () => ({ start: 0, end: 0 }));
54
+ replace(/^[^\p{L}]+/gu, () => ({ start: 0, end: 0 }));
55
+ return {
56
+ text: chars
57
+ .map((char) => char.text)
58
+ .join("")
59
+ .trimEnd(),
60
+ column: (chars[0]?.offset ?? 0) + 1,
61
+ };
62
+ }
63
+
64
+ /** Find a complete credit clause; ordinary prose and human identities pass. */
65
+ export function attribution(line) {
66
+ const normalized = normalize(line);
67
+ const trailer = normalized.text.match(/^co-authored-by:\s*(.*?)\s*(?:<([^<>]+)>)?$/i);
68
+ if (trailer && (identity.test(trailer[1]) || mailboxes.includes(trailer[2]?.toLowerCase()))) {
69
+ return { column: normalized.column, kind: "Co-Authored-By trailer naming an assistant" };
70
+ }
71
+ const credit = normalized.text.match(signature);
72
+ const generic = normalized.text.match(
73
+ /^AI-(?:assisted|generated)(?:\s+(?:by|with)\s+(.+?))?[.!]?$/i,
74
+ );
75
+ if (
76
+ (credit && identity.test(credit[1])) ||
77
+ (generic && (!generic[1] || identity.test(generic[1])))
78
+ ) {
79
+ return { column: normalized.column, kind: "assistant signature" };
80
+ }
81
+ return null;
82
+ }
@@ -0,0 +1,26 @@
1
+ /*
2
+ * Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
3
+ * SPDX-License-Identifier: GPL-3.0-or-later
4
+ */
5
+ import { readFileSync } from "node:fs";
6
+ import { relative } from "node:path";
7
+ import { attribution } from "./attribution.mjs";
8
+
9
+ const file = process.argv[2];
10
+ const lines = readFileSync(file, "utf8").split(/\r?\n/);
11
+ let rejected = false;
12
+ for (const [index, line] of lines.entries()) {
13
+ // Git's verbose diff starts at the scissors comment; other comments are inert.
14
+ if (/^#.*>8/.test(line)) break;
15
+ if (line.startsWith("#")) continue;
16
+ const finding = attribution(line);
17
+ if (!finding) continue;
18
+ console.error(
19
+ `${relative(process.cwd(), file)}:${index + 1}:${finding.column}: error: ${finding.kind}; attribution is not allowed in commit messages.`,
20
+ );
21
+ rejected = true;
22
+ }
23
+ if (rejected) {
24
+ console.error("Remove the assistant credit and commit again.");
25
+ process.exitCode = 1;
26
+ }
@@ -1,40 +1,8 @@
1
1
  #!/usr/bin/env sh
2
- #
3
- # Reject commit messages that carry AI/assistant attribution.
4
- #
5
- # This project does not want Co-Authored-By trailers naming an AI assistant, nor
6
- # "Generated with Claude Code"-style signatures, in its history. The matching CI
7
- # guard is the shared HeroicLands/.github/actions/no-attribution action, which
8
- # this repository's .github/workflows/no-attribution.yml calls. That action holds
9
- # the pattern for every repository; this hook is the only other copy, because a
10
- # git hook runs from your checkout and cannot live in an action. Keep them in sync.
11
- #
12
- # Installed for everyone via the package.json "prepare" script, which points
13
- # git at this directory (`git config core.hooksPath .githooks`) on `npm install`.
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+ # Check the commit message using the same contract as the shared CI action.
14
4
 
15
5
  . "$(dirname "$0")/hook-enabled.sh"
16
-
17
- # On unless refused: it costs nothing, and the No Attribution workflow enforces
18
- # the same rule on GitHub, so opting out locally only moves the failure later.
19
6
  hook_enabled noAttribution true || exit 0
20
7
 
21
- msg_file="$1"
22
-
23
- # Scan only the real message: drop the diff appended by `commit --verbose` (from
24
- # the scissors comment onward) and strip remaining comment lines.
25
- content="$(sed '/^#.*>8/,$d' "$msg_file" | grep -v '^#')"
26
-
27
- # Anchored to the start of a line: real attribution is a trailer / signature at
28
- # column 0, so prose that merely *mentions* these phrases mid-sentence (e.g. this
29
- # hook's own docs) does not trip it. The generated-with branch tolerates a
30
- # leading emoji/space.
31
- pattern='^[[:space:]]*co-authored-by:.*(claude|anthropic)|^[^[:alpha:]]*generated with .*claude code'
32
-
33
- if printf '%s\n' "$content" | grep -iEq "$pattern"; then
34
- echo "commit-msg: AI/assistant attribution is not allowed in commit messages." >&2
35
- printf '%s\n' "$content" | grep -iEn "$pattern" | sed 's/^/ /' >&2
36
- echo "Remove the 'Co-Authored-By: …' / 'Generated with Claude Code' line and commit again." >&2
37
- exit 1
38
- fi
39
-
40
- exit 0
8
+ exec node "$(dirname "$0")/commit-message.mjs" "$1"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "22.9.1",
3
+ "version": "22.9.2",
4
4
  "description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "type": "module",