agentleFS
napnap11napnap11/claude-skills/authorship-validator/SKILL.md

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

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
  1. Very specific:

    Do NOT edit source. Do NOT touch interview_result.md, REVIEWED.md, or any other review file.

    line 69
  2. Distinctive:

    The pressure-test decides: Every report closes with a concrete, line-grounded, 20–30 minute in-person plan.

    line 76
  3. Distinctive:

    Authorship is a gate, not a score: Never propose moving the technical score.

    line 77
  4. Distinctive:

    Every question must cite a real file:line you verified.

    line 220

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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:

  1. 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.
  2. 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 name AUTHORSHIP_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:

PartIf the user opts into fan-outWhy it parallelizes
The 8 static signals + AI tellsone agent per signal, each returning that signal's verdict (✓/⚠/✗) with evidence8 independent passes over the same repo; running them serially is just latency
Verbatim public-source search (signal 8)one agent per distinctive phrasea 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 file AUTHORSHIP_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> to submission (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

  1. 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.
  2. 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.
  3. 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."
  4. 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.
  5. 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 in finish/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)

  1. 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.
  2. 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.
  3. Naming consistency. Mixed acronym casing inside one struct (NationalId beside PatientHN) = paste from multiple sources. Internally consistent casing, even if non-idiomatic, leans single-author.
  4. 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.
  5. 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).
  6. 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."
  7. Tutorial / sample-app artifacts. MyUser/ExampleHandler names, leftover example data, commented-out sample chunks, unused imports that imply paste from a bigger codebase, identical phrasing across 3+ disjoint comment blocks.
  8. 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

  1. 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 clarifying AskUserQuestion with 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 clarifying AskUserQuestion happens here.)
  2. 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.
  3. Git forensics. git log --oneline --all, git log --shortstat, and where it helps git log -p on key files. Note commit count, time spread, per-file touch counts, message style.
  4. 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.
  5. 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.
  6. Discount the non-diagnostic traps explicitly (consistency, asymmetry balance, lone single-commit). State that you did.
  7. Weigh + verdict. Choose the lean and the confidence. Name the single most decisive piece of evidence in either direction.
  8. 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 gofmt on imports, two pass — why?", "your JWT secret defaults to 'secret' — what happens on a bare docker 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.

  1. Call the Write tool to create AUTHORSHIP_REVIEW_<ref>.md in the submission root (<ref> = candidate name/handle, ticket, or submission; 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.
  2. Verify the write succeeded. Confirm the Write tool returned a success result. If you are at all unsure, run ls -1 AUTHORSHIP_REVIEW_*.md (or test -f <path> && echo OK) via Bash and confirm the file exists at the path you intend to report.
  3. 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 Write tool created. Never claim a file was written unless the corresponding Write tool 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.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.