promote-tier — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited promote-tier (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You flip the support tier of one MCP tool, resource, or prompt atomically across BOTH source-of-truth sites: the [McpToolMetadata("category", "tier", ...)] attribute (or its resource/prompt equivalent on the catalog entry) AND the matching catalog row. The SurfaceCatalogTests parity check enforces the dual-write contract — an edit that touches one site without the other fails the test, which is the safety net for this skill.
This is a maintainer-side skill (lives at .claude/skills/promote-tier/, NOT skills/promote-tier/). It is invoked by the maintainer at release-cut time after /publish-preflight Step 8 surfaces promotion candidates. It is NOT shipped to plugin consumers.
/publish-preflight Step 8 now feeds this skill from an aggregated scorecard produced by eng/aggregate-promotion-scorecards.ps1, which gathers per-repo _latest-promotion-scorecard.json files from every configured sibling repo and applies a quorum rule before recommending a tier flip:
verdict: "promote: ready").Maintainer override. If a strong single-repo signal warrants flipping a tier without quorum (e.g. a tool whose only meaningful exercise repo is the one that audited it), the maintainer may invoke this skill directly with the name + target tier. There is no flag that bypasses the quorum gate at the aggregator layer — the override is simply "skip Step 8's surface and call /promote-tier <name> <tier> manually." Document the override rationale in the commit message or release notes so future audits can correlate the flip with its evidence.
This skill itself does NOT enforce quorum — it is a mechanical two-site flip. Quorum is /publish-preflight Step 8's contract; this skill is invoked after Step 8 has either surfaced a quorum-passing candidate or the maintainer has chosen to override.
/publish-preflight Step 8)The aggregator emits JSON of shape:
{
"schemaVersion": 1,
"entries": [
{
"kind": "tool",
"name": "scaffold_test_apply",
"category": "scaffolding",
"currentTier": "experimental",
"verdict": "promote: ready",
"promoteVotes": 2,
"sourceRepos": { "promote": ["repo-a", "repo-b"] }
}
]
}Step 8 filters entries[] by verdict == "promote: ready" and surfaces each as a /promote-tier <name> stable invocation. This skill accepts the same <name> <tier> argument shape regardless of whether the source was an aggregated quorum or a single-repo override.
The legacy single-file scorecard format (scorecard[] with per-entry recommendation) is no longer read by /publish-preflight; if a sibling repo still emits it at the deprecated <audited-repo>/ai_docs/audit-reports/_latest-promotion-scorecard.json path (the canonical per-repo path is <audited-repo>/audit-reports/ at the repo root, per #937), the aggregator prefers the canonical path and only falls back to the deprecated one, and Step 8 surfaces a one-line WARN.
Today /publish-preflight Step 8 emits a manual Edit: checklist for each promotion-recommended row:
- workspace_drift_check (tool, workspace) — currentTier=experimental, recommendation=promote
Edit: src/RoslynMcp.Host.Stdio/Tools/WorkspaceDriftTool.cs
Edit: src/RoslynMcp.Host.Stdio/Catalog/ServerSurfaceCatalog.Workspace.cs
Verify: dotnet test --filter SurfaceCatalogTestsThe maintainer then opens both files and hand-flips two literals. Pieces A (the /publish-preflight Step 8 gate) and B (the promotion scorecard JSON output) shipped in PR #496. This skill is piece C — the mechanical replacement for the hand-edit step. One invocation flips both literals and runs the parity test.
$ARGUMENTS is two space-separated tokens:
ServerSurfaceCatalog.* (e.g. workspace_drift_check, server_catalog_full, explain_error). Kebab-snake, lowercase.stable or experimental. The skill looks up the current tier first; if the requested tier matches the current tier, it refuses (no-op).Example: /promote-tier workspace_drift_check stable
If either argument is missing, refuse with a one-line usage message.
"Refusing: working tree has unstaged edits to <file>. Commit or stash before flipping tier.".ServerSurfaceCatalog. Zero matches → refuse with a "did you mean?" hint that lists the closest 3 names. Multiple matches → refuse with the conflicting entries (indicates a catalog-integrity bug that a human should fix)."Refusing: <name> is already at tier <target>. Nothing to do.".Use mcp__roslyn__symbol_search (preferred over Grep) to locate the entry in the catalog files. The ServerSurfaceCatalog.*.cs partials each carry an array of Tool(...), Resource(...), or Prompt(...) factory calls — find the one whose first string argument matches the input name. Capture:
tool | resource | prompt (which factory is used)."workspace", "server", "prompts")."stable" or "experimental").src/RoslynMcp.Host.Stdio/Catalog/ServerSurfaceCatalog.Workspace.cs.If the entry is not found, refuse per Precondition 2.
Three resolver paths, one per kind:
#### Tools (kind == tool)
The implementation site is a static method annotated with [McpServerTool(Name = "<name>")] AND [McpToolMetadata("<category>", "<tier>", ...)]. To find it:
mcp__roslyn__symbol_search with the search hint <name> to find the static method whose [McpServerTool] attribute carries Name = "<name>". (Method names usually mirror the tool name in PascalCase, but not always — the attribute is the source of truth.)[McpToolMetadata(...)] attribute literal on that method. The tier literal is parameter index 1 (0-based: category, tier, readOnly, destructive, summary).#### Resources (kind == resource)
Resources are simpler — the tier lives ONLY in the catalog entry's 3rd argument. The ServerResources.cs / WorkspaceResources.cs files do not carry a tier marker on the [McpServerResource] attribute itself. Skip Step 3's "implementation-site edit" entirely; only the catalog edit applies.
(Cross-check at impl time by reading src/RoslynMcp.Host.Stdio/Resources/ServerResources.cs — if a future PR adds a tier marker to the resource attribute, this skill must be updated to handle it. Today: catalog-only.)
#### Prompts (kind == prompt)
Same as resources today — the tier lives only in the catalog. Implementation files are at src/RoslynMcp.Host.Stdio/Prompts/RoslynPrompts.*.cs. Cross-check by reading the file; if a tier marker is found on the [McpServerPrompt] attribute or a paired metadata attribute, edit it. Today: catalog-only.
For tools, use Edit on the source file:
old_string: the attribute literal anchor — McpToolMetadata("<category>", "<currentTier>",McpToolMetadata(...) without a leading [ because the attribute often appears as the second attribute in a [Attr1, Attr2] pair (e.g. [McpServerTool(...), McpToolMetadata("workspace", "experimental", ...)]). Anchoring on [McpToolMetadata would miss those.<readOnly>, <destructive>, literals plus the next-line summary) to make the match unique within the file, but stay surgical — do NOT replace the whole attribute block.new_string: identical, with <currentTier> flipped to <targetTier>.Verify the match is unique by reading the file first (or relying on Edit's uniqueness check). If the match is not unique, escalate the old_string with more surrounding context until it is.
For resources and prompts, skip this step.
Use Edit on the catalog partial file (e.g. ServerSurfaceCatalog.Workspace.cs):
old_string: the full factory call line, e.g.Tool("workspace_drift_check", "workspace", "experimental", true, false, "Compare the in-memory workspace snapshot... Include enough context to make the match unique (the name + category + tier prefix is usually sufficient since names are unique catalog-wide).
new_string: identical, with the tier flipped.SurfaceCatalogTests to confirm parityThe SurfaceCatalogTests.McpToolMetadata_RequiredOnEveryTool_MatchesCatalogEntry test asserts that every [McpToolMetadata] attribute matches its catalog entry on every field. This is the dual-write safety net.
Run:
dotnet test --filter SurfaceCatalogTests --nologo --no-restore(Or, when the workspace is loaded, prefer mcp__roslyn__test_run --filter "SurfaceCatalogTests" for faster feedback.)
Pass → both edits landed correctly; the dual-write contract holds.
Fail → one of the edits is inconsistent. Read the failure message: it names the offending tool and the field that drifted. Fix and re-run. DO NOT proceed past this step on failure — a half-flipped tier silently misadvertises the surface.
Emit:
Promoted <name> (<kind>, <category>) from <currentTier> to <targetTier>:
Edit: <impl-site-path> (skipped for resource/prompt)
Edit: <catalog-partial-path>
Verify: SurfaceCatalogTests PASS
Next: stage the two edits, commit, and ship the tier flip in the next release. Run `/draft-changelog-entry` if a changelog fragment is desired., the README.md surface counts, or any other doc.** Tier flips are tracked in CHANGELOG.md only when the maintainer chooses to call them out — usually as a single-line "Maintenance" or "Changed" entry — and that's /draft-changelog-entry`'s job./ship (typically batched with other release-cut bookkeeping)."<name> is already <target>. No-op.".[McpServerSlashCommand]), refuse with: "Unsupported entry kind '<kind>'. The promote-tier skill currently handles tool, resource, and prompt only. Add a resolver path and round-trip test before flipping this kind."Input: /promote-tier workspace_drift_check stable
Resolution:
Resolved workspace_drift_check:
kind: tool
category: workspace
current tier: experimental
catalog entry: src/RoslynMcp.Host.Stdio/Catalog/ServerSurfaceCatalog.Workspace.cs:20
impl site: src/RoslynMcp.Host.Stdio/Tools/WorkspaceDriftTool.cs:24 ([McpToolMetadata("workspace", "experimental", true, false, ...)])
Editing impl site... ok
Editing catalog entry... ok
Running SurfaceCatalogTests... PASS
Promoted workspace_drift_check (tool, workspace) from experimental to stable.Reverting the same flip (round-trip):
Input: /promote-tier workspace_drift_check experimental
Resolved workspace_drift_check:
kind: tool
... current tier: stable
Editing impl site... ok
Editing catalog entry... ok
Running SurfaceCatalogTests... PASS
Reverted workspace_drift_check (tool, workspace) from stable to experimental.The round-trip is bit-identical — the test that pairs with this skill (tests/RoslynMcp.Tests/Skills/PromoteTierRoundTripTests.cs) asserts this contract: a forward + reverse promotion leaves the source tree exactly as it found it.
recommendation: "promote" rows in Step 8 and emits the Edit: checklist. This skill replaces the manual edits it suggests.changelog.d/<row-id>.md fragment. After a tier flip ships in a release, the maintainer may invoke this to record the promotion in the changelog (optional — minor tier flips often ride in a ## [X.Y.Z] section's "Maintenance" bullet without a dedicated fragment).changelog.d/*.md fragments into a tagged release. Tier flips are visible to consumers from this point./bump → /ship → tag → reinstall). A typical release-cut sequence is: /publish-preflight → /promote-tier (per recommended row) → /release-cut.[McpToolMetadata] attribute's parameter shape is defined at src/RoslynMcp.Host.Stdio/Catalog/McpToolMetadataAttribute.cs. Today the parameter order is (category, supportTier, readOnly, destructive, summary) — supportTier is parameter index 1 (0-based). If the attribute shape ever changes, this skill's "Step 3 — Edit the implementation site" must be updated to match.Tool, Resource, Prompt) live in src/RoslynMcp.Host.Stdio/Catalog/ServerSurfaceCatalog.cs. Same parameter-shape caveat — tier is parameter index 2 (0-based: name, category, tier, ...).SurfaceCatalogTests.McpToolMetadata_RequiredOnEveryTool_MatchesCatalogEntry test is the dual-write safety net. If a future refactor splits the catalog or reshapes the attribute, that test must continue to assert dual-site parity OR this skill needs a different verification strategy.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.