Skip to content
Back to skills

Skill Quality

ASecurity

Automatically rate any skill's description quality and rewrite it if needed — the quality gate for the skill creation pipeline. Invoked by /skill-creator after skill installation, or directly with /skill-quality [skill-name]. When to trigger: automatically after every new skill is installed via /skill-creator; when manually auditing a skill's description quality; when a skill's description fails to attract correct usage; or when enhancing an existing skill and wanting to verify the descriptio...

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 29, 2026
ai-agentspython

Security analysis

A100/100

Scanned September 29, 2026

npx -y skills add Tekkiiiii/the-agency --skill skill-quality --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Skill Quality?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Skill Quality
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tekkiiiii-skill-quality/badge)](https://www.skillsdirectory.com/skills/tekkiiiii-skill-quality)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: skill-quality
description: >
  Automatically rate any skill's description quality and rewrite it if needed — the quality gate
  for the skill creation pipeline. Invoked by /skill-creator after skill installation, or
  directly with /skill-quality [skill-name]. When to trigger: automatically after every new
  skill is installed via /skill-creator; when manually auditing a skill's description
  quality; when a skill's description fails to attract correct usage; or when enhancing an
  existing skill and wanting to verify the description is strong. Key capabilities: two
  independent critics (Capability + Likelihood to Pick Up, 0–100 each); calibrated gate
  (avg≥80 AND one≥85 AND none<70); one automatic rewrite cycle on failure; results written
  to JSON immediately (survives session compaction); and discrepancy detection (flags when
  critics disagree by >20 points). Also handles: --quality-gate off bypass flag, self-referential
  check (skill-quality skips its own catalog registration), and idempotent re-rating of
  already-rated skills. Compare with skill-import (one-off conflict detection) and skill-creator
  (full creation pipeline that calls this skill automatically).
---

# Skill Quality — Description Critic Gate

Spawns two independent critics to rate a skill's description, applies the calibrated gate,
and rewrites automatically if it fails. Called by `/skill-creator` as a post-install step,
or run directly.

---

## Step 0: Parse Arguments

Accept `--skill [name]` or a bare skill name. No flags = error.

Detect `--quality-gate off` bypass: if present, skip all critics, write `quality_gate_passed: null`,
return immediately without catalog registration.

**Self-referential guard:** If the target is `skill-quality` itself, run the critic check normally
but mark `registration_status: "skipped"` in the output — skill-quality never registers itself to
the catalog.

---

## Step 1: Validate Target Exists

```python
import yaml
from pathlib import Path

skill = "[name]"
path = Path(f"~/.claude/skills/{skill}/SKILL.md").expanduser()

if not path.exists():
    raise ValueError(f"Skill not found: {skill}")

content = path.read_text()
fm = yaml.safe_load('---\n' + content.split('---', 2)[1])
desc = fm.get('description', '')
if not desc:
    raise ValueError(f"No description field in {skill}/SKILL.md")
```

Always use `yaml.safe_load('---\n' + content.split('---', 2)[1])` — not regex — to handle
folded scalar blocks with `:` characters correctly.

---

## Step 2: Spawn Critics in Parallel

Spawn two independent depth-1 subagents in parallel. Both read `memory/agents/critic/agent-card.md`
for the scoring rubric.

### critic-a
- Spawn: `Agent(tool=Agent, run_in_background=true)` — fires and waits for SendMessage callback
- Prompt: see agent-card.md

```
Rate ONE skill — [skill] — using its current description.

Extract description with:
```python
import yaml
from pathlib import Path
content = Path('~/.claude/skills/[skill]/SKILL.md').expanduser().read_text()
fm = yaml.safe_load('---\n' + content.split('---', 2)[1])
desc = fm['description']
```

**Dimension 1 — Capability (C):** How well does the description convey purpose, triggers, capabilities?
100 = instantly actionable to any agent.

**Dimension 2 — Likelihood to Pick Up (L):** How likely to invoke this skill based on
description alone? 100 = irresistible and obvious.

**Gate:** avg ≥ 80 AND at least one score ≥ 85 AND no score < 70.

Write result to /tmp/[skill]-critic-a.json:
```json
{"skill":"[skill]","critic":"critic-a","capability":N,"likelihood":N,"avg":N,"gate":"PASS|FAIL","notes":"...","timestamp":"[ISO8601]"}
```

Read memory/agents/critic/agent-card.md for full rubric before rating.
```

### critic-b
- Same as critic-a but write to `/tmp/[skill]-critic-b.json`, `critic: "critic-b"`

**Both critics fire simultaneously.** Wait for both SendMessage callbacks before proceeding to Step 3.
If a critic fails to respond within 5 minutes, retry it once. If it still fails, proceed with
`capability: null, likelihood: null, avg: null, gate: "ERROR"` — don't block the pipeline.

---

## Step 3: Collect and Combine Results

Read both JSON files:
```python
import json
critic_a = json.loads(Path("/tmp/[skill]-critic-a.json").read_text())
critic_b = json.loads(Path("/tmp/[skill]-critic-b.json").read_text())
```

### Compute gate conditions

```python
all_scores = [
    critic_a["capability"], critic_a["likelihood"],
    critic_b["capability"], critic_b["likelihood"]
]

avg_ge_80  = (critic_a["avg"] + critic_b["avg"]) / 2 >= 80
one_ge_85  = any(s >= 85 for s in all_scores if s is not None)
none_lt_70 = all(s >= 70 for s in all_scores if s is not None)
gate_met   = avg_ge_80 and one_ge_85 and none_lt_70

# Discrepancy: critics disagree by >20 points on average
critic_avg_gap = abs(critic_a["avg"] - critic_b["avg"])
discrepancy_flag = critic_avg_gap > 20
```

### Write immediate JSON
Write to `results/[skill]-critic-a.json` and `results/[skill]-critic-b.json`
(absolute paths from current dir). Write BEFORE applying gate — survives compaction.

---

## Step 4: Apply Gate — Pass Path

If `gate_met == true`:
1. Write `results/[skill]-quality-results.json` with gate_met=true, round=1, rewrite_performed=false, registration_status="registered"
2. Return the summary table (Step 6)

---

## Step 5: Apply Gate — Fail Path (One Rewrite Cycle)

If `gate_met == false`, rewrite the description automatically:

### 5a. Rewrite the Description

Write a new description that:
1. **Opens with user value** — what does the user gain? Not the mechanism.
2. **Lists 5–7 concrete trigger scenarios** — phrases a user would actually say
3. **Names key capabilities** — specific workflows, commands, platforms, frameworks
4. **Includes "Also for..."** coverage — adjacent use cases
5. **Differentiates from adjacent skills** — add "Compare with [skill-x]" if relevant
6. **Length: 600–1,000 chars** (hard cap: 1,536; minimum: 300)

### 5b. Write the Rewritten SKILL.md

```python
import yaml, json
from pathlib import Path

skill_path = Path(f"~/.claude/skills/[skill]/SKILL.md").expanduser()
content = skill_path.read_text()
fm = yaml.safe_load('---\n' + content.split('---', 2)[1])
fm['description'] = rewritten_description

# Re-serialize to YAML preserving the ---
out_lines = ["---"]
out_lines.append(yaml.safe_dump(fm, default_flow_style=False, sort_keys=False).rstrip())
out_lines.append("---")
skill_path.write_text('\n'.join(out_lines) + '\n')
```

Use `json.dumps(desc)` to safely write descriptions containing `:` — avoids folded-block colon parse errors.
Use `yaml.safe_load('---\n' + content.split('---', 2)[1])` for extraction.

### 5c. Re-rate (One More Round)

Spawn critic-a and critic-b again on the rewritten description — same prompt as Step 2,
but the description now comes from the just-rewritten SKILL.md.

Apply gate again. If still FAIL after rewrite, `registration_status = "flagged"`.

**One rewrite cycle only.** Do not loop. If it fails after rewrite, flag it and proceed.

### 5d. Write Final Quality Gate JSON

Write `results/[skill]-quality-results.json`:
```json
{
  "skill": "[skill]",
  "round": 2,
  "critic_a": {"capability": N, "likelihood": N, "avg": N},
  "critic_b": {"capability": N, "likelihood": N, "avg": N},
  "combined_avg": N,
  "gate_met": true|false,
  "gate_conditions": {
    "avg_ge_80": bool,
    "one_ge_85": bool,
    "none_lt_70": bool
  },
  "rewrite_performed": true,
  "discrepancy_flag": bool,
  "registration_status": "registered|flagged",
  "timestamp": "[ISO8601]"
}
```

---

## Step 6: Output Summary Table

```
## Quality Gate Results — [skill]

| Critic   | Capability | Likelihood | Average | Gate |
|----------|-----------|------------|---------|------|
| critic-a | [C]        | [L]        | [avg]   | [PASS/FAIL] |
| critic-b | [C]        | [L]        | [avg]   | [PASS/FAIL] |
| **Combined** | —    | —          | **[avg]** | **[✅ PASS / ❌ FAIL]** |

Gate conditions: avg≥80 [✅/❌]  one≥85 [✅/❌]  none<70 [✅/❌]
[+ "⚠️ Discrepancy detected (critics differed by [N] pts)" if discrepancy_flag]

Registration: [registered / flagged / skipped]
Rewritten: [yes / no]

Results written to:
  results/[skill]-critic-a.json
  results/[skill]-critic-b.json
  results/[skill]-quality-results.json
```

---

## Key Rules

- **Always use yaml.safe_load extraction** — never regex, to handle folded blocks with `:`
- **Write results JSON BEFORE applying gate** — survives session compaction
- **One rewrite cycle max** — do not loop after failure
- **Self-referential: skill-quality evaluates itself** but never registers itself to catalog
- **Error-tolerant critics** — if a critic errors, proceed with null scores and flag for review
- **depth-1 spawns only** — critics are spawned directly from the calling session, not from subagents

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…