audit-skills
Heuristic scanner + structured checker for everything that should not ship. Fast (grep + JSON parse), false-positive-tolerant, never silently rewrites files. The agent classifies and the human decides.
When to use
- Before publishing a new skill / plugin
- Before merging a skill PR from another contributor
- After a refactor that touched multiple SKILL.md, plugin.json, README.md, or marketplace.json files
- After bumping plugin/marketplace conventions or rename rounds
- Periodic hygiene pass (monthly, before releases)
- After the official validator (
claude plugin validate .) surfaces a warning that smells like a structural drift
Three axes
1. Leaks (personal info + internal references)
User-facing fields (description, keywords, frontmatter, README excerpts) where the content shouldn't be public.
Personal info (always blocking, regardless of catalog visibility):
- Real names of contributors (other than
Yesterday org / public author handles)
- Private email addresses (look for
@ not on documented public domains)
- Local filesystem paths that reveal a username (
/Users/<name>/, /home/<name>/, C:\Users\<name>\)
- Personal IPs, hostnames, machine names (e.g.
kcma-d8, nas1, 192.168.x.x, *.lan, *.local)
- Personal API keys / tokens (
sk-…, ghp_…, xoxb-…, anything matching common secret patterns)
- Browser bookmarks, session cookies, OAuth callback URLs with state tokens
Yesterday-internal (blocking in PUBLIC catalogs, expected in PRIVATE):
- Internal repo names ending in
-internal (e.g. yopstack-internal, ystacks-internal)
- Internal infrastructure hostnames (
*.yester.cloud is mostly public; *.yesterday.internal / *.ys.internal is not)
- Internal Slack channels, Linear project codes, internal Notion URLs
- Refactoring history phrases ("promoted from .-internal", "moved out of .-internal", "vendored from temp-home")
- Internal team member shorthand
- Yesterday-only dependency declarations (
marketplace: "<name>-internal") in plugins meant for the public catalog
- TODO/FIXME/XXX comments referencing internal Jira/Linear tickets
Migration-history framing (blocking in PUBLIC catalogs, OK in .ytstack/ history records):
formerly X, previously X, predecessor, (renamed: X), migrated from
- Old plugin names that have been renamed (
yopstack, yastack, ydstack, ydstack-extras, ystacks in the public Yesterday catalog)
2. Catalog consistency (structural drift)
Mechanical alignment checks across the repo. Failures here usually mean install will silently break or the marketplace will surface mismatched info.
| Check |
Why it matters |
marketplace.json.name matches every install command in READMEs / docs |
Users copy-paste commands; mismatched marketplace name = "no such plugin" errors |
Bundle folder name === plugin.json.name |
Inconsistency confuses maintainers; compile.mjs slug = folder name but marketplace surfaces plugin.name |
Every cross-marketplace dep target ({name, marketplace}) actually exists in that marketplace |
Spec blocks cross-mp deps not allow-listed; install fails silently |
marketplace.json.allowCrossMarketplaceDependenciesOn covers every marketplace named in any bundle's dependencies[] |
Spec rule: cross-mp deps from un-allowlisted marketplaces are blocked at install |
README plugin counts (X plugins total, per-section counts) match .compiled/marketplace.json |
Doc rot when new skills are added but README isn't bumped |
Every plugin.json dependencies[] entry resolves: bare-string deps refer to a same-marketplace plugin; object-form deps refer to a real plugin in the named marketplace |
Same as above |
compile.mjs + clean.mjs are byte-identical with the sister catalog |
Drift between two compile pipelines wastes engineering time |
.gitignore covers .compiled/ and root marketplace symlinks |
Otherwise generated artifacts leak into commits |
3. Plugin-spec compliance
Validate against the official schemas:
Plugin-manifest rules (.claude-plugin/plugin.json):
| Rule |
Source |
name is the only required field; kebab-case, no spaces |
plugins-reference §"Required fields" |
version is optional; if set, plugin is pinned to that string until you bump it; if omitted, Claude Code uses the git commit SHA |
plugins-reference §"Metadata fields" |
author documented fields: name (required), email (optional). url is not in the schema |
plugins-reference §"Metadata fields" |
Other optional fields: $schema, description, homepage, repository, license, keywords, dependencies |
plugins-reference §"Metadata fields" |
.claude-plugin/ contains only plugin.json. Skills, agents, commands, hooks, mcp, lsp folders MUST be at plugin root, not inside .claude-plugin/ |
plugins-reference §"Standard plugin layout" warning |
Skills location: skills/<name>/SKILL.md at plugin root (or custom paths via skills field) |
plugins-reference §"Skills" |
Marketplace-manifest rules (.claude-plugin/marketplace.json):
| Rule |
Source |
Required: name (kebab-case), owner (with name), plugins array |
plugin-marketplaces §"Required fields" |
owner documented fields: name (required), email (optional). url is not in the schema |
plugin-marketplaces §"Owner fields" |
Per-plugin entry required: name, source |
plugin-marketplaces §"Required fields" |
Relative path sources must start with ./, never use .. |
plugin-marketplaces §"Relative paths" |
allowCrossMarketplaceDependenciesOn lists every other marketplace that this catalog's plugins depend on; missing entries block install |
plugin-marketplaces §"Optional fields" |
Reserved marketplace names: claude-code-marketplace, claude-code-plugins, claude-plugins-official, anthropic-marketplace, anthropic-plugins, agent-skills, knowledge-work-plugins, life-sciences |
plugin-marketplaces §"Reserved names" |
Cursor parity:
| Rule |
Source |
Cursor mirror manifest at .cursor-plugin/plugin.json (per-plugin) and .cursor-plugin/marketplace.json (catalog) |
cursor.com/docs/plugins |
Skills, rules, MCP servers in same default locations as Claude Code (skills/, rules/, mcp.json) |
cursor.com/docs/plugins |
Where to look
Public-facing surfaces in this repo:
| Surface |
Path glob |
Why it's user-facing |
| Skill frontmatter |
skills/**/SKILL.md (top YAML block, esp. description) |
Read by the model; surfaced in plugin listings |
| Skill body |
skills/**/SKILL.md |
Loaded into the agent's context every time the skill fires |
| Skill manifests |
skills/**/.plugin.json |
Symlinked into the marketplace; description is shown in /plugin install |
| Bundle manifests |
plugins/*/plugin.json (canonical) |
Authoritative; .claude-plugin/ and .cursor-plugin/ symlink to it |
| Marketplace header |
marketplace.json |
Top-level catalog metadata |
| Compiled marketplace |
.compiled/marketplace.json |
Generated; should match sources |
| Skill READMEs |
plugins/*/README.md, skills/**/README.md |
Often included verbatim in plugin install pages |
| Skill references |
skills/**/references/*.md |
Loaded on-demand by skills; same exposure as body |
| Catalog docs |
README.md, CONTRIBUTING.md, AGENTS.md, NOTICE |
Public install + contributor guide |
Procedure
- Run axis 1 (leak scan) -- inline command below or
references/leak-patterns.md regex set.
- Run axis 2 (consistency scan) -- inline checks below.
- Run axis 3 (spec compliance) -- where possible, run the official validator (
claude plugin validate . from inside any <plugin-folder> or marketplace root) and supplement with the inline checks below.
- Classify each hit as one of:
LEAK -- clear personal/internal info, must be removed before publishing
JARGON -- internal-sounding phrasing that should be rewritten in user-facing language
INCONSISTENCY -- structural drift (renames, dep targets, doc counts)
SPEC -- violates the Claude Code or Cursor plugin spec
OK -- false positive
- Report the triage table to the user, grouped by file. Quote the offending line. Suggest a replacement when obvious. Do NOT auto-fix.
- For each non-OK finding, ask the user whether to apply the suggested fix (one-by-one) or batch.
- Re-run the relevant axis after fixes to confirm zero blocking findings remain.
Inline scan commands
Axis 1: Leak scan
# from repo root. Use find+xargs (recursive, glob-safe) and `grep -e` so
# patterns starting with `-` aren't parsed as options.
PATTERNS='\b\w*-internal\b|/Users/[a-z]+/|/home/[a-z]+/|\bkcma-d8\b|\bnas1\b|\b192\.168\.|\b10\.0\.|\.local\b|\.lan\b|\btemp-home\b|promoted from .*-internal|vendored from temp-home|formerly\b|previously lived|migrated from|\(renamed:|TODO\([^)]+\)|\bFIXME\b|\bXXX:|sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}'
find skills plugins marketplace.json README.md AGENTS.md CONTRIBUTING.md NOTICE -type f \
\( -name '*.md' -o -name '*.json' -o -name 'NOTICE' -o -name 'LICENSE' \) \
-not -path '*/.compiled/*' \
-not -path '*/node_modules/*' \
-not -path '*/.agents/*' \
-not -path '*/.ytstack/*' \
2>/dev/null \
| xargs grep -nE -e "$PATTERNS" 2>/dev/null
Excluded: .compiled/ (generated), .agents/ (this skill's own pattern docs), .ytstack/ (project memory is allowed to keep history records per the "no migration framing in user-facing files" rule).
Axis 2: Consistency checks
# from repo root.
# 2a. Marketplace name must match every install command
MP_NAME=$(node -e "console.log(JSON.parse(require('fs').readFileSync('marketplace.json','utf8')).name)")
echo "Marketplace name: $MP_NAME"
echo "Install commands referencing other names:"
grep -rn '/plugin install [^@]*@' README.md AGENTS.md CONTRIBUTING.md plugins/*/README.md 2>/dev/null \
| grep -v "@$MP_NAME"
# 2b. Bundle folder name vs plugin.json name
echo "Folder/name mismatches:"
for f in plugins/*/plugin.json; do
n=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$f','utf8')).name)" 2>/dev/null)
fold=$(basename $(dirname $f))
[ "$n" != "$fold" ] && echo " $fold -> $n"
done
# 2c. Cross-marketplace deps that point to non-existent plugins
# (requires sister marketplace .compiled/marketplace.json to compare against;
# inline this check with the actual sister-repo path you cross-depend on)
# 2d. README plugin count vs compiled count
COMPILED=$(node -e "console.log(JSON.parse(require('fs').readFileSync('.compiled/marketplace.json','utf8')).plugins.length)")
README_CLAIM=$(grep -oE '[0-9]+ plugins total' README.md | head -1)
echo "Compiled count: $COMPILED README claim: $README_CLAIM"
# 2e. compile.mjs / clean.mjs / .gitignore parity with sister catalog
# Adapt SISTER_PATH to your sister repo
SISTER_PATH=../yesterday-skills # or ../skills, depending which side you are
diff -q compile.mjs $SISTER_PATH/compile.mjs
diff -q clean.mjs $SISTER_PATH/clean.mjs
diff -q .gitignore $SISTER_PATH/.gitignore
# 2f. .compiled/ / root marketplace symlinks gitignored
grep -E '^\.compiled/?$|^\.claude-plugin/marketplace\.json|^\.cursor-plugin/marketplace\.json' .gitignore
Axis 3: Spec compliance
# 3a. Run the official validator on the marketplace + every bundle
claude plugin validate . # marketplace check
for d in plugins/*/; do claude plugin validate "$d"; done
# 3b. plugin.json: required field present
for f in plugins/*/plugin.json skills/**/.plugin.json; do
node -e "
const d=JSON.parse(require('fs').readFileSync('$f','utf8'));
if (!d.name) console.log('MISSING name:', '$f');
"
done
# 3c. plugin.json: undocumented author fields
for f in plugins/*/plugin.json skills/**/.plugin.json; do
node -e "
const d=JSON.parse(require('fs').readFileSync('$f','utf8'));
const ok=new Set(['name','email']);
if (d.author) Object.keys(d.author).filter(k=>!ok.has(k)).forEach(k=>console.log('AUTHOR.$f has undocumented .'+k));
"
done
# 3d. plugin.json: forbidden version pin (project policy: omit version, use git SHA)
for f in plugins/*/plugin.json skills/**/.plugin.json; do
grep -l '"version"' "$f" 2>/dev/null | xargs -I{} echo "VERSION pin in {}"
done
# 3e. marketplace.json: required fields + undocumented owner fields
node -e "
const m=JSON.parse(require('fs').readFileSync('marketplace.json','utf8'));
for (const k of ['name','owner','plugins']) if (!m[k]) console.log('MARKETPLACE missing:', k);
if (m.owner) Object.keys(m.owner).filter(k=>!['name','email'].includes(k)).forEach(k=>console.log('OWNER undocumented field: .'+k));
"
# 3f. relative source paths must start with ./, no ..
node -e "
const m=JSON.parse(require('fs').readFileSync('.compiled/marketplace.json','utf8'));
m.plugins.filter(p=>typeof p.source==='string').forEach(p=>{
if (!p.source.startsWith('./')) console.log('SOURCE not ./-relative:', p.name, p.source);
if (p.source.includes('..')) console.log('SOURCE contains ..:', p.name, p.source);
});
"
Output format
Report a table per file. Example:
plugins/office/plugin.json
L3: JARGON "...live in ydstack-extras"
-> office-extras (renamed plugin)
README.md
L60: INCONSISTENCY "13 plugins total: ... 8 standalone"
-> compiled count is 14 / 9; bump and add new standalone to category section
plugins/<name>/plugin.json
-: SPEC author has undocumented `url` field
-> remove or replace with email (spec only documents name + email)
End the report with a one-line summary: <L> LEAK, <J> JARGON, <I> INCONSISTENCY, <S> SPEC across <F> files. Suggest fixes? (y / per-file / skip).
What NOT to flag
- Public Yesterday brand mentions (
Yesterday, Yesterday-AI, yester.cloud) -- intentionally public.
- Plugin descriptions that mention other PUBLIC plugins by name (e.g.
office-extras referencing office) -- catalog cross-refs.
- README install commands that reference org names.
- Keyword arrays containing
yesterday.
- Internal references in PRIVATE catalogs (sister
yesterday-skills) -- flag for awareness, but they are intentional content there.
- Migration history living in
.ytstack/DECISIONS.md -- intentional history record (project memory is allowed to keep what user-facing files cannot).
False-positive examples
kcma-d8 inside a local-llm skill that legitimately documents that the home-network GPU server is reachable at that hostname for users who set up the same topology -- if the doc explains the assumption, classify as JARGON not LEAK.
/Users/alex/ inside a code block that demonstrates path-handling -- annotate or replace with /Users/<you>/.
- The word "internal" in a sentence like "internal state of the function" -- pure English usage; the regex
\b\w*-internal\b requires a hyphen prefix, so this should not match. If it does, tighten the regex.
promoted from corrections in self-improvement docs -- pattern promoted from .*-internal was tightened to avoid this; flag any regression to promoted from bare.
Limits of this skill
- Heuristic. Will miss findings that don't match patterns and over-flag legitimate uses. The human verdict is the source of truth.
- Does not parse YAML / JSON semantically for axis 1 -- regex on raw bytes. A leak that spans multiple lines after a key may be partially shown.
- Axis 3 (
claude plugin validate) is the closest thing to a definitive spec check; the inline JSON checks are supplemental.
- Does not check git history. A clean current tree may still have leaks in earlier commits; rewriting history is out of scope.
- Does not verify whether a
homepage or repository URL actually points to a public repo. Use gh repo view <owner/repo> separately.
See also
references/leak-patterns.md -- canonical regex list for axis 1, easier to extend than editing the skill body
~/.claude/CLAUDE.md user rules section "PUBLIC: As skills are public, skills NEED to: be depersonalized..."
.ytstack/DECISIONS.md "Migration history lives only in .ytstack/ docs" -- the rule that drives axis 1's migration-framing patterns
.ytstack/DECISIONS.md "Acceptance criterion -- public-installability" -- internal-perspective framing rule
1---2name: audit-skills3description: Audit this repo's skills + plugins on three axes -- (1) leaks of personal info or Yesterday-internal references in user-facing artifacts, (2) catalog consistency (folder/name/dep/install-target alignment), (3) plugin-spec compliance (Claude Code + Cursor manifest schemas, layout rules). Reports findings as a triage list; does not auto-fix. Use before publishing, before merging skill PRs, or as a periodic hygiene pass.4---56# audit-skills78Heuristic scanner + structured checker for everything that should not ship. Fast (grep + JSON parse), false-positive-tolerant, never silently rewrites files. The agent classifies and the human decides.910## When to use1112- Before publishing a new skill / plugin13- Before merging a skill PR from another contributor14- After a refactor that touched multiple SKILL.md, plugin.json, README.md, or marketplace.json files15- After bumping plugin/marketplace conventions or rename rounds16- Periodic hygiene pass (monthly, before releases)17- After the official validator (`claude plugin validate .`) surfaces a warning that smells like a structural drift1819## Three axes2021### 1. Leaks (personal info + internal references)2223User-facing fields (description, keywords, frontmatter, README excerpts) where the content shouldn't be public.2425**Personal info (always blocking, regardless of catalog visibility):**2627- Real names of contributors (other than `Yesterday` org / public author handles)28- Private email addresses (look for `@` not on documented public domains)29- Local filesystem paths that reveal a username (`/Users/<name>/`, `/home/<name>/`, `C:\Users\<name>\`)30- Personal IPs, hostnames, machine names (e.g. `kcma-d8`, `nas1`, `192.168.x.x`, `*.lan`, `*.local`)31- Personal API keys / tokens (`sk-…`, `ghp_…`, `xoxb-…`, anything matching common secret patterns)32- Browser bookmarks, session cookies, OAuth callback URLs with state tokens3334**Yesterday-internal (blocking in PUBLIC catalogs, expected in PRIVATE):**3536- Internal repo names ending in `-internal` (e.g. `yopstack-internal`, `ystacks-internal`)37- Internal infrastructure hostnames (`*.yester.cloud` is mostly public; `*.yesterday.internal` / `*.ys.internal` is not)38- Internal Slack channels, Linear project codes, internal Notion URLs39- Refactoring history phrases ("promoted from .*-internal", "moved out of .*-internal", "vendored from temp-home")40- Internal team member shorthand41- Yesterday-only dependency declarations (`marketplace: "<name>-internal"`) in plugins meant for the public catalog42- TODO/FIXME/XXX comments referencing internal Jira/Linear tickets4344**Migration-history framing (blocking in PUBLIC catalogs, OK in `.ytstack/` history records):**4546- `formerly X`, `previously X`, `predecessor`, `(renamed: X)`, `migrated from`47- Old plugin names that have been renamed (`yopstack`, `yastack`, `ydstack`, `ydstack-extras`, `ystacks` in the public Yesterday catalog)4849### 2. Catalog consistency (structural drift)5051Mechanical alignment checks across the repo. Failures here usually mean install will silently break or the marketplace will surface mismatched info.5253| Check | Why it matters |54| --- | --- |55| `marketplace.json.name` matches every install command in READMEs / docs | Users copy-paste commands; mismatched marketplace name = "no such plugin" errors |56| Bundle folder name === `plugin.json.name` | Inconsistency confuses maintainers; `compile.mjs` slug = folder name but marketplace surfaces `plugin.name` |57| Every cross-marketplace dep target (`{name, marketplace}`) actually exists in that marketplace | Spec blocks cross-mp deps not allow-listed; install fails silently |58| `marketplace.json.allowCrossMarketplaceDependenciesOn` covers every marketplace named in any bundle's `dependencies[]` | Spec rule: cross-mp deps from un-allowlisted marketplaces are blocked at install |59| README plugin counts (`X plugins total`, per-section counts) match `.compiled/marketplace.json` | Doc rot when new skills are added but README isn't bumped |60| Every plugin.json `dependencies[]` entry resolves: bare-string deps refer to a same-marketplace plugin; object-form deps refer to a real plugin in the named marketplace | Same as above |61| `compile.mjs` + `clean.mjs` are byte-identical with the sister catalog | Drift between two compile pipelines wastes engineering time |62| `.gitignore` covers `.compiled/` and root marketplace symlinks | Otherwise generated artifacts leak into commits |6364### 3. Plugin-spec compliance6566Validate against the official schemas:6768- [Claude Code Plugins reference](https://code.claude.com/docs/en/plugins-reference) -- plugin.json schema, folder layout, slug conventions69- [Claude Code Plugin marketplaces](https://code.claude.com/docs/en/plugin-marketplaces) -- marketplace.json schema, source types, plugin entry fields70- [Cursor Plugins](https://cursor.com/docs/plugins) -- `.cursor-plugin/` mirror manifest, skills/rules layout7172**Plugin-manifest rules (`.claude-plugin/plugin.json`):**7374| Rule | Source |75| --- | --- |76| `name` is the only required field; kebab-case, no spaces | plugins-reference §"Required fields" |77| `version` is optional; if set, plugin is pinned to that string until you bump it; if omitted, Claude Code uses the git commit SHA | plugins-reference §"Metadata fields" |78| `author` documented fields: `name` (required), `email` (optional). `url` is **not** in the schema | plugins-reference §"Metadata fields" |79| Other optional fields: `$schema`, `description`, `homepage`, `repository`, `license`, `keywords`, `dependencies` | plugins-reference §"Metadata fields" |80| `.claude-plugin/` contains only `plugin.json`. Skills, agents, commands, hooks, mcp, lsp folders MUST be at plugin root, not inside `.claude-plugin/` | plugins-reference §"Standard plugin layout" warning |81| Skills location: `skills/<name>/SKILL.md` at plugin root (or custom paths via `skills` field) | plugins-reference §"Skills" |8283**Marketplace-manifest rules (`.claude-plugin/marketplace.json`):**8485| Rule | Source |86| --- | --- |87| Required: `name` (kebab-case), `owner` (with `name`), `plugins` array | plugin-marketplaces §"Required fields" |88| `owner` documented fields: `name` (required), `email` (optional). `url` is **not** in the schema | plugin-marketplaces §"Owner fields" |89| Per-plugin entry required: `name`, `source` | plugin-marketplaces §"Required fields" |90| Relative path sources must start with `./`, never use `..` | plugin-marketplaces §"Relative paths" |91| `allowCrossMarketplaceDependenciesOn` lists every other marketplace that this catalog's plugins depend on; missing entries block install | plugin-marketplaces §"Optional fields" |92| Reserved marketplace names: `claude-code-marketplace`, `claude-code-plugins`, `claude-plugins-official`, `anthropic-marketplace`, `anthropic-plugins`, `agent-skills`, `knowledge-work-plugins`, `life-sciences` | plugin-marketplaces §"Reserved names" |9394**Cursor parity:**9596| Rule | Source |97| --- | --- |98| Cursor mirror manifest at `.cursor-plugin/plugin.json` (per-plugin) and `.cursor-plugin/marketplace.json` (catalog) | cursor.com/docs/plugins |99| Skills, rules, MCP servers in same default locations as Claude Code (`skills/`, `rules/`, `mcp.json`) | cursor.com/docs/plugins |100101## Where to look102103Public-facing surfaces in this repo:104105| Surface | Path glob | Why it's user-facing |106| --- | --- | --- |107| Skill frontmatter | `skills/**/SKILL.md` (top YAML block, esp. `description`) | Read by the model; surfaced in plugin listings |108| Skill body | `skills/**/SKILL.md` | Loaded into the agent's context every time the skill fires |109| Skill manifests | `skills/**/.plugin.json` | Symlinked into the marketplace; description is shown in `/plugin install` |110| Bundle manifests | `plugins/*/plugin.json` (canonical) | Authoritative; `.claude-plugin/` and `.cursor-plugin/` symlink to it |111| Marketplace header | `marketplace.json` | Top-level catalog metadata |112| Compiled marketplace | `.compiled/marketplace.json` | Generated; should match sources |113| Skill READMEs | `plugins/*/README.md`, `skills/**/README.md` | Often included verbatim in plugin install pages |114| Skill references | `skills/**/references/*.md` | Loaded on-demand by skills; same exposure as body |115| Catalog docs | `README.md`, `CONTRIBUTING.md`, `AGENTS.md`, `NOTICE` | Public install + contributor guide |116117## Procedure1181191. **Run axis 1** (leak scan) -- inline command below or `references/leak-patterns.md` regex set.1202. **Run axis 2** (consistency scan) -- inline checks below.1213. **Run axis 3** (spec compliance) -- where possible, run the official validator (`claude plugin validate .` from inside any `<plugin-folder>` or marketplace root) and supplement with the inline checks below.1224. **Classify each hit** as one of:123 - `LEAK` -- clear personal/internal info, must be removed before publishing124 - `JARGON` -- internal-sounding phrasing that should be rewritten in user-facing language125 - `INCONSISTENCY` -- structural drift (renames, dep targets, doc counts)126 - `SPEC` -- violates the Claude Code or Cursor plugin spec127 - `OK` -- false positive1285. **Report** the triage table to the user, grouped by file. Quote the offending line. Suggest a replacement when obvious. Do NOT auto-fix.1296. **For each non-OK finding**, ask the user whether to apply the suggested fix (one-by-one) or batch.1307. **Re-run** the relevant axis after fixes to confirm zero blocking findings remain.131132## Inline scan commands133134### Axis 1: Leak scan135136```bash137# from repo root. Use find+xargs (recursive, glob-safe) and `grep -e` so138# patterns starting with `-` aren't parsed as options.139PATTERNS='\b\w*-internal\b|/Users/[a-z]+/|/home/[a-z]+/|\bkcma-d8\b|\bnas1\b|\b192\.168\.|\b10\.0\.|\.local\b|\.lan\b|\btemp-home\b|promoted from .*-internal|vendored from temp-home|formerly\b|previously lived|migrated from|\(renamed:|TODO\([^)]+\)|\bFIXME\b|\bXXX:|sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}'140141find skills plugins marketplace.json README.md AGENTS.md CONTRIBUTING.md NOTICE -type f \142 \( -name '*.md' -o -name '*.json' -o -name 'NOTICE' -o -name 'LICENSE' \) \143 -not -path '*/.compiled/*' \144 -not -path '*/node_modules/*' \145 -not -path '*/.agents/*' \146 -not -path '*/.ytstack/*' \147 2>/dev/null \148 | xargs grep -nE -e "$PATTERNS" 2>/dev/null149```150151Excluded: `.compiled/` (generated), `.agents/` (this skill's own pattern docs), `.ytstack/` (project memory is allowed to keep history records per the "no migration framing in user-facing files" rule).152153### Axis 2: Consistency checks154155```bash156# from repo root.157158# 2a. Marketplace name must match every install command159MP_NAME=$(node -e "console.log(JSON.parse(require('fs').readFileSync('marketplace.json','utf8')).name)")160echo "Marketplace name: $MP_NAME"161echo "Install commands referencing other names:"162grep -rn '/plugin install [^@]*@' README.md AGENTS.md CONTRIBUTING.md plugins/*/README.md 2>/dev/null \163 | grep -v "@$MP_NAME"164165# 2b. Bundle folder name vs plugin.json name166echo "Folder/name mismatches:"167for f in plugins/*/plugin.json; do168 n=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$f','utf8')).name)" 2>/dev/null)169 fold=$(basename $(dirname $f))170 [ "$n" != "$fold" ] && echo " $fold -> $n"171done172173# 2c. Cross-marketplace deps that point to non-existent plugins174# (requires sister marketplace .compiled/marketplace.json to compare against;175# inline this check with the actual sister-repo path you cross-depend on)176177# 2d. README plugin count vs compiled count178COMPILED=$(node -e "console.log(JSON.parse(require('fs').readFileSync('.compiled/marketplace.json','utf8')).plugins.length)")179README_CLAIM=$(grep -oE '[0-9]+ plugins total' README.md | head -1)180echo "Compiled count: $COMPILED README claim: $README_CLAIM"181182# 2e. compile.mjs / clean.mjs / .gitignore parity with sister catalog183# Adapt SISTER_PATH to your sister repo184SISTER_PATH=../yesterday-skills # or ../skills, depending which side you are185diff -q compile.mjs $SISTER_PATH/compile.mjs186diff -q clean.mjs $SISTER_PATH/clean.mjs187diff -q .gitignore $SISTER_PATH/.gitignore188189# 2f. .compiled/ / root marketplace symlinks gitignored190grep -E '^\.compiled/?$|^\.claude-plugin/marketplace\.json|^\.cursor-plugin/marketplace\.json' .gitignore191```192193### Axis 3: Spec compliance194195```bash196# 3a. Run the official validator on the marketplace + every bundle197claude plugin validate . # marketplace check198for d in plugins/*/; do claude plugin validate "$d"; done199200# 3b. plugin.json: required field present201for f in plugins/*/plugin.json skills/**/.plugin.json; do202 node -e "203 const d=JSON.parse(require('fs').readFileSync('$f','utf8'));204 if (!d.name) console.log('MISSING name:', '$f');205 "206done207208# 3c. plugin.json: undocumented author fields209for f in plugins/*/plugin.json skills/**/.plugin.json; do210 node -e "211 const d=JSON.parse(require('fs').readFileSync('$f','utf8'));212 const ok=new Set(['name','email']);213 if (d.author) Object.keys(d.author).filter(k=>!ok.has(k)).forEach(k=>console.log('AUTHOR.$f has undocumented .'+k));214 "215done216217# 3d. plugin.json: forbidden version pin (project policy: omit version, use git SHA)218for f in plugins/*/plugin.json skills/**/.plugin.json; do219 grep -l '"version"' "$f" 2>/dev/null | xargs -I{} echo "VERSION pin in {}"220done221222# 3e. marketplace.json: required fields + undocumented owner fields223node -e "224 const m=JSON.parse(require('fs').readFileSync('marketplace.json','utf8'));225 for (const k of ['name','owner','plugins']) if (!m[k]) console.log('MARKETPLACE missing:', k);226 if (m.owner) Object.keys(m.owner).filter(k=>!['name','email'].includes(k)).forEach(k=>console.log('OWNER undocumented field: .'+k));227"228229# 3f. relative source paths must start with ./, no ..230node -e "231 const m=JSON.parse(require('fs').readFileSync('.compiled/marketplace.json','utf8'));232 m.plugins.filter(p=>typeof p.source==='string').forEach(p=>{233 if (!p.source.startsWith('./')) console.log('SOURCE not ./-relative:', p.name, p.source);234 if (p.source.includes('..')) console.log('SOURCE contains ..:', p.name, p.source);235 });236"237```238239## Output format240241Report a table per file. Example:242243```244plugins/office/plugin.json245 L3: JARGON "...live in ydstack-extras"246 -> office-extras (renamed plugin)247248README.md249 L60: INCONSISTENCY "13 plugins total: ... 8 standalone"250 -> compiled count is 14 / 9; bump and add new standalone to category section251252plugins/<name>/plugin.json253 -: SPEC author has undocumented `url` field254 -> remove or replace with email (spec only documents name + email)255```256257End the report with a one-line summary: `<L> LEAK, <J> JARGON, <I> INCONSISTENCY, <S> SPEC across <F> files. Suggest fixes? (y / per-file / skip)`.258259## What NOT to flag260261- Public Yesterday brand mentions (`Yesterday`, `Yesterday-AI`, `yester.cloud`) -- intentionally public.262- Plugin descriptions that mention other PUBLIC plugins by name (e.g. `office-extras` referencing `office`) -- catalog cross-refs.263- README install commands that reference org names.264- Keyword arrays containing `yesterday`.265- Internal references in PRIVATE catalogs (sister `yesterday-skills`) -- flag for awareness, but they are intentional content there.266- Migration history living in `.ytstack/DECISIONS.md` -- intentional history record (project memory is allowed to keep what user-facing files cannot).267268## False-positive examples269270- `kcma-d8` inside a `local-llm` skill that legitimately documents that the home-network GPU server is reachable at that hostname for users who set up the same topology -- if the doc explains the assumption, classify as JARGON not LEAK.271- `/Users/alex/` inside a code block that demonstrates path-handling -- annotate or replace with `/Users/<you>/`.272- The word "internal" in a sentence like "internal state of the function" -- pure English usage; the regex `\b\w*-internal\b` requires a hyphen prefix, so this should not match. If it does, tighten the regex.273- `promoted from corrections` in self-improvement docs -- pattern `promoted from .*-internal` was tightened to avoid this; flag any regression to `promoted from` bare.274275## Limits of this skill276277- Heuristic. Will miss findings that don't match patterns and over-flag legitimate uses. The human verdict is the source of truth.278- Does not parse YAML / JSON semantically for axis 1 -- regex on raw bytes. A leak that spans multiple lines after a key may be partially shown.279- Axis 3 (`claude plugin validate`) is the closest thing to a definitive spec check; the inline JSON checks are supplemental.280- Does not check git history. A clean current tree may still have leaks in earlier commits; rewriting history is out of scope.281- Does not verify whether a `homepage` or `repository` URL actually points to a public repo. Use `gh repo view <owner/repo>` separately.282283## See also284285- `references/leak-patterns.md` -- canonical regex list for axis 1, easier to extend than editing the skill body286- `~/.claude/CLAUDE.md` user rules section "PUBLIC: As skills are public, skills NEED to: be depersonalized..."287- `.ytstack/DECISIONS.md` "Migration history lives only in .ytstack/ docs" -- the rule that drives axis 1's migration-framing patterns288- `.ytstack/DECISIONS.md` "Acceptance criterion -- public-installability" -- internal-perspective framing rule