Skip to content

feat(skills): dot- skill governance — catalog, scaffolder, lint & CI gate#36466

Open
nollymar wants to merge 10 commits into
mainfrom
worktree-skill-governance
Open

feat(skills): dot- skill governance — catalog, scaffolder, lint & CI gate#36466
nollymar wants to merge 10 commits into
mainfrom
worktree-skill-governance

Conversation

@nollymar

@nollymar nollymar commented Jul 8, 2026

Copy link
Copy Markdown
Member

What & why

Establishes governance for first-party skills so new skills stay consistent and non-duplicative as the team resumes adding them. The core principle: every rule is enforced by tooling, not by memory — via one of three layers:

  • Generate — the scaffolder produces correct-by-construction skills
  • Gate — CI fails the PR on any violation (tool-agnostic; works whether a skill was authored with just new-skill, skill-creator, or a hand-copy)
  • Flag — surfaces the one thing automation can't decide (semantic duplicates)

Naming

First-party skills use dot-<domain>-<action> (provenance at a glance in the flat runtime skill list). Approved domains: issue, pr, release, cicd, content, ui.

What's included

File Role
.claude/skills/skills.config.json Single source of truth (prefix, domains, required fields, grandfather list)
.claude/tools/gen-skills-catalog.mjsCATALOG.md Auto-generated inventory (never hand-edited)
.claude/tools/new-skill.mjs Scaffolder with creation-time duplicate grep
.claude/tools/skill-lint.mjs Validates naming, frontmatter, supersedessuperseded-by, catalog freshness
.github/workflows/cicd_pr_skill-lint.yml CI gate (path-filtered on .claude/skills/**), mirrors cicd_pr_qa-stuck-check-validate.yml
.claude/skills/CONTRIBUTING.md Naming, frontmatter, status lifecycle, 4-stage workflow
justfile just new-skill / skills-catalog / skills-lint

Zero npm deps; Node-only (.nvmrc = 22.15).

Status model

experimental → active → superseded. New skills scaffold as experimental; author may open as active if confident. Retirement uses supersedes/superseded-by, whose consistency CI enforces.

Grandfathering

Skills predating dot- are listed in skills.config.json and exempt (their issues are warnings, not failures), so CI is green day one. The linter already surfaces the punch-list for the follow-up rename PR:

  • check-release-rollback/SKILL.md has no frontmatter
  • sdk-analytics name doesn't match its directory

Verified

Ran the tooling against real scenarios: valid scaffold ✅, bad prefix ❌, missing owner ❌, stale catalog ❌, broken supersedes link ❌, correct supersedes ✅, baseline legacy ✅ (warnings only).

Follow-ups (separate PRs)

  • Rename legacy skills to dot-* + add owner/status, remove from grandfather list

🤖 Generated with Claude Code

This PR fixes: #36467

… CI gate

Enforce first-party skill conventions through tooling rather than
discipline, so the rules hold regardless of how a skill is authored
(just new-skill, skill-creator, or hand-copy).

- skills.config.json: single source of truth (dot- prefix, approved
  domains, required fields, grandfather list)
- gen-skills-catalog.mjs + CATALOG.md: auto-generated inventory
- new-skill.mjs: scaffolder with creation-time duplicate check
- skill-lint.mjs + cicd_pr_skill-lint.yml: CI gate for naming,
  frontmatter, supersedes<->superseded-by consistency, catalog freshness
- CONTRIBUTING.md: naming, frontmatter, status lifecycle, workflow
- justfile: new-skill / skills-catalog / skills-lint recipes

Existing pre-dot- skills are grandfathered (warnings, not failures);
a follow-up PR will rename them and add owner/status.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@nollymar
nollymar requested a review from a team as a code owner July 8, 2026 00:16
@github-actions github-actions Bot added the Area : CI/CD PR changes GitHub Actions/workflows label Jul 8, 2026
@claude

claude Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @nollymar's task in 3m 17s —— View job


Review

Reviewed the governance tooling (skill-lib.mjs, skill-lint.mjs, new-skill.mjs, gen-skills-catalog.mjs), skills.config.json, CATALOG.md, the CI workflow, and the justfile targets. The design is clean — pure buildCatalog() shared between generator and linter, a single skillNameRegex() shared between gate and scaffolder, bidirectional supersedessuperseded-by enforcement, and external symlinks keyed on directory name so the catalog is deterministic without .agents/skills checked out. Zero deps, path-filtered CI.

New Issues

  • 🟡 Medium: .claude/tools/skill-lib.mjs:73listSkills() sorts with a.dir.localeCompare(b.dir). localeCompare ordering is locale/ICU-dependent, and this ordering directly determines row order in CATALOG.md, which the linter byte-compares against the committed file (skill-lint.mjs catalog-freshness check). If the runner's ICU/locale collates the hyphenated ASCII names differently than the author's machine, CI reports the catalog as stale even though it was regenerated correctly — a flaky gate that can't be fixed by re-running just skills-catalog locally.
    • Assumption: GitHub ubuntu-24.04 runners and contributor machines could resolve punctuation collation (-) differently under default ICU variable-weighting.
    • What to verify: regenerate the catalog under LC_ALL=C vs en_US.UTF-8 and confirm identical row order. If they differ, switch to a deterministic code-point comparator, e.g. .sort((a, b) => (a.dir < b.dir ? -1 : a.dir > b.dir ? 1 : 0)). For the current all-lowercase dot-* names the risk is low, hence non-blocking. Fix this →

Notes (non-blocking)

  • skills.config.json approvedDomains includes sdk, which the PR description's domain list (issue, pr, release, cicd, content, ui) omits. This is correctdot-sdk-analytics needs it to pass lint — but the PR body is out of sync. Worth updating the description so the approved-domain set isn't misread later.

Nothing else stood out: the frontmatter parser's block-scalar/inline-array branches and the baseIndent-clamped strip handle the empty/last-line cases safely; the supersede checks are genuinely bidirectional (no half-retirement gap); and grandfathering correctly demotes skill-doctor's issues to warnings so CI is green on day one.

· worktree-skill-governance

@nollymar

nollymar commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

Tracked by #36467.

…olve

The catalog lists external skills, which are symlinks into .agents/skills.
Without that path in the job's sparse-checkout the symlinks dangle in CI,
the generator emits "0 external", and the catalog-freshness check reports
CATALOG.md as stale. Add .agents/skills so CI matches local output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add `deprecated` status for skills retired with no replacement
  (distinct from `superseded`, which points to a successor). Added to
  validStatuses and documented in CONTRIBUTING with the superseded-vs-
  deprecated distinction and both retirement paths.
- Clarify in CONTRIBUTING when to run `just skills-lint`: optional, before
  pushing a PR and especially after hand-editing a skill without the
  scaffolder (when you must also regenerate the catalog).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ening

From the code review on #36466:

- skill-lint no longer mutates CATALOG.md. Extracted a pure buildCatalog()
  into skill-lib (returns the string, no write); the generator writes it and
  the linter diffs the committed file against it. Previously lint exec'd the
  generator, which wrote to disk even during a read-only check.
- new-skill: drop the dead near-match filter clause (`|| existing.some(...)`
  was constant w.r.t. the item and masked by the later collision exit).
- skill-lib block-scalar parser: clamp the indent strip to each line's own
  leading whitespace so an inconsistently-indented continuation line can't
  lose real characters.
- skill-lint naming regex: reject leading/trailing/double hyphens
  (dot-<domain>-[a-z0-9]+(-[a-z0-9]+)*).
- skill-lint CI: add .agents/skills/** to path filter so external-skill
  changes that alter catalog output re-run the freshness gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ser/filter

From the re-review on #36466:

- skill-lint: add the reverse supersede pass. A skill declaring
  `superseded-by: X` is now validated (X must exist and declare
  `supersedes:` back), matching the forward check — so retirement can't be
  half-done from either side.
- skill-lib parseFrontmatter: tolerate extra/aligned whitespace after the
  colon (`\s?`→`\s*` + trim), so `related:  [a, b]` parses as an array
  instead of being silently misread as a scalar string (which dropped the
  links from the catalog).
- skill-lint: align the duplicate-similarity grandfather filter with the
  per-skill check (key on `fm.name || dir`, not `dir` alone).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…older name guard

From the re-review on #36466:

- Catalog no longer depends on symlink targets being checked out. listSkills
  includes external symlinks even when their target is absent, and buildCatalog
  keys external rows on the directory name (not read-through frontmatter). The
  catalog is now byte-identical with or without .agents/skills present, so the
  confusing "CATALOG.md is stale" failure mode from a dropped sparse-checkout
  path can't happen. Consequently removed the now-vestigial .agents/skills
  entries from the workflow's sparse-checkout and path filter (symlink add/
  remove is already covered by .claude/skills/**).
- Scaffolder validates the derived name against the linter's pattern before
  creating the directory, so it can't produce a skill that immediately fails
  lint (correct-by-construction). The naming regex is now a single shared
  helper (skillNameRegex) used by both new-skill and skill-lint.
- CONTRIBUTING §2 frontmatter example: add `deprecated` to the status comment.

Not changed (reviewed, low-value): comma-split array parser (skill names have
no commas); supersede targets resolved within first-party set (by design).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a one-liner: use just new-skill so a skill is born passing CI; use
skill-creator for help authoring content; CI judges the result either way.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…l-creator

Clarify the two are complementary and give the recommended order (scaffold
with just new-skill, then use skill-creator on the existing skill, then
regenerate the catalog and lint before the PR).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to #36466. Tracked by #36467.

> **Stacked PR** — base is `worktree-skill-governance` (needs
`skills.config.json` + `skill-lint`). Retarget to `main` after #36466
merges.

## What

Brings the pre-`dot-` first-party skills into convention and adds
required `owner`/`status` frontmatter, so they pass `skill-lint` as full
first-party skills (removed from the grandfather list). Result:
**`skill-lint` passes with 0 warnings**.

| Old | New | Domain added? |
|---|---|---|
| `dotcms-github-issues` | `dot-issue-manage` | |
| `cicd-diagnostics` | `dot-cicd-diagnose` | |
| `lts-backport` | `dot-release-backport-lts` | |
| `check-release-rollback` | `dot-release-rollback-check` | *+ added
missing frontmatter* |
| `vtl-migration` | `dot-ui-vtl-migration` | |
| `sdk-analytics` | `dot-sdk-analytics` | `sdk` |

`skill-doctor` **stays grandfathered** — it's a vendored/generic skill
we didn't author, so it keeps its upstream name and no `dot-` prefix.

> **Note:** The `triage` skill is **not** part of this rename — it is
being migrated to a separate repo, so it has been removed here rather
than renamed/governed. Its associated agents (`dotcms-code-researcher`,
`dotcms-issue-validator`, `dotcms-team-router`,
`dotcms-duplicate-detector`) and the `/triage-pr-review` command still
live in `.claude/` and are out of scope for this PR.

## References updated (so nothing breaks)

- `/create-issue`, `/query-issue`, `/find-issues`, `/update-issue`
command bodies → point at `dot-issue-manage`
- `/check-release-rollback` command body → `dot-release-rollback-check`
- `settings.json` permission paths → `dot-cicd-diagnose`
- Each renamed skill's internal self-path references
(`.claude/skills/<old>/…`)

## ⚠️ Two things for reviewers to confirm

1. **Owners are best-guess placeholders** (`@dotCMS/scout` /
`@dotcms/platform`) — please correct to the real owning teams.
2. **Directly-invoked skills change their slash-command name**:
`/cicd-diagnostics → /dot-cicd-diagnose`, `/lts-backport →
/dot-release-backport-lts`, `/vtl-migration → /dot-ui-vtl-migration`,
`/sdk-analytics → /dot-sdk-analytics`. Command-fronted skills (issue
commands, rollback check) keep their command names. If any `/old-name`
muscle memory is heavily used, we can add thin alias commands — flag it.

## Verified

`just skills-lint` → ✅ pass, 7 first-party skills, **0 warnings** (was 3
legacy warnings before). Catalog regenerated. Swept for stale old-name
references — none remain in tracked files.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added Area : Documentation PR changes documentation files and removed AI: Safe To Rollback labels Jul 13, 2026
---
name: lts-backport
name: dot-release-backport-lts
owner: "@dotcms/platform"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

owner should be maintenance

@@ -1,3 +1,10 @@
---
name: dot-release-rollback-check
owner: "@dotcms/platform"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

owner should be maintenance or support

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : CI/CD PR changes GitHub Actions/workflows Area : Documentation PR changes documentation files

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Skill governance: dot- naming convention, catalog, lint & CI gate

2 participants