Skip to content

feat(web): add static checks for skill trees as a display-only signal - #275

Open
dirtybits wants to merge 1 commit into
mainfrom
feat/skill-static-checks
Open

dirtybits wants to merge 1 commit into
mainfrom
feat/skill-static-checks

Conversation

@dirtybits

Copy link
Copy Markdown
Owner

What this changes

Phase 1, PR 3 of .agents/plans/skill-evals-and-packages.plan.md. Independent of #274.

Decision I took for you (Q8): you closed my question card and asked me to continue. So this PR follows the plan's proposal: static check results show on the skill page and in /api/check as a display-only signal. It can only warn. It never changes recommended_action or the "Trusted / Review / Avoid" verdict. If you want "API only" or "hidden" instead, tell me and I will change it.

New static checks (web/lib/skillStaticChecks.ts, version static-v1). They read every file of a skill, so they also cover the part of a large tree that the AI scan cuts off. They need no model and cost nothing.

Rule Severity Finds
Agent Skills frontmatter low / info Missing or invalid name and description, compatibility over 500 characters, fields outside the spec. Rules come from the upstream spec and the skills-ref validator (checked 2026-11-04).
Hidden Unicode medium Bidirectional controls, zero-width characters, Unicode tag characters, stacked variation selectors. Normal emoji, real flag emoji, and joiners in scripts such as Persian are allowed.
Download-and-run medium curl … | sh, wget … | sudo bash, bash <(curl …), sh -c "$(curl …)", iwr … | iex, and similar. || and curl … | jq are not flagged.
Secrets medium Private keys (with key data), AWS, GitHub, Slack, Stripe live, Anthropic, OpenAI, and Google keys, and 64-byte Solana keypair arrays. Documentation placeholders are skipped.
Archives medium / info Password-protected zip files (cannot be checked). Other archives get an info note.
  • No file content in findings. A finding gives the rule, file, first line, and a count, never an excerpt or a secret value. The URL inventory keeps host names only. So paid skills stay private before purchase.
  • Signal: static_checks is pass (nothing above low), warn (any medium finding), or unknown (no checks on record). It never gives fail.
  • Storage: additive skill_static_checks table, primary key (tree_hash, checks_version), the same pattern as skill_scans.
  • When checks run: after a publish, a new version, and every mirror sync, also when the mirror skips the AI review. /api/check checks uploaded files without storing them, and uses the cache for stored skills.
  • Shown in: the trust checklist on the skill page, GET /api/skills/{id} (static_checks), and /api/check (static_checks plus the signal). Browse cards do not change.

After merge: run the backfill once

Existing skills show "Static checks: n/a" until their checks exist. From web/ on the Mac:

npx tsx --env-file=.env.local ./scripts/backfill-skill-static-checks.ts           # dry run, no writes
npx tsx --env-file=.env.local ./scripts/backfill-skill-static-checks.ts --apply   # writes the reports

It prints the database host first. It reads stored trees directly, so it records no download events. Large mirrored trees can fail to read until #274 merges, because #274 fixes the stored-tree read limits.

Verified

  • npm run format:check, npm run lint, npm run typecheck, CLI tsc --noEmit.
  • npm test: web 165 files, 1,660 tests; CLI 17 files, 137 tests.
  • New tests for each rule, with cases that must be found and cases that must not (emoji, Persian text, ||, placeholders). A test confirms that a warn never changes recommended_action.
  • Mutation checks: treating || as a pipe, running the archive check before the text checks, keeping a leading byte order mark, and the old flag-emoji rule each make a test fail.
  • next build --webpack compiled and ran TypeScript, then stopped at the /sitemap.xml prerender because this workspace has no DATABASE_URL. The Vercel preview is the real build check.

Not verified

  • The backfill has not run on production.
  • No live request on the preview.

Notes for merging



Generated by Claude Code

Phase 1, PR 3 of .agents/plans/skill-evals-and-packages.plan.md.

- web/lib/skillStaticChecks.ts (static-v1): deterministic checks over
  every file of a skill. Agent Skills frontmatter rules (upstream spec and
  the skills-ref validator), hidden Unicode (bidirectional controls,
  zero-width and tag characters, stacked variation selectors),
  download-and-run steps (curl | sh, iwr | iex, bash <(curl ...)), values
  that look like secrets, and password-protected zip archives. Findings
  never contain file content, and the URL inventory keeps host names only.
- skill_static_checks table (additive, PK tree_hash + checks_version).
  The checks run after a publish, a new version, and every mirror sync,
  also when the mirror skips the AI review.
- static_checks trust signal (pass, warn, or unknown; never fail) on the
  skill page and in /api/check, which also returns the report as
  static_checks. recommendedActionFromSignals does not read it.
- scripts/backfill-skill-static-checks.ts covers versions stored before
  this change (dry run by default; --apply writes).
- Docs: web/public/skill.md (2.3.0), docs/AGENT_REFERENCE.md, the plan.

Reading taken for open question Q8: follow the plan's proposal (a
display-only signal on the site). Q8 stays open for evaluation evidence
and packages.

Verified: format:check, lint, web and CLI typecheck, web vitest (165
files, 1660 tests), CLI vitest (17 files, 137 tests), Python vectors.
Mutation checks: the || rule, the archive bypass, the leading byte order
mark, and the flag-emoji rule each make a test fail when broken.
next build --webpack compiled and ran TypeScript, then stopped at the
/sitemap.xml prerender without DATABASE_URL.

Not verified: the backfill has not run on production. Until it runs,
existing skills show "Static checks: n/a".

This branch was successfully deployed

1 active deployment
Preview — 307be597 Deployed Nov 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant