Methodology

How Lightyear evaluates a Claude Code setup.

No black box. Everything below is derived from constants in the codebase. Current analysis model: claude-opus-4-7. Prompt version: v6.

1 · The pipeline

  1. 01Sign inGitHub OAuth via Convex Auth. Your identity is the GitHub numeric id; no password storage.
  2. 02Zipcd ~ && zip -r dotclaude.zip .claude. The browser unpacks the zip locally — nothing leaves your machine yet.
  3. 03WalkJS-side filter keeps the 14 canonical paths, strips binaries / oversize files / per-plugin cache + source. Hard caps: 2 MB per file, 500 files, 10 MB total.
  4. 04Scrub28+ regex patterns detect secrets. Blocks are always redacted (and server-rejected). Warns default to keep (mock paths, demo env vars rarely contain real secrets); you can flip them.
  5. 05Hash & uploadsha256 over the canonical-serialized bundle and (bundle_hash + tldr + loomUrl + prompt_version + model). The blob goes to Convex storage; the analysis_hash gates idempotency.
  6. 06VerifyServer re-hashes the stored blob; mismatch → reject. Asserts tldr/loomUrl are already canonical; refuses non-canonical input.
  7. 07Structural passBucket each file into the matching canonical category, count items / bytes / lines.
  8. 08Claude reviewclaude-opus-4-7 via Anthropic tool-use. One schema-bound submit_analysis tool — no free-form text. Untrusted bundle content is wrapped in sentinel markers and a policy preamble.
  9. 09Post-parseZod validates shape + enums + min lengths. clampAnalysis truncates near-edge string overshoots. Scrubber runs over Claude's output prose as defense in depth.
  10. 10StoreReports table upserts per submission. status: error → revivable in place on resubmit.

2 · Detected upload root

The walker detects what kind of folder you zipped. The score and the prompt then treat unreachable categories as N/A, not as missing — so submitting only your home ~/.claude doesn't penalize you for the lack of a project-level CLAUDE.md.

project_root

Project root

Has CLAUDE.md, CLAUDE.local.md, .claude/, or .mcp.json at top level.

Reaches: All 14 categories.

dotclaude_dir

Bare .claude/ dir

Top-level entries match .claude internals (hooks/, commands/, skills/, agents/, settings.json…) with no enclosing project.

Reaches: 10 categories under .claude/. Project-root artifacts (CLAUDE.md, .mcp.json, .gitignore) are N/A.

user_dotclaude

User-level ~/.claude

Top-level contains user-only signals like projects/, sessions/, paste-cache/, file-history/.

Reaches: Same 10 .claude/ categories. The four project-root-only entries are explicitly excluded from scoring.

3 · The 14 canonical categories

The bar. Weights sum to 100 by construction. Coverage scoring divides only by weights reachable from your detected root.

PathKindWeightTargetWhy it matters
CLAUDE.mdfile121Project memory that survives every session and steers Claude's behavior across the whole repo.
CLAUDE.local.mdfile41Personal overrides for your machine — gitignored — for paths, tokens, and quirks unique to you.
.gitignorefile21Prevents local files (and secrets) from leaking into the repo's history.
.mcp.jsonfile81Project-scoped MCP server configuration — extends Claude with custom tools beyond its built-ins.
.claude/hooks/dir93Deterministic shell hooks (PostToolUse, SessionStart, PreCompact) that enforce policy automatically.
.claude/commands/dir105Slash commands — reusable prompts and workflows you trigger by name.
.claude/skills/dir125Model-readable capabilities Claude auto-invokes when the situation matches.
.claude/agents/dir104Subagents with isolated context — used for code review, log triage, and protecting the main thread.
.claude/output-styles/dir62Custom response styles — bend Claude's voice and brevity to match the surface.
.claude/plugins/dir92First-class plugin bundles — commands, agents, and skills versioned together.
.claude/rules/dir63Path-scoped policy that loads only when files in that subtree are touched.
.claude/statuslinefile31Bottom-bar UI customization — at-a-glance status, model, branch, cost.
.claude/settings.jsonfile51Permissions, model, hook registry — the spine of Claude Code's behavior in this repo.
.claude/settings.local.jsonfile41Personal, gitignored settings layered on top of settings.json.

4 · Coverage score formula

Deterministic. Claude does not see or set this number — it's pure structure + weights.

per_category(c)
  = present
    ? min(1, log1p(itemCount) / log1p(targetCount)) * weight
    : 0

earned   = Σ per_category(c) for c in reachable(rootKind)
possible = Σ weight(c)        for c in reachable(rootKind)

score = clamp(0..100, round((earned / possible) * 100, 1))

The log1p ramp means going from 0 → 1 item buys most of the credit; the next few items round it up; piling on past target adds nothing. Designed to reward having any of a category at all without inflating expert tiers further.

5 · Sophistication tier

Claude picks one of these three. Not deterministic — it's a structured tool-use output, validated against the same enum the schema enforces.

Exploratory

Trying things out. Few categories populated, mostly defaults. Low depth.

Intentional

Deliberate, layered choices. Multiple categories populated. Patterns visible across files.

Expert

Self-sustaining system. Hooks enforce policy, agents protect context, skills auto-fire, plugins bundle workflows.

6 · Secret scrubbing

The scrubber registry runs in two places: in your browser before upload, and in the Convex action before Claude sees anything. Same patterns, no drift.

Block-severity matches are mandatory redactions and the server rejects a bundle that still contains any. Warn-severity (paths, demo env vars, email) defaults to keep — the false-positive rate on real .claude folders is high enough that auto-redacting them shreds signal. You can flip the whole warn class on the review screen.

7 · Idempotency

analysis_hash = sha256(
  bundle_hash + " " +
  canonicalize(tldr) + " " +
  canonicalize(loomUrl) + " " +
  PROMPT_VERSION + " " +
  ANALYSIS_MODEL
)

Same five inputs → same analysis_hash → preflight short-circuits to your existing report. Change any of them — re-zip, edit the TL;DR, paste a different Loom URL, model bump, prompt rewrite — and you get a fresh analysis. Errored runs revive in place on resubmit, preserving the URL.