Generated banner for Audit Reference Originality
Generated logo for Audit Reference Originality

Audit Reference Originality

Meng ToCommunityCreated by Meng To

  • Skill
  • Review and audit
  • Software development
  • MIT
  • Low risk
  • Has scripts

From the author

Audit a website or digital experience against its supplied source references for originality and plagiarism risk. Use when Codex must compare current or historical site output with reference pages, capture packs, screenshots, copy, brands, numbers, images, assets, videos, layouts, motion, or code; raise evidence-backed red flags; distinguish common visual grammar from distinctive copying; and propose concrete fixes without making unsupported legal claims.
— the description by Meng To

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured methodology for auditing a website or digital experience against a corpus of reference materials. It focuses on evidence-based identification of potential originality risks, such as copied text, brand assets, imagery, video, and structural elements. The process emphasizes distinguishing between common design patterns and distinctive copying, requiring users to pair red flags with specific evidence from both the current site and the reference materials. It includes guidance on using automated inventory tools, manual inspection techniques, and a severity-based rubric to prioritize findings and propose concrete, non-cosmetic fixes.

What it does

  • Compares current site output against a provided registry of reference materials.
  • Identifies originality risks across text, brands, numbers, images, assets, video, and structure.
  • Uses an evidence-based approach to flag potential issues without making legal claims.
  • Provides a severity-based rubric to categorize findings from low-risk to blockers.
  • Suggests concrete, substantive fixes for identified overlaps.
  • Generates a comprehensive audit report including findings, history, and fix plans.

When to use it

  • When verifying that a new digital experience is original and not inadvertently copying source materials.
  • Before a major release to ensure no stale or unauthorized source content remains.
  • When investigating potential plagiarism or brand identity overlap in a project.
  • To audit historical site versions and repository artifacts for copied content.

Advantages

  • Reduces risk of shipping unauthorized or copied content.
  • Provides a clear, evidence-based framework for objective auditing.
  • Distinguishes between standard design grammar and actual originality risks.
  • Encourages substantive fixes rather than superficial masking.
  • Supports both automated inventory and deep manual inspection.

Usage examples

“Audit the current staging site against the provided brand guidelines and competitor reference pack.”
The agent will inventory the staging site, compare it against the reference pack, and produce a report detailing any red flags, their severity, and recommended fixes.
“Check if our new landing page copy or layout overlaps with the source project files.”
The agent will perform a category-by-category comparison of text and structure, flagging any verbatim or near-verbatim overlaps with evidence.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/mengto/audit-reference-originality/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip audit-reference-originality.zip -d .agents/skills

Documentation by Meng To

Audit Reference Originality

Compare the shipped experience with the complete reference corpus. Treat the audit as an evidence exercise, not a vibe check.

Preserve the audit boundary

  • Audit only unless the user also asks for fixes.

  • Call findings originality risks, overlaps, or red flags; do not declare legal plagiarism from visual similarity alone.

  • Pair every red flag with exact current-site evidence and exact reference evidence.

  • Keep fact, inference, and unknown access separate.

  • Do not clear a site after checking only its homepage screenshot.

1. Build the source registry

Start with the materials explicitly supplied in the task or recorded by the project:

  • manifests, prompt packs, and originality matrices

  • representative stills, full-page captures, and section crops

  • MP4s and extracted motion frames

  • reference URLs and named creators

  • source brand assets, copy, numbers, screenshots, and downloads

  • project briefs, IMAGE_CREDITS.md, licenses, and attribution files

Prefer local evidence captured at the time of the brief. Use a live reference only to fill a real gap, because it may have changed. Record each reference's path or URL, role, date when known, and which categories it can prove.

Stop and report an access gap when a promised reference is missing. Do not silently reduce a full-page or motion audit to one cover image.

2. Inventory the current site and its history

Inspect:

  • rendered text, metadata, navigation, calls to action, legal copy, and hidden accessible labels

  • brand names, wordmarks, logos, icons, people, companies, URLs, and product/interface data

  • prices, metrics, dates, counts, percentages, package names, and repeated proof claims

  • every rendered image, background, texture, screenshot, font, logo, icon, audio file, and downloadable asset

  • every video, poster, frame sequence, shot order, transition, duration, and playback treatment

  • layout hierarchy, section order, distinctive compositions, typography behavior, motion grammar, shaders, cursor effects, and interaction sequences

  • current source, built output when available, asset provenance, and repository history

Do not trust filenames as proof of originality. Inspect the bytes, visible result, and history. Search renamed, deleted, and replaced files with git log, git show, git log -S, and git log --all --name-status.

Run the deterministic inventory helper when local files are available:

python <skill-dir>/scripts/build_evidence_inventory.py \
  --site <site-root> \
  --reference <reference-file-or-directory> \
  --reference <another-reference> \
  --output <temporary-output.json>

The helper finds current and historical exact-file matches, suspicious basename reuse, normalized text overlap, and repeated number tokens. Treat its output as leads for human review, not an automatic verdict.

3. Compare category by category

Read references/audit-rubric.md before judging findings.

Audit at least these categories:

  1. Text — headlines, body copy, labels, CTAs, captions, legal text, alt text, metadata, and decorative wording.

  2. Brands — names, marks, wordmarks, proprietary icons, people, companies, partnerships, URLs, and distinctive verbal identity.

  3. Numbers — metrics, percentages, prices, dates, counts, plan structures, durations, and interface values.

  4. Images — exact files, crops, generated derivatives, screenshots, people, poses, objects, signature compositions, and color treatment.

  5. Assets — fonts, icons, logos, textures, mockups, downloads, code bundles, and third-party media with unclear provenance.

  6. Videos — exact files, frames, shots, timing, camera moves, edit rhythm, transitions, overlays, posters, and audio.

  7. Structure and motion — section order, unusual layout devices, pinned sequences, cursor interactions, shaders, and combinations of signature elements.

  8. History — copied material that was later renamed, recolored, cropped, hidden, deleted, or replaced.

Common patterns such as black backgrounds, large sans-serif type, ordinary pricing tables, standard fade-ins, or a conventional footer are not red flags by themselves. Escalate combinations of distinctive elements or direct evidence.

4. Triangulate every red flag

For each candidate:

  1. Identify the current-site artifact and location.

  2. Identify the exact reference artifact and location.

  3. State the observable overlap without guessing intent.

  4. State what differs.

  5. Assign severity using the rubric.

  6. Propose the smallest fix that breaks the overlap while preserving the site's goal.

Use hashes for exact files, normalized excerpts for copy, side-by-side crops for imagery, and matched timestamps or frames for video. A source-brand string found only in a test that explicitly forbids it is not a shipped-copy violation; explain context.

5. Propose fixes

Prefer concrete replacements:

  • rewrite source-like copy from the new brand's audience, offer, and vocabulary

  • replace names, URLs, logos, people, metrics, dates, plan names, and legal text

  • regenerate or license new imagery with a materially different subject, composition, and motif arrangement

  • replace copied assets and document provenance

  • re-cut videos with new shots, timing, transitions, overlays, and audio

  • reorder or redesign distinctive section and motion sequences

  • remove stale source material from current output and, when required, repository history or published artifacts

Do not recommend cosmetic recoloring as a fix for copied identity, copy, media, or composition.

6. Report the result

Lead with one verdict:

  • Clear in checked scope

  • Clear with low-risk similarities

  • Changes recommended

  • Block release

  • Blocked by missing evidence

Then provide:

  1. checked source registry

  2. red-flag table ordered by severity

  3. category pass list

  4. history findings

  5. access gaps and unproven areas

  6. prioritized fix plan

Include a row even when a high-risk category could not be checked. Never turn missing evidence into a pass.

Completion checks

  • Every supplied reference form was inspected.

  • Rendered output and source were both checked.

  • Text, brands, numbers, images, assets, videos, structure/motion, and history were covered.

  • Every red flag cites two evidence locations.

  • Exact matches are distinguished from stylistic similarity.

  • Proposed fixes replace the copied element rather than disguising it.

  • The report states what remains unverified.

Files

scripts/build_evidence_inventory.py

#!/usr/bin/env python3
"""Build deterministic originality-audit leads from a site and references."""

from __future__ import annotations

import argparse
import hashlib
import json
import re
import subprocess
import sys
from collections import Counter, defaultdict
from datetime import datetime, timezone
from pathlib import Path
from typing import Iterable


TEXT_EXTENSIONS = {
    ".css",
    ".csv",
    ".htm",
    ".html",
    ".js",
    ".json",
    ".jsonl",
    ".jsx",
    ".md",
    ".mdx",
    ".scss",
    ".svg",
    ".toml",
    ".ts",
    ".tsx",
    ".txt",
    ".xml",
    ".yaml",
    ".yml",
}
IMAGE_EXTENSIONS = {
    ".avif",
    ".bmp",
    ".gif",
    ".jpeg",
    ".jpg",
    ".png",
    ".svg",
    ".tif",
    ".tiff",
    ".webp",
}
VIDEO_EXTENSIONS = {".avi", ".m4v", ".mov", ".mp4", ".mpeg", ".mpg", ".webm"}
SKIP_DIRECTORIES = {
    ".git",
    ".next",
    ".turbo",
    ".wrangler",
    "__pycache__",
    "build",
    "coverage",
    "dist",
    "node_modules",
}
MAX_TEXT_BYTES = 2_000_000
SHINGLE_SIZE = 8
NUMBER_PATTERN = re.compile(
    r"(?<![\w.])(?:[$€£¥]\s*)?\d[\d,.]*(?:\s?%|\s?(?:ms|sec|secs|seconds|minutes|hours|days|weeks|months|years|k|m|b))?(?![\w.])",
    re.IGNORECASE,
)
TOKEN_PATTERN = re.compile(r"[\w€£¥$%]+(?:['’.-][\w€£¥$%]+)*", re.UNICODE)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Inventory exact files, text overlap, number reuse, and Git-history matches."
    )
    parser.add_argument("--site", required=True, type=Path, help="Site project root")
    parser.add_argument(
        "--reference",
        required=True,
        action="append",
        type=Path,
        help="Reference file or directory; repeat as needed",
    )
    parser.add_argument("--output", type=Path, help="Write JSON to this path")
    parser.add_argument(
        "--no-history",
        action="store_true",
        help="Skip the Git-history blob check",
    )
    return parser.parse_args()


def sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as handle:
        for chunk in iter(lambda: handle.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def classify(path: Path) -> str:
    suffix = path.suffix.lower()
    if suffix in VIDEO_EXTENSIONS:
        return "video"
    if suffix in IMAGE_EXTENSIONS:
        return "image"
    if suffix in TEXT_EXTENSIONS:
        return "text"
    return "asset"


def iter_files(targets: Iterable[Path]) -> list[Path]:
    found: list[Path] = []
    for target in targets:
        resolved = target.expanduser().resolve()
        if not resolved.exists():
            raise FileNotFoundError(f"Missing input: {resolved}")
        if resolved.is_file():
            found.append(resolved)
            continue
        for path in resolved.rglob("*"):
            if not path.is_file():
                continue
            if any(part in SKIP_DIRECTORIES for part in path.parts):
                continue
            if path.name == ".DS_Store":
                continue
            found.append(path)
    return sorted(set(found))


def relative_label(path: Path, roots: list[Path]) -> str:
    for root in roots:
        resolved = root.expanduser().resolve()
        if resolved.is_file():
            if path == resolved:
                return resolved.name
            continue
        try:
            return f"{resolved.name}/{path.relative_to(resolved)}"
        except ValueError:
            continue
    return str(path)


def file_record(path: Path, roots: list[Path]) -> dict[str, object]:
    return {
        "path": relative_label(path, roots),
        "bytes": path.stat().st_size,
        "sha256": sha256(path),
        "category": classify(path),
    }


def read_text(path: Path) -> str | None:
    if path.suffix.lower() not in TEXT_EXTENSIONS:
        return None
    if path.stat().st_size > MAX_TEXT_BYTES:
        return None
    try:
        return path.read_text(encoding="utf-8", errors="ignore")
    except OSError:
        return None


def normalized_tokens(text: str) -> list[str]:
    return [match.group(0).lower() for match in TOKEN_PATTERN.finditer(text)]


def shingles(tokens: list[str]) -> set[str]:
    if len(tokens) < SHINGLE_SIZE:
        return set()
    return {
        " ".join(tokens[index : index + SHINGLE_SIZE])
        for index in range(len(tokens) - SHINGLE_SIZE + 1)
    }


def text_overlap(
    site_files: list[Path],
    reference_files: list[Path],
    site_roots: list[Path],
    reference_roots: list[Path],
) -> tuple[list[dict[str, object]], list[dict[str, object]]]:
    reference_index: dict[str, set[str]] = defaultdict(set)
    reference_counts: dict[str, int] = {}
    reference_numbers: dict[str, set[str]] = {}

    for path in reference_files:
        text = read_text(path)
        if text is None:
            continue
        label = relative_label(path, reference_roots)
        file_shingles = shingles(normalized_tokens(text))
        reference_counts[label] = len(file_shingles)
        reference_numbers[label] = {
            match.group(0).strip().lower() for match in NUMBER_PATTERN.finditer(text)
        }
        for shingle in file_shingles:
            reference_index[shingle].add(label)

    overlaps: list[dict[str, object]] = []
    number_overlaps: list[dict[str, object]] = []

    for path in site_files:
        text = read_text(path)
        if text is None:
            continue
        site_label = relative_label(path, site_roots)
        site_shingles = shingles(normalized_tokens(text))
        matches: Counter[str] = Counter()
        examples: dict[str, list[str]] = defaultdict(list)
        for shingle in site_shingles:
            for reference_label in reference_index.get(shingle, ()):
                matches[reference_label] += 1
                if len(examples[reference_label]) < 3:
                    examples[reference_label].append(shingle)
        for reference_label, shared in matches.items():
            if shared < 3:
                continue
            site_ratio = shared / max(1, len(site_shingles))
            reference_ratio = shared / max(1, reference_counts.get(reference_label, 0))
            if max(site_ratio, reference_ratio) < 0.12:
                continue
            overlaps.append(
                {
                    "site_path": site_label,
                    "reference_path": reference_label,
                    "shared_8_word_sequences": shared,
                    "site_containment": round(site_ratio, 4),
                    "reference_containment": round(reference_ratio, 4),
                    "examples": sorted(examples[reference_label]),
                }
            )

        site_numbers = {
            match.group(0).strip().lower() for match in NUMBER_PATTERN.finditer(text)
        }
        for reference_label, values in reference_numbers.items():
            shared_values = sorted(site_numbers & values)
            if shared_values:
                number_overlaps.append(
                    {
                        "site_path": site_label,
                        "reference_path": reference_label,
                        "values": shared_values[:50],
                    }
                )

    overlaps.sort(
        key=lambda item: (
            -int(item["shared_8_word_sequences"]),
            str(item["site_path"]),
            str(item["reference_path"]),
        )
    )
    number_overlaps.sort(key=lambda item: (str(item["site_path"]), str(item["reference_path"])))
    return overlaps, number_overlaps


def exact_matches(
    site_records: list[dict[str, object]],
    reference_records: list[dict[str, object]],
) -> list[dict[str, object]]:
    by_hash: dict[str, list[dict[str, object]]] = defaultdict(list)
    for record in reference_records:
        by_hash[str(record["sha256"])].append(record)
    matches: list[dict[str, object]] = []
    for site_record in site_records:
        for reference_record in by_hash.get(str(site_record["sha256"]), []):
            matches.append(
                {
                    "site_path": site_record["path"],
                    "reference_path": reference_record["path"],
                    "sha256": site_record["sha256"],
                    "category": site_record["category"],
                    "bytes": site_record["bytes"],
                }
            )
    return matches


def basename_matches(
    site_files: list[Path],
    reference_files: list[Path],
    site_roots: list[Path],
    reference_roots: list[Path],
) -> list[dict[str, str]]:
    reference_names: dict[str, list[Path]] = defaultdict(list)
    for path in reference_files:
        reference_names[path.name.lower()].append(path)
    matches: list[dict[str, str]] = []
    for site_path in site_files:
        for reference_path in reference_names.get(site_path.name.lower(), []):
            matches.append(
                {
                    "site_path": relative_label(site_path, site_roots),
                    "reference_path": relative_label(reference_path, reference_roots),
                    "basename": site_path.name,
                }
            )
    return matches


def run_git(site_root: Path, args: list[str], input_bytes: bytes | None = None) -> bytes:
    return subprocess.check_output(
        ["git", "-C", str(site_root), *args],
        input=input_bytes,
        stderr=subprocess.DEVNULL,
    )


def git_history_matches(
    site_root: Path,
    reference_files: list[Path],
    reference_roots: list[Path],
) -> list[dict[str, object]]:
    try:
        run_git(site_root, ["rev-parse", "--is-inside-work-tree"])
        objects_output = run_git(site_root, ["rev-list", "--objects", "--all"]).decode(
            "utf-8", errors="replace"
        )
    except (subprocess.CalledProcessError, FileNotFoundError):
        return []

    historical_paths: dict[str, set[str]] = defaultdict(set)
    for line in objects_output.splitlines():
        oid, _, path = line.partition(" ")
        if path:
            historical_paths[oid].add(path)

    matches: list[dict[str, object]] = []
    for reference_path in reference_files:
        try:
            oid = run_git(
                site_root,
                ["hash-object", "--stdin"],
                input_bytes=reference_path.read_bytes(),
            ).decode("ascii").strip()
        except (OSError, subprocess.CalledProcessError):
            continue
        paths = sorted(historical_paths.get(oid, set()))
        if paths:
            matches.append(
                {
                    "reference_path": relative_label(reference_path, reference_roots),
                    "git_blob": oid,
                    "historical_site_paths": paths,
                }
            )
    return matches


def category_counts(records: list[dict[str, object]]) -> dict[str, int]:
    return dict(sorted(Counter(str(record["category"]) for record in records).items()))


def main() -> int:
    args = parse_args()
    site_root = args.site.expanduser().resolve()
    reference_roots = [path.expanduser().resolve() for path in args.reference]
    if not site_root.is_dir():
        print(f"Site root is not a directory: {site_root}", file=sys.stderr)
        return 2

    try:
        site_files = iter_files([site_root])
        reference_files = iter_files(reference_roots)
    except FileNotFoundError as error:
        print(str(error), file=sys.stderr)
        return 2

    site_records = [file_record(path, [site_root]) for path in site_files]
    reference_records = [
        file_record(path, reference_roots) for path in reference_files
    ]
    overlaps, number_overlaps = text_overlap(
        site_files,
        reference_files,
        [site_root],
        reference_roots,
    )
    report = {
        "generated_at": datetime.now(timezone.utc).isoformat(),
        "site_root": str(site_root),
        "reference_roots": [str(path) for path in reference_roots],
        "summary": {
            "site_files": len(site_records),
            "reference_files": len(reference_records),
            "site_categories": category_counts(site_records),
            "reference_categories": category_counts(reference_records),
        },
        "exact_file_matches": exact_matches(site_records, reference_records),
        "historical_exact_matches": (
            []
            if args.no_history
            else git_history_matches(site_root, reference_files, reference_roots)
        ),
        "basename_matches": basename_matches(
            site_files,
            reference_files,
            [site_root],
            reference_roots,
        ),
        "text_overlap_leads": overlaps,
        "number_overlap_leads": number_overlaps,
        "notes": [
            "Leads are not automatic plagiarism findings.",
            "Review rendered context, licenses, visual composition, and video motion manually.",
            "Generic numbers and common functional phrases may be false positives.",
        ],
    }
    output = json.dumps(report, indent=2, ensure_ascii=False) + "\n"
    if args.output:
        args.output.parent.mkdir(parents=True, exist_ok=True)
        args.output.write_text(output, encoding="utf-8")
    else:
        sys.stdout.write(output)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Python

Security

What our scanner noticed· Low risk

  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Meng To
Publisher
Meng To (Community)
Source
MengTo/Skills · agent-skills/codex/audit-reference-originality · commit 5ea1661
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Meng To.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download audit-reference-originality.zip