Imported from learnn-com/engineering-kb (
.skills/capability-classify/SKILL.md). Install upstream withnpx skills add learnn-com/engineering-kb --skill capability-classify. Copyright stays with the author.
/capability-classify — Objective Classification
Compile the classification matrix for a card or PR by applying the quality model — never by inventing criteria. The model is the single source of rules (§3 risk dimensions, §3.2 tier = max, §5 tag projection); this skill is the Layer-2 skill that runs those rules on demand and produces the artifact (quality-model §1). Downstream consumers (gate, /process-review, /next, automation) read the resulting matrix and tags without owning any criteria (D18).
Two invocation contexts, one model:
- refinement — evidence is the story context (declared/estimated scope, touched subdomains, cross-context integrations). Shift-left (R1.3): the matrix exists before code does.
- review — evidence is the code/diff (observed footprint,
/capability-assess-securityverdict,/capability-assess-couplingverdict). The review value is a floor: it confirms or raises the refinement tier, and never lowers it (quality-model §3.2, D17).
Arguments
| Argument | Required | Description |
|---|---|---|
$context |
No | refinement — classify from the story context. review — classify from the diff. Auto-detected: called by /process-refine-story → refinement; by /process-review → review; a PR/diff in context → review; otherwise → refinement. |
$target |
No | The card/PR to classify (story ID or PR number). Default: the item in context. The target names the side every write goes to: a card ⇒ the PM tool, a PR ⇒ the code host (they are the same tool unless the project declares code-host — see the routing table). |
$override |
No | An explicit tier or dimension override passed by a human (resolution cascade Argument layer, quality-model §Resolution order). Raises only — never lowers a review value below the model result. |
Prerequisite: the quality model
The rules this skill applies live entirely in the quality model (#221) plus its optional adoption delta tech/risk-matrix.md (§6). This skill contains no classification criteria of its own — it is grep-verifiably a model-applier, not a model-owner.
No quality model doc installed ⇒ HALT with a pointer to the #221 prerequisite: classify cannot apply rules that are not present. This is the one hard stop; every other absence degrades gracefully to a KB default (D21).
Resolution order
Every rule resolves through the quality model's cascade — Argument ($override) > Adoption (tech/risk-matrix.md) > KB default (quality-model.md). A malformed adoption file is treated as absent: warn and fall back to KB defaults entirely, including no tag emission (quality-model §6, D21).
Algorithm
Step 0: Detect Context
- Check: Is
$contextprovided? - Skip: If provided, use it. Proceed to Step 1.
- Act: Auto-detect — invoked by
/process-refine-story→refinement; by/process-review, or with a PR/diff in context →review; otherwise →refinement. - Verify: Context is set to exactly one of
refinement|review.
Step 1: Load the Model
- Act: Read quality-model.md. If absent → HALT (pointer to #221).
- Act: Read
tech/risk-matrix.mdif present — its up-to-three independent sections (## Tag Projection,## Criticality Table,## Overrides, quality-model §6); the presence of one never implies the others. Absent or malformed ⇒ KB defaults apply completely (D21). - Verify: The effective rule set is assembled — KB defaults, plus whichever adoption sections parsed cleanly. Adoption absence is logged as "KB defaults" in the output, not treated as a gap.
Step 2: Gather Evidence
Per $context, collect the source for each dimension (quality-model §3.1, "Source" column):
- refinement — from the story body: declared scope (change/diff-risk proxy), the subdomain classification of what it touches (business impact), touched-path heuristics (security relevance), and — if
subdomain//boundedcontext/artifacts exist — the volatility of touched subdomains plus the cross-context integrations the story introduces (coupling balance). - review — from the diff: observed footprint (change/diff risk), the
/capability-assess-securityreview verdict (security relevance, quality-model §3.1), and the/capability-assess-couplingverdict on the diff (coupling balance). An unreadable diff (huge/binary) falls back to file-path/service-level evidence and flags low confidence in the<details>.
Coupling sources absent (no subdomain/bounded-context artifacts in refinement, /capability-assess-coupling not installed in review) ⇒ the coupling dimension is reported not assessed, excluded from the tier max, and never blocks (quality-model §3.1, D21).
Step 3: Compile the Matrix
- Act: Resolve each of the five dimensions (Service/domain criticality, Change/diff risk, Business impact, Security relevance, Coupling balance) to
green/yellow/redper quality-model §3.1, applying the resolution cascade (Step 1's effective rule set) to each — e.g. a service listedHighin the Criticality Table overrides the Medium default (AC5); a service not listed in an existing table resolves to conservative High. - Act: Risk tier = max(assessed dimensions) (quality-model §3.2), projected as
risk:green|yellow|red. Compute the cost class independently as the highest detected signal (quality-model §3.3,cost:green|yellow|orange|red) — always written to the body, never part of the risk max. - Act (review only, never-lower): read the refinement-time tier from the story body — an item read ⇒
pm-tool(routing table), the opposite side from Step 4's PR write. When the tools are split, resolve the story's item id from the PR'sRefs:cross-link line first (composed from/process-reviewthe story is already in session; the standalone$target: <PR>path has only the PR, and without the id the refinement floor is unreadable — the never-lower rule would silently degrade to the review value alone, the D17 violation this point exists to prevent).Refs:absent on a split project ⇒ report the floor as unreadable and keep the review value, flagged, rather than treating it asgreen. The review tier ismax(review-computed, refinement-tier)per dimension and overall — the review pass may raise a dimension (e.g. the diff reveals a schema migration ⇒risk:red) but never lowers a value the story's refinement already set (quality-model §3.2, D17). A$overridemay raise further; it never lowers below the model result. - Verify: Every dimension is
green/yellow/red/not assessed; the tier equals the max of the assessed dimensions; in review, no dimension is below its refinement value. Determinism: the same card/PR under the same model + adoption yields an identical matrix and tier on every run (AC4).
Step 4: Write the Matrix to the Body (always)
- Act: Render the matrix as 1 line +
<details>(D22 reading budget — never an inline table) and write it into the target body's classification section — card ⇒ the PM tool, PR ⇒ the code host (routing by target, per the routing table; one tool unlesscode-hostis declared). In review context the target is the PR, so this is a code-host write. This write happens regardless of tag projection — §3.2/§3.3 body output never depends on it. - Verify: The body carries a 1-line verdict (
risk:<tier>, pluscost:<class>and any not-assessed note) and a collapsed<details>with the per-dimension breakdown and confidence.
Step 5: Tag Projection (adoption-gated)
Tag emission is declared, not implicit (quality-model §5) — the matrix in Step 4 stands whether or not any tag is emitted.
-
Check: Does
tech/risk-matrix.mdhave a## Tag Projectionsection? -
Skip: If yes, use it exactly as written — apply the chromatic tag for each
Activeparameter (risk:green|yellow|red;cost:green|yellow|orange|redonly ifcostis listed; a renamed label per §5 keeps the same color values) — and never propose again.Active: noneis a durable opt-out: emit no tags, do not ask. -
Act: If no
## Tag Projectionsection, propose only theriskdefault (propose-then-write-if-confirmed, the pattern/capability-verify-qualityuses for its first-run Custom Gate Registry):No Tag Projection declared yet. Activate
risk:green|yellow|redon stories and PRs? (recommended — other model parameters can be exposed as tags later, if you decide you want them)- Yes, activate
risk(writes the declaration) - No, don't tag anything (records the opt-out so this isn't asked again)
On yes, write the
## Tag Projection→Active: risksection intotech/risk-matrix.md; on no, writeActive: none. This registry write isclassify's own (the §5 precedent — the same self-write/capability-verify-qualityperforms for its gate registry), not adoption decision content, so it does not route through/capability-record-decision. - Yes, activate
-
Act: Apply the resolved tags to the target — card ⇒ PM-tool labels, PR ⇒ code-host labels (same routing as Step 4).
classifyoutput wins for classification tags (risk:*,cost:*, …); foreign/manual tags are left untouched. The target's host lacks label-API access (PM tool for a card, code host for a PR) ⇒ the matrix stays in the body (Step 4), the tagging failure is reported, non-blocking (AC edge case). -
Verify: Either the declared tags are applied, or the proposal was answered and recorded (no re-prompt next run), or the opt-out is honored — and the body matrix from Step 4 exists in every case.
Output Format
CLASSIFY ([refinement | review]):
├── Target: [#story | #PR]
├── Tier: risk:[green | yellow | red] [(raised from refinement risk:yellow) — review only | (refinement floor unreadable — review value only, NOT confirmed) — review only]
├── Cost: cost:[green | yellow | orange | red]
├── Model: [KB defaults | + Criticality Table | + Overrides | + Tag Projection]
├── Tags: [applied: risk:red | proposed (awaiting answer) | opted out (Active: none) | tagging failed — non-blocking]
└── Body: matrix written (1 line + <details>, D22)
<details>
<summary>Matrix — per dimension</summary>
| Dimension | Tier | Source | Note |
| --- | --- | --- | --- |
| Service/domain criticality | [g/y/r] | [Criticality Table | KB default] | |
| Change/diff risk | [g/y/r] | [story scope | diff footprint] | |
| Business impact | [g/y/r] | [subdomain class] | |
| Security relevance | [g/y/r] | [path heuristic | assess-security verdict] | |
| Coupling balance | [g/y/r | not assessed] | [subdomain volatility + integrations | assess-coupling verdict | absent] | |
Confidence: [high | low — unreadable diff, path/service-level fallback | low — refinement floor unreadable]
</details>
Worked Examples
Hand-traced fixtures that pin the two behavioural invariants — determinism (AC4) and never-lower (D17) — to concrete matrices. Each example lists every dimension value and the resulting tier so the rule (risk tier = the max of the assessed dimensions, floored by the refinement pass in review) is auditable, not merely asserted. These fixtures are conformance-checked (src/conformance/capability-classify.test.ts): each must carry all five dimensions and a tier that follows the max-rule — and, in review, the refinement floor.
Legend — dimension values are green | yellow | red | not assessed; not assessed is excluded from the max (D21). Tier is risk:<green|yellow|red>.
Worked Example 1 — Determinism (refinement)
Same story context classified on two independent runs yields an identical matrix (AC4). Story context: an internal supporting-subdomain UI tweak — reposition the settings icon; no data-model change, no security-relevant paths, no cross-context integration, no DDD artifacts present.
| Run | Service/domain criticality | Change/diff risk | Business impact | Security relevance | Coupling balance | Tier |
|---|---|---|---|---|---|---|
| A | green | green | green | green | not assessed | risk:green |
| B | green | green | green | green | not assessed | risk:green |
Run A and Run B are byte-identical — no randomness, no time-dependence. Tier = max(green, green, green, green) = risk:green on both runs.
Worked Example 2 — Never-lower, benign diff (review)
The story was refined at risk:yellow (Change/diff risk yellow — a moderate declared scope). The review diff is benign: a docs paragraph and a user-facing copy string, no logic, no schema, no security paths. The review-computed dimensions are all green, but the refinement floor holds the tier at yellow — the review confirms, it never lowers (D17).
| Pass | Service/domain criticality | Change/diff risk | Business impact | Security relevance | Coupling balance | Tier |
|---|---|---|---|---|---|---|
| refinement | green | yellow | green | green | not assessed | risk:yellow |
| review | green | green | green | green | not assessed | risk:yellow |
Review raw max = green, but max(review, refinement-floor) = max(green, yellow) = risk:yellow. The tier stays yellow; nothing is lowered.
Worked Example 3 — Never-lower, risky diff raises (review)
Same refinement start (risk:yellow), but the review diff reveals a database schema migration plus a change to an authentication path. Change/diff risk rises to red and Security relevance rises to red, so the tier is raised to risk:red — above both the review-nothing case and the refinement floor.
| Pass | Service/domain criticality | Change/diff risk | Business impact | Security relevance | Coupling balance | Tier |
|---|---|---|---|---|---|---|
| refinement | green | yellow | green | green | not assessed | risk:yellow |
| review | green | red | green | red | not assessed | risk:red |
Review raw max = red (Change/diff risk, Security relevance), so max(red, yellow-floor) = risk:red. The tier is raised, never lowered — a later benign diff could confirm red but could not drop it below the refinement floor.
Composition Interface
Composed by /process-refine-story (refinement): invoked with $context: refinement after the technical analysis. Returns the matrix; /process-refine-story embeds the 1-line + <details> into the story body (D22) and lets classify apply/propose tags. Absent ⇒ /process-refine-story warns and continues without a matrix (graceful degradation — never HALTs on classify's absence).
Composed by /process-review (review, Phase 1): invoked with $context: review against the PR diff. Produces the review-time matrix (confirm-or-raise, never lower). The /capability-assess-security verdict (Phase 2) feeds the Security relevance dimension and the /capability-assess-coupling verdict feeds Coupling balance — both raise-only, consistent with the review floor. Any raise to risk:red is surfaced for /process-review's own decision step; classify never blocks or merges — it has no such authority.
Invoked directly: classify a single card or PR on demand; same algorithm, $context auto-detected.
Edge Cases
- No quality model doc installed: HALT, pointer to #221. (The only hard stop.)
tech/risk-matrix.mdabsent or malformed: KB defaults apply completely; no tags emitted; nothing fails (quality-model §6, D21).- Conflicting manual tags on the card:
classifyoutput wins for classification tags (risk:*,cost:*); foreign tags untouched. - Unreadable diff (huge/binary): fall back to file-path/service-level evidence; flag low confidence in the
<details>. - Projection defined but the target's host lacks label-API access (PM tool for a card, code host for a PR): matrix still written to the body; tagging failure reported, non-blocking.
- Coupling sources absent: coupling dimension "not assessed", excluded from the max, never blocks (D21).
- Refinement floor unreadable (review, split tools, the PR carries no
Refs:line — or the PM item is unreachable): the never-lowermax()has no floor to apply, so report the tier as the review-computed value, explicitly flagged as unconfirmed (Tier: risk:yellow (refinement floor unreadable — review value only, NOT confirmed),Confidence: low) and name the missing cross-link. Never silently present it as a confirmed tier: the floor may have been higher (D17). - PR with no classification present (read by a downstream consumer, not produced here): treated as
red(fail-safe, quality-model §3.2).
Graceful Degradation
See graceful degradation (guideline missing → minimal run, ask directly; adoption file missing → run against KB defaults; the target's host unreachable — PM tool for a card, code host for a PR → matrix returned to caller, tagging deferred). Additional cases:
/capability-assess-securitynot available (review): fall back to the path-heuristic security relevance (quality-model §3.1) instead of the verdict; note the fallback./capability-assess-couplingnot installed / no DDD artifacts: coupling dimension "not assessed" (never a HALT)./process-refine-storyor/process-reviewabsent as caller: run standalone via direct invocation.
Notes
- Applies the model, owns no criteria (D18) — grep-verifiable: no green/yellow/red threshold lives in this file that is not a reference back to quality-model §3. Consumers downstream (gate,
/process-review,/next) read tags only. - Built exactly twice per work item (D17): refinement (story context) and review (diff). Review confirms or raises, never lowers — the never-lower rule is a floor over the refinement tier, per dimension and overall.
- Deterministic: same input (card/PR + model + adoption) ⇒ same matrix and tags every run (AC4). No randomness, no time-dependence.
- Reading budget (D22): the body/report output is 1 line +
<details>, never an inline table. - Writes two kinds of thing itself: the matrix into the card/PR body (Step 4) and — only on a confirmed first-run proposal — the
## Tag Projectionregistry section intech/risk-matrix.md(Step 5, the quality-model §5 self-write precedent, mirroring/capability-verify-quality's Custom Gate Registry). Adoption decision content is never self-written — that routes through/capability-record-decision; the Tag Projection section is config-registry state, not a decision record. - No eligibility tag: automation eligibility is an adoption-declared filter over classification tags (
risk:green, …), not a special tag —/nextconsumes it generically (quality-model §5, D18). - Idempotent — see idempotency convention: re-running on an already-classified card recomputes the same matrix; a declared or opted-out Tag Projection is never re-proposed. Review re-runs recompute against the current diff (a raise can happen commit-to-commit; a lower never can).