From b36f547502edbcf12446261c54f790f307c506f9 Mon Sep 17 00:00:00 2001 From: Rage Lopez Date: Wed, 29 Jul 2026 20:53:21 -0500 Subject: [PATCH] fix: distinguish generated artifacts from AI disclosure Signed-off-by: Rage Lopez --- src/core/evaluator.mjs | 6 +++-- test/evaluator.test.mjs | 49 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/core/evaluator.mjs b/src/core/evaluator.mjs index 3440867..a464c6b 100644 --- a/src/core/evaluator.mjs +++ b/src/core/evaluator.mjs @@ -21,6 +21,8 @@ const GENERIC_TITLES = new Set([ "quick fix" ]); +const TOOL_GENERATION_DISCLOSURE = /\b(chatgpt|claude|copilot|llm|coding assistant|ai-generated|ai generated|generated by ai|generated by a tool|tool-generated|tool-suggested|coccinelle generated|checkpatch\.pl --fix)\b/i; + const SIGNALS = { issueLink: /\b(fix(?:e[sd])?|close[sd]?|resolve[sd]?)\s+#\d+\b|github\.com\/[^/\s]+\/[^/\s]+\/issues\/\d+/i, testMention: /\b(npm test|node --test|pytest|cargo test|go test|unit tests?|integration tests?|manual(?:ly)? test(?:ed)?|verified|verification)\b/i, @@ -31,7 +33,7 @@ const SIGNALS = { version: /\b(version|commit|sha|main|release|v?\d{4}\.\d+(?:\.\d+)?|v\d+\.\d+|node\s+\d+|python\s+\d+|os:|environment)\b/i, logs: /```|stack trace|traceback|journal|log output|error output|exception|panic/i, rootCause: /\b(root cause|cause[ds]?|because|bisect|regression|culprit|patch|proposed fix|able to fix|workaround|analysis)\b/i, - aiDisclosure: /\b(ai|llm|chatgpt|copilot|claude|gemini|generated)\b/i, + aiDisclosure: TOOL_GENERATION_DISCLOSURE, aiReportClaim: /\b(?:asked\s+(?:an?\s+)?ai|ai\s+(?:said|says|found|reported|detected|suggested)|(?:chatgpt|copilot|claude|gemini|ai tool|llm)\s+(?:said|says|found|reported|detected|suggested|generated|wrote|claims?))\b/i, humanAccountability: /\b(tested|verified|reviewed|reproduced|i understand|manual|locally)\b/i, dependencyJustification: /\b(dependenc(?:y|ies)|package|lockfile|upgrade|security update|npm install|npm audit|vulnerability)\b/i, @@ -1520,7 +1522,7 @@ function hasHumanSignedOff(text) { } function hasMeaningfulToolGeneration(text) { - return /\b(chatgpt|claude|copilot|llm|coding assistant|ai-generated|ai generated|generated by ai|generated by a tool|tool-generated|tool-suggested|coccinelle generated|checkpatch\.pl --fix)\b/i.test(text); + return TOOL_GENERATION_DISCLOSURE.test(text); } function analyzeProvenance(input) { diff --git a/test/evaluator.test.mjs b/test/evaluator.test.mjs index e657cd3..5915191 100644 --- a/test/evaluator.test.mjs +++ b/test/evaluator.test.mjs @@ -31,6 +31,55 @@ test("ready pull request passes as reviewable", async () => { assert.doesNotMatch(result.comment, /- undefined:/); }); +test("generated artifact wording is not treated as AI disclosure", () => { + const result = evaluateContribution({ + kind: "pull_request", + title: "docs: update checked-in reference README", + body: [ + "Closes #42.", + "", + "The generated README is checked in because repository policy requires the reference output to remain synchronized with its source." + ].join("\n"), + authorAssociation: "MEMBER", + draft: false, + changedFiles: 1, + additions: 4, + deletions: 2, + files: [ + { filename: "docs/reference/README.md", additions: 4, deletions: 2 } + ], + checks: [ + { name: "readme-consistency", conclusion: "success" } + ] + }); + + assert.ok(result.checks.some((check) => check.id === "accountability" && check.status === "pass")); + assert.equal(result.provenance.toolGenerated, false); +}); + +test("explicit tool generation without human verification still fails accountability", () => { + const result = evaluateContribution({ + kind: "pull_request", + title: "fix: handle empty parser input", + body: "Closes #42.\n\nChatGPT generated this patch. It should work.", + authorAssociation: "CONTRIBUTOR", + draft: false, + changedFiles: 2, + additions: 12, + deletions: 3, + files: [ + { filename: "src/parser.mjs", additions: 8, deletions: 3 }, + { filename: "test/parser.test.mjs", additions: 4, deletions: 0 } + ], + checks: [ + { name: "test", conclusion: "success" } + ] + }); + + assert.ok(result.labels.includes("needs-human-verification")); + assert.ok(result.checks.some((check) => check.id === "accountability" && check.status === "fail")); +}); + test("unready issue requires reproducer and real evidence", async () => { const result = evaluateContribution(await fixture("issue-unready")); assert.equal(result.kind, "issue");