authorship-validator
Skill1 starsChanged 5 months agoMITView on GitHub
Authorship-gate validator for interview / take-home code submissions. Answers one question: did the CANDIDATE write this, or did they primarily generate it with an AI assistant and submit it? Walks the 8 static authorship signals plus AI-generation tells (em-dashes, decorative Unicode separators, formatter heterogeneity, orphaned-but-correct features, verbatim public-source matches), runs GitHub Code Search on distinctive phrases, then designs the line-grounded in-person pressure-test that actually settles it. Read + report only — never edits source. Writes AUTHORSHIP_REVIEW_<ref>.md. Use when the user says "did the candidate write this or use AI", "validate authorship", "/authorship-validator", "is this AI-generated", "authorship gate/review", or "who actually wrote this".
In this repository24 other files it gives its agents.
- auth-setupSkill
- code-reviewSkill
- design-reviewSkill
- design-systemSkill
- devex-reviewSkill
- diagram-siteSkill
- doc-syncSkill
- file-followup-issuesSkill
- handoffSkill
- idea-reviewSkill
- investigate-bugSkill
- plan-reviewSkill
- pr-commentSkill
- pre-merge-reviewSkill
- qa-fixSkill
- qa-reportSkill
- qaSkill
- scope-reviewSkill
- semantic-compressorSkill
- shipSkill
- sql-performanceSkill
- team-retroSkill
- ui-polishSkill
- weekly-planSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 3 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 69
Do NOT edit source. Do NOT touch interview_result.md, REVIEWED.md, or any other review file.
- Distinctive: line 76
The pressure-test decides: Every report closes with a concrete, line-grounded, 20–30 minute in-person plan.
- Distinctive: line 77
Authorship is a gate, not a score: Never propose moving the technical score.
- Distinctive: line 220
Every question must cite a real file:line you verified.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Authorship Validation
Gather context first
This skill ships standalone, so it can't assume your project's conventions, house style, or preferences the way it could for its author. Before doing the main work:
- Auto-detect what you safely can from the repo — language/stack, base branch, build/test commands, existing config and docs. Never ask for something you can read for yourself.
- Ask, don't assume, for the rest. Where an input, convention, or preference would change the result and you can't reliably detect it, ask ONE concise
AskUserQuestion(put a sensible default first, labelled Recommended) instead of guessing. The user has less context than this skill's author assumed — a wrong silent default is worse than a quick question. Don't ask about things you can detect, and don't ask more than you need.
For this skill, confirm up front (only the items you can't already detect):
- Submission scope + the starter-code boundary — which directory/files are the candidate's own work versus take-home scaffold or starter code you must NOT attribute to them. Auto-detect the repo root; ask when the boundary is unclear, because judging provided scaffold as their authorship corrupts every signal.
- Claimed language/level + the take-home brief — what they were asked to build and at what seniority. Skill-ceiling discrepancy, README-absence, and orphaned-feature signals are meaningless without this, and it can't be read from the code.
- The
<ref>label + report destination — what to nameAUTHORSHIP_REVIEW_<ref>.md(candidate handle / ticket /submission) and whether to write the file at all or just report the verdict inline. - Depth/mode — full walk (8 signals + verbatim public-source search + pressure-test design) versus a static-only lean, and whether GitHub Code Search / WebSearch is actually available for the verbatim sweep (if not, say so rather than silently skipping it).
How this runs
This skill lives in the main conversation, and inline is the default. The interactive spine has to stay here regardless of mode: the scope check (Process step 0), any clarifying AskUserQuestion, the verdict, and the final gate ceremony all require talking to the user.
Two stretches of the job are pure non-interactive compute — the 8-signal walk and the verbatim public-source sweep. By default you grind through both inline, in this context. They are also the two stretches that parallelize cleanly, so they are the natural opt-in targets:
| Part | If the user opts into fan-out | Why it parallelizes |
| The 8 static signals + AI tells | one agent per signal, each returning that signal's verdict (✓/⚠/✗) with evidence | 8 independent passes over the same repo; running them serially is just latency |
| Verbatim public-source search (signal 8) | one agent per distinctive phrase | a single phrase rarely surfaces the source; independent queries cover more ground |
Before spawning any subagent or Workflow, stop and ask the user — for example:
This submission is large ([N files / many signals]). I can do the 8-signal walk and verbatim search inline here (cheaper, slower) or fan them out as [M] parallel agents (faster, more tokens). Which do you want?
Spawn subagents/workflows only after an explicit yes. If the user declines or doesn't answer, do the whole job inline.
If — and only if — the user opts into fan-out: Workflow agent() calls run headless in the background. They cannot call AskUserQuestion, cannot talk to the user, and cannot write files. So every gate, scope check, clarifying question, and every file write (AUTHORSHIP_REVIEW_<ref>.md) stays in the main loop. The workflow only reads code, marks signals, and runs searches, then hands back structured results; you do the weighing, pick the verdict, design the pressure-test, and write the report. Set model: 'opus' on every agent() call (never let a lighter tier leak in), and pass everything in through args — Workflow scripts have no Date.now / Math.random / filesystem access, so timestamps and file writes only happen back in the main loop.
Arguments
Read $ARGUMENTS:
- A candidate name / handle / ticket / PR number / commit ref → use it as
<ref>for the output fileAUTHORSHIP_REVIEW_<ref>.md. - A submission root / file path → scope the review to that directory or file.
- A mode hint — "static only" / "report only" → deliver the static lean plus the pressure-test design without over-claiming certainty beyond what code review can support (that ceiling is the default anyway).
- Empty → default the submission root to the repo cwd and
<ref>tosubmission(or the candidate's name, if you know it), but confirm both in the scope step if the repo holds more than the candidate's own work or the candidate's identity is unclear.
Philosophy
There is exactly one question to answer: did the candidate write this code, or did they mostly generate it with an AI and hand it in?
Static review of code-as-artifact cannot prove authorship. A clean, correct, well-tested submission could be entirely hand-typed, entirely AI-generated, borrowed from a friend, or any blend of the three. The signals below mark suspicion and direction — nothing more. The one filter that actually resolves it is the in-person pressure-test. So the work splits in two: (1) weigh the static signals honestly without over-claiming; (2) design the concrete pressure-test that closes the question. That pressure-test design is the real deliverable — build it like the product, not a footnote.
You do NOT score the code (that belongs to the /review-interview rubric) and you do NOT move the technical score. You settle the authorship reasoning and hand back a gate recommendation.
Do NOT edit source. Do NOT touch interview_result.md, REVIEWED.md, or any other review file. The only thing you write is AUTHORSHIP_REVIEW_<ref>.md.
Prime Directives
- Consistency is NOT evidence of human authorship. The classic reviewer mistake: "every signal is internally consistent → must be human." A single AI pass is also internally consistent — usually more so. Uniform style, uniform comment cadence, uniform error-handling shape, one tidy commit: all of that fits one generation at least as well as iterative human work. Consistency carries almost no information. Do not bank it as pro-human.
- The human fingerprint is inconsistency where a generator would be uniform. Hunt for the spots a from-scratch AI pass would smooth over but this submission did not: formatter failures clustered in some files and not others (an import auto-dropped at the cursor instead of alphabetized — a hand-editing tell); one helper written in a different idiom than its neighbors; a TODO to self; a
FIXME; a comment justifying a non-obvious local decision. These earned, idiosyncratic, locally-motivated traces lean human. Their absence on otherwise-polished work leans generated. - Static review tops out at MEDIUM confidence. Never declare "definitely human" or "definitely AI" with HIGH confidence on code alone. A verbatim public-source match (3+ disjoint hits on one tutorial) lets you go higher on the AI/copied side. Short of that, the honest ceiling is MEDIUM, and what you hand the hiring team is "here's the lean plus the gate that settles it."
- The pressure-test decides. Every report closes with a concrete, line-grounded, 20–30 minute in-person plan. No hand-waving "ask them about their code." Point at real lines in this repo.
- Authorship is a gate, not a score. Never propose moving the technical score. A perfect-scoring submission can still fail the gate; a middling one can clear it.
Signals that are NOT diagnostic (inference traps — name them, then discount)
- Internal consistency (Directive 1).
- Form/Thought asymmetry BALANCE. A skew (high form %, low thought %) flags form-over-thought — a paste of disparate sources, or a non-coder leaning on AI for the easy parts. But a balanced split is NOT pro-human; a fully-generated submission balances too. Balance → neutral, not human.
- A lone bulk commit on its own. Candidates routinely squash before submitting. Weak by itself; it needs corroboration (
git log --shortstat: genuine iterative work touches each file 3–8 times, a one-shot paste touches each file once). Single-commit + each-file-touched-once + a message ending infinish/final/initialize, all on one day, is a far stronger combination than commit count alone.
The 8 static signals (walk every one, mark ✓ no-concern / ⚠ mild / ✗ strong)
- Skill-ceiling discrepancy. Production-grade moves (JWT method-check, bcrypt cost tuning, timing-attack defense, transactional writes, hexagonal layering) sitting next to beginner mistakes (free-text dates, no search LIMIT, no input validation,
alg:none-vulnerable parse). Real authors hold a consistent ceiling — they know both the senior moves and the basics, or neither. Sharp discontinuities point to a paste of disparate sources. - Comment-to-code voice. BOTH extremes are AI-shaped: zero comments, OR verbose comments that just restate the signature ("this function validates the input"). Real authors leave some voice — a TODO, a FIXME, a comment that explains one non-obvious choice.
- Naming consistency. Mixed acronym casing inside one struct (
NationalIdbesidePatientHN) = paste from multiple sources. Internally consistent casing, even if non-idiomatic, leans single-author. - Test-to-code skill match. Tests shouldn't out-skill the code they cover (or the reverse). Mid-tier test idioms wrapped around beginner-mistake handlers is suspicious.
- Git history shape.
git log --oneline+git log --shortstat. Iterative work (20–50 commits across the take-home window, each file touched 3–8 times) leans human; one bulk drop touching each file once leans paste. Squash caveat applies (see traps). - README absence + sophisticated everything else. Someone who ships transactional GORM with sentinel errors knows what a README is. A conspicuous absence on otherwise-polished work hints at "I didn't write the rest either."
- Tutorial / sample-app artifacts.
MyUser/ExampleHandlernames, leftover example data, commented-out sample chunks, unused imports that imply paste from a bigger codebase, identical phrasing across 3+ disjoint comment blocks. - Verbatim match to public sources. Actually run it (WebSearch / GitHub Code Search) on 2–3 distinctive phrases: a custom error message, a non-trivial helper signature, a distinctive query expression, an idiosyncratic comment. 3+ disjoint verbatim hits on one tutorial = that's the source. Log the searches you ran and the hits.
Additional AI-generation tells (modern-model signatures)
- Em-dashes (—) in prose / comments / README. A strong typographic signature of model output, especially when clustered in boilerplate, tests, and docs.
- Decorative Unicode separators in source/tests: box-drawing rules (
─── section ───,═══), check/cross glyphs as section markers, sparkle/emoji headers. Humans rarely hand-type these as comment dividers. {" "}JSX whitespace-artifact nodes (frontend) —grep -rn '{" "}' src/; more than 5 hits inside JSX blocks is a cheap "not hand-written" signal.- Over-uniform comment cadence — every function fronted by a one-line WHAT-comment of identical shape.
- Formatter heterogeneity = human fingerprint (read the direction correctly). A from-scratch AI pass emits uniformly formatter-clean output (
gofmt/prettier/black). So: uniformly clean formatting is neutral-to-slightly-generated; file-by-file formatter failures (an import wedged inside the stdlib block in some files but not others, not even sorted) are a hand-editing tell — they lean human. Run the formatter in-l/check mode and look at WHERE it fails. - Orphaned-but-correct features. A feature built correctly but never wired into the request path is genuinely ambiguous: human-ran-out-of-time vs. AI-generated-a-feature-it-never-integrated. Do NOT read authorship off it — make it the #1 live pressure-test ("wire it now").
- "Textbook" structure with zero idiosyncrasy — the canonical layout straight from the framework docs, no personal organizational quirks anywhere.
Process
- Confirm scope. Auto-detect what you can — repo root, language/stack, git history, any existing prior-review file (
interview_result.md,REVIEWED.md, or similar) — then batch whatever's left from the "Gather context first" list (starter-code boundary, claimed language/level + brief,<ref>+ report destination, depth/mode) into ONE clarifyingAskUserQuestionwith sensible defaults. If a prior review exists, read it — but treat its authorship conclusion as a hypothesis to challenge, not a fact to inherit. (Main loop — the clarifyingAskUserQuestionhappens here.) - Read the code yourself. Open the highest-logic files (services, handlers, middleware, models, tests, entrypoint, Dockerfile, infra, README, API docs). Never trust a quoted snippet — verify every cited file:line by opening it.
- Git forensics.
git log --oneline --all,git log --shortstat, and where it helpsgit log -pon key files. Note commit count, time spread, per-file touch counts, message style. - Walk all 8 signals + the AI tells. Mark each ✓/⚠/✗ with one sentence of evidence (file:line) on any ⚠/✗. Default: do this inline, signal by signal, in this context. If the user opted into fan-out, run it as the parallel-signal Workflow below instead, then consolidate the ✓/⚠/✗ table in the main loop.
- Run the verbatim search. Log the queries and hits (or clean). Default: run the 2–3 phrase searches inline. If the user opted into fan-out, dispatch them as the search-sweep Workflow below.
- Discount the non-diagnostic traps explicitly (consistency, asymmetry balance, lone single-commit). State that you did.
- Weigh + verdict. Choose the lean and the confidence. Name the single most decisive piece of evidence in either direction.
- Design the pressure-test (below).
Signal walk — opt-in parallel fan-out
By default, walk the 8 signals (with the AI tells folded into the relevant ones) inline, one after another, marking each ✓/⚠/✗ as you go. They are independent passes over the same submission, so on a large job they fan out cleanly. Only if the user opted into subagents, run this Workflow so each signal is evaluated in parallel by an independent agent, then consolidate the ✓/⚠/✗ table in the main loop. Background agents read the repo and return findings only — they never ask the user anything and never write files.
export const meta = {
name: 'authorship-validator-signals',
description: 'Parallel walk of the 8 static authorship signals plus AI tells over a submission',
phases: [{ title: 'Analyze' }],
}
const SIGNAL_SCHEMA = {
type: 'object',
properties: {
signal: { type: 'string' },
mark: { type: 'string', enum: ['no-concern', 'mild', 'strong'] }, // ✓ / ⚠ / ✗
evidence: { type: 'string' }, // one sentence, file:line on any concern
citations: { type: 'array', items: { type: 'string' } }, // verified file:line refs
leans: { type: 'string', enum: ['human', 'generated', 'neutral'] },
},
required: ['signal', 'mark', 'evidence', 'leans'],
}
const SIGNALS = [
{ key: 'skill-ceiling', spec: 'Skill-ceiling discrepancy: production-grade moves coexisting with beginner mistakes. Sharp discontinuities suggest paste from disparate sources. Real authors are consistent in their ceiling.' },
{ key: 'comment-voice', spec: 'Comment-to-code voice: BOTH zero comments AND signature-paraphrasing comments are AI-shaped. Real authors leave some voice (TODO, FIXME, a comment explaining one non-obvious choice). Also flag em-dashes in prose/comments and over-uniform one-line WHAT-comment cadence.' },
{ key: 'naming', spec: 'Naming consistency: mixed acronym casing within one struct (e.g. NationalId next to PatientHN) = paste from multiple sources. Internally consistent casing (even if non-idiomatic) leans single-author.' },
{ key: 'test-skill', spec: 'Test-to-code skill match: tests should not out-skill the code they test or vice versa. Mid-tier test idioms wrapping beginner-mistake handlers is suspicious.' },
{ key: 'git-shape', spec: 'Git history shape: run git log --oneline and git log --shortstat. Iterative (20-50 commits, each file touched 3-8 times) leans human; one bulk drop touching each file once leans paste. Squash caveat: single-commit alone is weak; corroborate with each-file-touched-once + message ending in finish/final/initialize, all one day.' },
{ key: 'readme', spec: 'README absence + sophisticated everything else: a candidate who ships transactional code with sentinel errors knows what a README is. Conspicuous absence on otherwise-polished work suggests they did not write the rest either.' },
{ key: 'tutorial-artifacts', spec: 'Tutorial / sample-app artifacts: MyUser/ExampleHandler names, leftover example data, commented-out sample chunks, unused imports suggesting paste from a larger codebase, identical phrasing across 3+ disjoint comment blocks. Also decorative Unicode separators (box-drawing rules, check/cross glyphs, sparkle/emoji headers) and >5 {" "} JSX whitespace nodes.' },
{ key: 'formatter-orphans', spec: 'Formatter heterogeneity + orphaned features: run the formatter in -l/check mode and inspect WHERE it fails. Uniformly clean = neutral-to-slightly-generated; file-by-file formatter failures = hand-editing tell, leans human. Orphaned-but-correct features (implemented, never wired) are AMBIGUOUS — do not conclude authorship; flag as a live pressure-test candidate. Also flag textbook structure with zero idiosyncrasy.' },
]
const findings = await parallel(SIGNALS.map(s => () =>
agent(
`You are evaluating ONE authorship signal over the code submission at ${args.root}. ` +
`Do NOT ask the user anything and do NOT write any files — return findings only; the main loop ` +
`consolidates and writes the report afterward.\n\n` +
`CANDIDATE CONTEXT: ${args.context}\n\n` +
`SIGNAL TO EVALUATE (${s.key}):\n${s.spec}\n\n` +
`Read the relevant files yourself and verify every cited file:line by opening it — never trust a ` +
`quoted snippet. Mark this signal no-concern (✓) / mild (⚠) / strong (✗), give one sentence of ` +
`evidence (with file:line on any concern), list the verified citations, and state whether it leans ` +
`human, generated, or neutral. Remember: internal consistency is NOT evidence of human authorship.`,
{ label: `signal:${s.key}`, phase: 'Analyze', model: 'opus', schema: SIGNAL_SCHEMA }
)
))
return findings.filter(Boolean)Pass args: { root: "<submission root path>", context: "<claimed language/level + brief, if any>" }. When the workflow returns, assemble the 8-signal ✓/⚠/✗ table in the main loop. If the workflow fails or times out, fall back to walking the signals inline.
Verbatim search — opt-in parallel sweep (signal 8)
Pull 2–3 distinctive phrases from the submission (a custom error message, a non-trivial helper signature, a distinctive query expression, an idiosyncratic comment). By default, search them inline, one at a time. 3+ disjoint verbatim hits on one tutorial = that's the source. Only if the user opted into subagents, fan the phrases out so each is searched in parallel by an independent agent via this Workflow.
export const meta = {
name: 'authorship-validator-verbatim',
description: 'Parallel verbatim public-source search — one distinctive phrase per agent',
phases: [{ title: 'Search' }],
}
const HIT_SCHEMA = {
type: 'object',
properties: {
phrase: { type: 'string' },
hits: {
type: 'array',
items: {
type: 'object',
properties: { title: { type: 'string' }, url: { type: 'string' }, verbatim: { type: 'boolean' } },
required: ['title', 'url', 'verbatim'],
},
},
source_suspected: { type: 'string' }, // the tutorial/repo if 3+ disjoint hits converge, else empty
},
required: ['phrase', 'hits', 'source_suspected'],
}
const results = await parallel(args.phrases.map(p => () =>
agent(
`Run a web / GitHub Code Search for this exact phrase pulled from a code submission, to find whether ` +
`it was copied verbatim from a public source. Phrase: ${JSON.stringify(p.text)} (origin: ${p.origin}). ` +
`Report each hit (title, url, and whether the match is verbatim vs paraphrased). If 3+ disjoint hits ` +
`converge on one tutorial or repo, name it as the suspected source; otherwise leave it empty (clean).`,
{ label: `verbatim:${p.key}`, phase: 'Search', model: 'opus', schema: HIT_SCHEMA }
)
))
return results.filter(Boolean)Pass args: { phrases: [{ key, text, origin }, ...] } where origin is the file:line the phrase came from. When the workflow returns, fold the queries-and-hits log into the report in the main loop. If WebSearch is unavailable, skip the search entirely and note: "Verbatim search unavailable — static signals only."
Verdict scale
AUTHORED-BY-CANDIDATE / MIXED (AI-assisted) / LIKELY-AI-GENERATED / CANNOT-DETERMINE-STATICALLY — each paired with a confidence word and the concrete evidence behind it. Most honest static-only verdicts land at MIXED or CANNOT-DETERMINE, MEDIUM confidence; that is fine and expected. Say so plainly instead of manufacturing false certainty.
Pressure-test design (the deliverable)
A concrete 20–30 minute in-person plan, every item grounded in a real line in this repo:
- Block A — wire/change something live (decisive). Point at a specific orphan, stub, or feature and have them integrate or modify it while you watch — e.g., "
SyncPatientFromHIS(patient_service.go:165) is never called; open the handler and wire fetch-on-local-miss now." This forces them to hold the real data flow in working memory; a generator-only candidate stalls, scrolls aimlessly, or can't navigate their own structure. - Block B — explain their own anomalies. Pick 2–3 idiosyncrasies only the author could account for: "four files fail
gofmton imports, two pass — why?", "your JWT secret defaults to'secret'— what happens on a baredocker run?", "this test asserts X — why X and not Y?" A hand-editor answers in a sentence; a non-author can't explain the specific split. - Block C — extend under a new requirement. A small live change that wasn't in the submission ("add pagination to search", "make the secret fail-fast"), to see whether they can write code in this codebase's idiom on the spot.
- Red-flag responses to watch for: can't locate code they "wrote" within ~90 seconds; explains code that isn't there; "I'd have to look that up" for their own core decision; narrates the framework's generic behavior instead of their specific choices; reaches to re-prompt an AI rather than edit.
Every question must cite a real file:line you verified.
Output
Mandatory write-then-report protocol — follow in this exact order. Do NOT compose your return summary until the file is written and verified. All writes happen in the main loop — Workflow agents cannot write files.
- Call the
Writetool to createAUTHORSHIP_REVIEW_<ref>.mdin the submission root (<ref>= candidate name/handle, ticket, orsubmission; use whatever name/location the user confirmed in the scope step). Unless the user asked for an inline-only verdict, this is a hard requirement — the full report lives in the file, not only in your return message. - Verify the write succeeded. Confirm the
Writetool returned a success result. If you are at all unsure, runls -1 AUTHORSHIP_REVIEW_*.md(ortest -f <path> && echo OK) via Bash and confirm the file exists at the path you intend to report. - Only after the file is confirmed written may you state, in your return summary, that you wrote it — and you must report the actual path the
Writetool created. Never claim a file was written unless the correspondingWritetool call succeeded in this session. If the write failed or you skipped it, say so explicitly and do not fabricate a path.
File structure: verdict header (verdict + confidence + single most decisive evidence) → 8-signal table with evidence → AI-tells table → verbatim-search log → non-diagnostic-traps discounted → pressure-test plan (Blocks A/B/C with line citations + red flags) → gate recommendation (proceed / run pressure-test before finalizing / reject).
Return a tight summary to the caller: the confirmed report path + verdict + confidence + the single most decisive piece of evidence + the verbatim-search result + the top 3 pressure-test questions (each with its file:line). Never claim more certainty than static review supports, and never claim more file output than you actually produced.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

