Skip to content

Commit a2bd283

Browse files
ref(skills): retire the router and category concept (#311)
Dissolving the last routers (#310) emptied both category buckets, but the generator still emitted their headings, intro prose, and a bare table header with no rows underneath. That ships to every agent that reads SKILL_TREE.md as two sections advertising capabilities with nothing in them. Removing just those sections would leave a worse state behind: a skill carrying a category would still be scanned and validated, but nothing would render it, so it would vanish from the tree with the script reporting success. So retire the concept instead of half of it. Nothing needs it. No skill carries category, parent, role, or disable-model-invocation; outside this script nothing reads those fields at all. What remained was ~120 lines validating and rendering a model the project has abandoned -- router roles, parent-must-be-a-router, leaf-listed-in-its-router, per-category tables -- none of it reachable. The hierarchy validation is replaced by the rule that actually holds now: those four fields are rejected outright, so a stale copy-paste or an attempt to reintroduce routing fails loudly rather than dropping a skill silently. The categorize pass collapses with it: one flat table means every skill is a row, so ALL_SKILLS is the list and the STANDALONE/ROUTERS split has nothing left to distinguish. The heading becomes "Available Skills" -- "Standalone" only meant something in contrast to the category sections that are gone. The inert breadcrumb check goes too. It skipped every skill that was neither a router nor categorized, which is all of them, so no link has been checked for some time; replacing it is a separate change.
1 parent da5b79c commit a2bd283

2 files changed

Lines changed: 34 additions & 223 deletions

File tree

scripts/build-skill-tree.sh

Lines changed: 31 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
# ============================================================
33
# build-skill-tree.sh — Generate and validate the Sentry skill tree
44
# ============================================================
5-
# Scans all src/skills/*/SKILL.md files, regenerates src/SKILL_TREE.md,
6-
# validates the skill hierarchy, and checks breadcrumb links.
5+
# Scans all src/skills/*/SKILL.md files, regenerates src/SKILL_TREE.md, and
6+
# validates each skill's frontmatter.
7+
#
8+
# Link checking lives in validate-skill-links.py — it needs the reference
9+
# manifests, which this script does not read.
710
#
811
# Usage:
912
# scripts/build-skill-tree.sh # regenerate + validate
@@ -111,98 +114,18 @@ done < <(find "$SKILLS_DIR" -name "SKILL.md" | sort)
111114
TOTAL_SKILLS=${#ALL_SKILLS[@]}
112115

113116
# ============================================================
114-
# SECTION 3: Categorize
115-
# ============================================================
116-
117-
ROUTERS=()
118-
STANDALONE=()
119-
SKILLS_SDK_SETUP=()
120-
SKILLS_WORKFLOW=()
121-
SKILLS_FEATURE_SETUP=()
122-
123-
for name in "${ALL_SKILLS[@]}"; do
124-
role="$(skill_get "$name" role)"
125-
cat="$(skill_get "$name" category)"
126-
127-
if [[ "$role" == "router" ]]; then
128-
ROUTERS+=("$name")
129-
elif [[ -z "$cat" ]]; then
130-
# Standalone skill: flat and self-contained, no router/category. These are
131-
# the next-generation skills; the router/leaf skills below are migrating
132-
# toward this shape.
133-
STANDALONE+=("$name")
134-
else
135-
case "$cat" in
136-
sdk-setup) SKILLS_SDK_SETUP+=("$name") ;;
137-
workflow) SKILLS_WORKFLOW+=("$name") ;;
138-
feature-setup) SKILLS_FEATURE_SETUP+=("$name") ;;
139-
internal) ;; # validated but not shown in public skill tree
140-
esac
141-
fi
142-
done
143-
144-
TOTAL_ROUTERS=${#ROUTERS[@]}
145-
146-
# ============================================================
147-
# SECTION 4: Generate SKILL_TREE.md content
117+
# SECTION 3: Generate SKILL_TREE.md content
148118
# ============================================================
149119

150-
# Extract a short column value from a description.
151-
# sdk-setup: "Full Sentry SDK setup for X." -> "X"
152-
# others: first sentence
153-
get_column_value() {
154-
local desc="$1"
155-
local category="$2"
156-
157-
case "$category" in
158-
sdk-setup)
159-
echo "$desc" \
160-
| sed 's/Full Sentry SDK setup for //' \
161-
| sed 's/\. .*//' \
162-
| sed 's/\.$//'
163-
;;
164-
*)
165-
echo "$desc" \
166-
| sed 's/\. .*//' \
167-
| sed 's/\.$//'
168-
;;
169-
esac
170-
}
171-
172-
column_header() {
173-
case "$1" in
174-
sdk-setup) echo "Platform" ;;
175-
workflow) echo "Use when" ;;
176-
feature-setup) echo "Feature" ;;
177-
internal) echo "Purpose" ;;
178-
*) echo "Notes" ;;
179-
esac
180-
}
181-
182-
# Build markdown table rows for a list of skills in a category
183-
build_table_rows() {
184-
local category="$1"
185-
shift
186-
local skills=("$@")
187-
188-
for name in ${skills[@]+"${skills[@]}"}; do
189-
local file desc col_val
190-
file="$(skill_get "$name" file)"
191-
desc="$(skill_get "$name" desc)"
192-
col_val="$(get_column_value "$desc" "$category")"
193-
printf "| %s | [\`%s\`](%s) |\n" "$col_val" "$name" "${file#src/}"
194-
done
195-
}
196-
197120
# Escape characters that would break a markdown table cell.
198121
escape_cell() {
199122
printf '%s' "$1" | tr '\n' ' ' | sed 's/|/\\|/g'
200123
}
201124

202-
# Build markdown rows for standalone skills, using the full description (it is
203-
# the routing signal for these flat skills).
204-
build_standalone_rows() {
205-
for name in ${STANDALONE[@]+"${STANDALONE[@]}"}; do
125+
# Build a markdown row per skill, using the full description it is the
126+
# routing signal now that there is nothing else to route through.
127+
build_skill_rows() {
128+
for name in "${ALL_SKILLS[@]}"; do
206129
local file desc
207130
file="$(skill_get "$name" file)"
208131
desc="$(escape_cell "$(skill_get "$name" desc)")"
@@ -228,150 +151,52 @@ You are **Sentry's AI assistant**. You help developers set up Sentry, debug prod
228151
229152
2. **Wait for their answer.** Do not proceed until the user tells you what they want.
230153
231-
3. **Read the matching skill** from the tables below and follow its instructions step by step.
154+
3. **Read the matching skill** from the table below and follow its instructions step by step.
232155
233156
Each skill file contains its own detection logic, prerequisites, and configuration steps. Trust the skill — read it carefully and follow it. Do not improvise or take shortcuts.
234157
235158
---
236159
HEADER
237160

238-
# Standalone Skills — flat, self-contained; surfaced first.
239-
cat <<'STANDALONE_HEADER'
161+
cat <<'SKILLS_HEADER'
240162
241-
## Standalone Skills
163+
## Available Skills
242164
243-
Self-contained skills — start here. If you're not sure what the user needs, read `sentry-get-started`; it orients you and points to the right skill.
165+
Each one is self-contained and named for the job it does. If you're not sure what the user needs, read `sentry-get-started`; it orients you and points to the right skill.
244166
245167
| Skill | What it does |
246168
|---|---|
247-
STANDALONE_HEADER
248-
build_standalone_rows
249-
250-
# Workflows
251-
local col_wf col_fs
252-
col_wf="$(column_header workflow)"
253-
cat <<'WF_HEADER'
254-
255-
## Workflows
256-
257-
Debug production issues and maintain code quality with Sentry context.
258-
259-
WF_HEADER
260-
printf "| %s | Skill |\n" "$col_wf"
261-
printf "|---|---|\n"
262-
build_table_rows "workflow" ${SKILLS_WORKFLOW[@]+"${SKILLS_WORKFLOW[@]}"}
263-
264-
# Feature Setup
265-
col_fs="$(column_header feature-setup)"
266-
cat <<'FS_HEADER'
267-
268-
## Feature Setup
269-
270-
Configure specific Sentry capabilities beyond basic SDK setup.
271-
272-
FS_HEADER
273-
printf "| %s | Skill |\n" "$col_fs"
274-
printf "|---|---|\n"
275-
build_table_rows "feature-setup" ${SKILLS_FEATURE_SETUP[@]+"${SKILLS_FEATURE_SETUP[@]}"}
169+
SKILLS_HEADER
170+
build_skill_rows
276171

277172
printf "\n"
278173
}
279174

280175
# ============================================================
281-
# SECTION 5: Validate
176+
# SECTION 4: Validate
282177
# ============================================================
283178

284-
KNOWN_CATEGORIES=("sdk-setup" "workflow" "feature-setup" "internal")
179+
# Frontmatter fields from the retired router model. Skills are flat and
180+
# task-shaped now: one skill = one job, discoverable from its own description.
181+
# A skill carrying any of these is either a stale copy-paste or an attempt to
182+
# reintroduce routing -- both worth stopping on, since nothing renders them.
183+
# Stored field name : the frontmatter key to name in the error.
184+
RETIRED_FIELDS=("category:category" "parent:parent" "role:role" "disable:disable-model-invocation")
285185

286186
validate() {
287187
for name in "${ALL_SKILLS[@]}"; do
288-
local role cat parent disable skill_file
289-
role="$(skill_get "$name" role)"
290-
cat="$(skill_get "$name" category)"
291-
parent="$(skill_get "$name" parent)"
292-
disable="$(skill_get "$name" disable)"
293-
skill_file="$(skill_get "$name" file)"
294-
295-
# ── (a/b/c) Required fields per skill type ───────────────
296-
297-
if [[ "$role" == "router" ]]; then
298-
: # role: router is sufficient
299-
elif [[ -z "$cat" ]]; then
300-
# (a) Standalone skill — flat and self-contained. Only a name (guaranteed
301-
# via directory fallback) and a description are required; no category,
302-
# parent, breadcrumb, or disable-model-invocation.
303-
[[ -n "$(skill_get "$name" desc)" ]] || \
304-
error "$name: standalone skill missing 'description' field"
305-
elif [[ "$cat" == "internal" ]]; then
306-
# (b) Internal skills
307-
[[ "$disable" == "true" ]] || \
308-
error "$name: internal skill missing 'disable-model-invocation: true'"
309-
else
310-
# (c) Router leaf skills
311-
[[ -n "$parent" ]] || \
312-
error "$name: leaf skill missing 'parent' field"
313-
[[ "$disable" == "true" ]] || \
314-
error "$name: leaf skill missing 'disable-model-invocation: true'"
315-
fi
316-
317-
# ── (g) Warn on unknown category ─────────────────────────
318-
if [[ -n "$cat" && "$role" != "router" ]]; then
319-
local known=false
320-
for kc in "${KNOWN_CATEGORIES[@]}"; do
321-
[[ "$cat" == "$kc" ]] && known=true && break
322-
done
323-
$known || warn "$name: unknown category '$cat'"
324-
fi
325-
326-
# ── (d) Parent must exist and be a router ────────────────
327-
if [[ -n "$parent" ]]; then
328-
local parent_role
329-
parent_role="$(skill_get "$parent" role)"
330-
if [[ -z "$(skill_get "$parent" file)" ]]; then
331-
error "$name: parent '$parent' does not exist"
332-
elif [[ "$parent_role" != "router" ]]; then
333-
error "$name: parent '$parent' is not a router (role=${parent_role:-none})"
334-
fi
335-
fi
336-
337-
# ── (e) Skill appears in its router's SKILL.md ───────────
338-
if [[ -n "$parent" ]]; then
339-
local router_file
340-
router_file="$(skill_get "$parent" file)"
341-
if [[ -n "$router_file" && -f "$router_file" ]]; then
342-
if ! grep -q "$name" "$router_file" 2>/dev/null; then
343-
error "$name: not listed in router '$parent' ($router_file)"
344-
fi
345-
fi
346-
fi
347-
348-
# ── (f) Breadcrumb links resolve (router/leaf skills only) ──
349-
# Standalone skills link shared references (references/…) that are copied
350-
# in by the build's hydrate step, so they don't exist beside the raw
351-
# source; skip the sibling-link check for them.
352-
if [[ "$role" != "router" && -z "$cat" ]]; then
353-
continue
354-
fi
188+
[[ -n "$(skill_get "$name" desc)" ]] || \
189+
error "$name: missing 'description' field"
355190

356-
local skill_dir
357-
skill_dir="$(dirname "$skill_file")"
358-
359-
while IFS= read -r breadcrumb_line; do
360-
# Extract only markdown link paths ending in .md: ](path.md)
361-
# Pattern ](path) where path ends with .md (skip http links)
362-
while IFS= read -r link_path; do
363-
[[ "$link_path" =~ ^https?:// ]] && continue
364-
local resolved="$skill_dir/$link_path"
365-
if [[ ! -f "$resolved" ]]; then
366-
error "$name: broken breadcrumb link '$link_path' (resolved: $resolved)"
367-
fi
368-
done < <(echo "$breadcrumb_line" | grep -oE '\]\([^)]+\.md\)' | sed 's/^](\(.*\))$/\1/')
369-
done < <(grep '^> ' "$skill_file" 2>/dev/null || true)
191+
for entry in "${RETIRED_FIELDS[@]}"; do
192+
[[ -z "$(skill_get "$name" "${entry%%:*}")" ]] || \
193+
error "$name: '${entry#*:}' is no longer supported -- every skill is standalone"
194+
done
370195
done
371196
}
372197

373198
# ============================================================
374-
# SECTION 6: Run
199+
# SECTION 5: Run
375200
# ============================================================
376201

377202
echo "Scanning ${TOTAL_SKILLS} skills in ${SKILLS_DIR}/..."
@@ -411,7 +236,7 @@ fi
411236
# ── Summary ──────────────────────────────────────────────────
412237

413238
echo ""
414-
echo "Summary: ${TOTAL_SKILLS} skills scanned, ${TOTAL_ROUTERS} routers, ${#ERRORS[@]} errors"
239+
echo "Summary: ${TOTAL_SKILLS} skills scanned, ${#ERRORS[@]} errors"
415240

416241
if [[ ${#ERRORS[@]} -gt 0 ]]; then
417242
echo ""

src/SKILL_TREE.md

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ You are **Sentry's AI assistant**. You help developers set up Sentry, debug prod
1414

1515
2. **Wait for their answer.** Do not proceed until the user tells you what they want.
1616

17-
3. **Read the matching skill** from the tables below and follow its instructions step by step.
17+
3. **Read the matching skill** from the table below and follow its instructions step by step.
1818

1919
Each skill file contains its own detection logic, prerequisites, and configuration steps. Trust the skill — read it carefully and follow it. Do not improvise or take shortcuts.
2020

2121
---
2222

23-
## Standalone Skills
23+
## Available Skills
2424

25-
Self-contained skills — start here. If you're not sure what the user needs, read `sentry-get-started`; it orients you and points to the right skill.
25+
Each one is self-contained and named for the job it does. If you're not sure what the user needs, read `sentry-get-started`; it orients you and points to the right skill.
2626

2727
| Skill | What it does |
2828
|---|---|
@@ -34,17 +34,3 @@ Self-contained skills — start here. If you're not sure what the user needs, re
3434
| [`sentry-otel-exporter-setup`](skills/sentry-otel-exporter-setup/SKILL.md) | Configure the OpenTelemetry Collector with Sentry Exporter for multi-project routing and automatic project creation. Use when setting up OTel with Sentry, configuring collector pipelines for traces and logs, or routing telemetry from multiple services to Sentry projects. |
3535
| [`sentry-setup-releases`](skills/sentry-setup-releases/SKILL.md) | Set up Sentry releases and deploy tracking — tag events with a version and environment, create the release in CI with its commits, and wire up suspect commits and code mappings, so Sentry can show which release introduced an issue, which commit is responsible, and release health. Use when asked to set up releases, track deploys, see what changed, or when issues show an unknown release or no suspect commit. |
3636
| [`sentry-snapshots-cocoa`](skills/sentry-snapshots-cocoa/SKILL.md) | Full Sentry Snapshots setup for Apple/Cocoa projects. Use when asked to "setup SnapshotPreviews", "setup Apple snapshot testing", "upload Apple snapshots to Sentry", "setup Apple snapshot GitHub Actions", or "setup Apple selective snapshot testing". |
37-
38-
## Workflows
39-
40-
Debug production issues and maintain code quality with Sentry context.
41-
42-
| Use when | Skill |
43-
|---|---|
44-
45-
## Feature Setup
46-
47-
Configure specific Sentry capabilities beyond basic SDK setup.
48-
49-
| Feature | Skill |
50-
|---|---|

0 commit comments

Comments
 (0)