> LLM-judgment skill that critiques identifier names — variables, functions, types, and files — for clarity, concreteness, weight, and predictive power. First member of the craft-pipeline initiative. Uses a curated rubric catalog seeded from Martin / Beck / Karlton. Emits 3-axis findings (tier × impact × confidence per ADR 0019).
Scanned 9/11/2026
Install to Claude Code
npx -y skills add Intense-Visions/harness-engineering --skill naming-craft --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Naming Craft?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/intense-visions-naming-craft-a115117d)More formats (shields.io, HTML) on the badges page.
# Naming Craft
> LLM-judgment skill that critiques identifier names — variables, functions, types, and files — for clarity, concreteness, weight, and predictive power. First member of the craft-pipeline initiative. Uses a curated rubric catalog seeded from Martin / Beck / Karlton. Emits 3-axis findings (tier × impact × confidence per ADR 0019).
## When to Use
- During PR review on code that adds or renames identifiers
- When onboarding a new contributor (audit names they introduced)
- When refactoring a module (verify renamed names earn their letters)
- As the cross-cutting naming critic for other craft skills (docs-craft, test-craft, code-craft will call into this)
- NOT for code-convention enforcement (use ESLint rules — this is ceiling, those are floor)
- NOT for autofix / rename codemod (this is judgment-only; the v2 sibling `align-naming` ships the fix path)
- NOT for module / branch / commit-subject naming (v1.x — different infrastructure)
- NOT for languages beyond TS/JS in v1 (Python/Go/Rust idiom catalogs are v1.x)
## Capability Roles
<!-- Capability seam: this skill participates in a real extension point whose three roles are named and concrete. A seam with only one role filled is accidental single-implementation lock-in. See harness-skill-authoring Phase 1C. -->
- **Defines (Service Definition):** the shared craft critique contract (`packages/cli/src/shared/craft/`) — `LlmProvider` + finding/axes schema + run store — shared across all `*-craft` skills. This skill implements, and does not own, that contract.
- **Provides (Provider):** **this skill** — a identifier-naming critique implemented over the shared contract (`packages/cli/src/naming-craft/`).
- **Consumes (Consumer):** `craft-fleet` (the craft-pipeline elevation sweep) and the `harness` natural-language router, which invoke every `*-craft` provider uniformly through the shared critique/finding shape
## Process
### Phase 1: EXTRACT — Identifier walk
1. **Read project configuration.** Check `harness.config.json` for:
- `craft.naming.enabled` — gate (default `true`)
- `craft.naming.maxFiles` — file count cap (default 100)
- `craft.naming.maxIdentifiersPerFile` — per-file sampling cap (default 15)
2. **Walk project files** (.ts / .tsx / .js / .jsx). Skip `node_modules`, `dist`, `build`, `coverage`, dotdirs.
3. **Extract identifiers per file** via TS Compiler API:
- **variable** — `const x =`, `let x =`, destructuring binders
- **function** — `function x()`, `const x = () =>`, class methods, arrow functions assigned to a name
- **type** — `type X`, `interface X`, `class X`
- For each: capture file, line, exported status, scope size (`short` = body ≤10 lines; `long` = otherwise), and ±2 context lines for LLM prompt construction.
### Phase 2: SAMPLE — Convention inference
For each identifier kind, sample up to N=500 identifiers across the project and infer the dominant convention via majority-rule:
- **variables / functions** — camelCase / snake_case / PascalCase
- **types** — PascalCase / camelCase
- **files** — kebab-case / camelCase / PascalCase (basenames sans extension)
`>50%` majority threshold per kind. Below threshold → `null` (no dominant convention) and the convention-conformance rubric silently skips.
### Phase 3: CRITIQUE — Per-rubric LLM loop
For each file:
1. **Sample identifiers** weighted by importance:
- Exported identifiers first
- Then long-scope (file-level, methods on long classes)
- Then short-scope random fill
- Cap at `maxIdentifiersPerFile` per file (default 15).
2. **For each (identifier, rubric)** in the cross-product:
- Build a prompt with rubric description + identifier + context lines + project convention.
- LLM returns fenced JSON: either `null` (rubric doesn't apply / name is fine) or `{ tier, impact, confidence, message }`.
- On non-null: emit a `NamingFinding` with `cite.rubricId` populated for ADR 0020 traceability.
3. **v1 rubric catalog (6 seed rubrics):**
- `NAME-R001` **predictive power** (Martin) — does the name predict the contract?
- `NAME-R002` **concreteness** (Martin / Beck) — concrete > vague
- `NAME-R003` **verb/noun honesty** (Beck) — verb for functions; noun for types; questions for booleans
- `NAME-R004` **convention conformance** (Karlton) — matches project convention
- `NAME-R005` **scope match** (Beck) — length proportional to scope
- `NAME-R006` **encoded measure** (Pragmatic Programmer) — silent units cause real bugs
### Phase 4: REPORT — Aggregate + cost telemetry
Emit `NamingCraftOutput`:
```ts
{
findings: NamingFinding[];
summary: {
phaseRun: ['critique'];
durationMs: number;
llmCalls: { provider, model, count, costUsd };
catalog: { rubricsApplied: string[] };
convention: { variables, functions, types, files };
runId: string;
}
}
```
## Harness Integration
- **`harness naming-craft`** — CLI entry. `--files <glob>` / `--kinds <variable|function|type|file>` / `--max-files <n>` / `--max-identifiers-per-file <n>` / `--json` / `--verbose`.
- **`mcp__harness__naming_craft`** — MCP tool. Two modes (see "In-session flow" below).
- **`mcp__harness__naming_craft_finalize`** — MCP tool that completes the in-session flow.
- **Cross-cutting API:** `critiqueNamesInFile(file, opts)` exported from `packages/cli/src/naming-craft/index.ts`. Future craft skills (docs-craft, test-craft, code-craft) import and invoke this when they want naming critique on a file they're already processing — no project re-walk needed.
- **LLM provider:** configured in `harness.config.json`. The shared selector in `packages/cli/src/shared/craft/llm/provider.ts` reads two blocks:
- **`agent.backends`** — named backend definitions, shared with the orchestrator. Supported types: `claude`, `anthropic`, `openai`, `local`, `pi`, `mock`. (`local` and `pi` are OpenAI-compatible — point them at Ollama / LM Studio / vLLM / LiteLLM / any compliant server.)
- **`craft.llm`** — either `{ "backend": "<name>" }` to route through one of the entries above, or `{ "mode": "in-session" | "mock" }` for the non-backend modes. Default when nothing is set: `in-session` (host chat answers prompts via the two-step MCP flow).
- **`HARNESS_CRAFT_LLM`** env var overrides the file. Accepts `in-session`, `mock`, or the name of any entry in `agent.backends`.
### Migration from `harness.orchestrator.md`
If you already declared `agent.backends` in `harness.orchestrator.md`, the craft selector reads from it as a fallback and emits a one-time warning on first run. Run `harness migrate backends` (preview with `--dry-run`) to copy the entries into `harness.config.json` so both files share a single source of truth.
### Example
Example config snippet for routing craft skills to a local Ollama:
```jsonc
{
"agent": {
"backends": {
"ollama": {
"type": "local",
"endpoint": "http://localhost:11434/v1",
"model": ["deepseek-coder-v2", "qwen3:8b"],
},
},
},
"craft": { "llm": { "backend": "ollama" } },
}
```
## In-session flow (default)
When `HARNESS_CRAFT_LLM` is unset (or set to `in-session`), the MCP tool does **not** call any LLM. Instead it returns a list of prompts for the calling agent to answer with its own model. This is a two-step protocol:
**Step 1 — `mcp__harness__naming_craft({ path, ... })`** returns:
```json
{
"status": "collected",
"runId": "<uuid>",
"pendingPrompts": [
{ "promptId": "p1", "systemPrompt": "...", "userPrompt": "..." },
...
],
"projection": { "promptCount": N, "budget": 100 }
}
```
If `projection.promptCount > budget`, `status` is `"budget-exceeded"` and `pendingPrompts` is empty — re-invoke with smaller `maxFiles` / `maxIdentifiersPerFile`, or pass `promptBudget` to raise the ceiling.
**Step 2** — for each pending prompt, generate the fenced-JSON response as if you were a senior engineer applying the rubric to the identifier. The required response shape (per prompt) is:
````
```json
null
```
````
if the rubric does not apply or the name is fine, OR:
````
```json
{
"tier": "foundational|polish|aspirational",
"impact": "small|medium|large",
"confidence": "high|medium|low",
"message": "<critique with suggested rename when possible>"
}
```
````
**Step 3 — `mcp__harness__naming_craft_finalize({ path, runId, responses: [{ promptId, raw }, ...] })`** parses the responses, applies the same validation the inline path uses, and returns the standard `NamingCraftOutput`.
If you want the inline behavior (skill calls an LLM directly), pass `mode: 'inline'` to step 1 and set `HARNESS_CRAFT_LLM` to a non-`in-session` provider.
## Success Criteria
See `docs/changes/craft-pipeline/naming-craft/proposal.md` for the full 34 success criteria. Highlights:
- 6 seed rubrics ship in `catalog/rubrics/<id>.ts` (file-per-rubric matches design-craft pattern)
- 3-axis output preserved (tier × impact × confidence, never collapsed) per ADR 0019
- `cite.rubricId` populated on every finding per ADR 0020
- Convention sampler returns `null` when no dominant convention (>50% threshold)
- Cross-cutting `critiqueNamesInFile` API exported for future craft skills
- LlmProvider / MockLlmProvider IMPORTED from design-craft (no duplication)
- MCP tool count bumps (running total maintained by parallel PRs)
## Rationalizations to Reject
These are common rationalizations that sound reasonable but lead to incorrect results. When you catch yourself thinking any of these, stop and follow the documented process instead.
| Rationalization | Why It Is Wrong |
| --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "This name is camelCase and matches the project convention, so it's fine." | Convention conformance (NAME-R004) is one rubric of six. A perfectly-cased `processData` still fails predictive-power (R001) and concreteness (R002) — cased correctly, says nothing. |
| "`timeout` reads clearly in context, so NAME-R006 (encoded measure) doesn't apply." | Encoded-measure asks whether the unit is silent — `timeout` could be ms or seconds. Reads-fine-in-context is exactly how silent-unit bugs ship. Suggest `timeoutMs`. |
| "The convention sampler returned null, so I'll infer the dominant style and flag it." | Below the >50% threshold the sampler returns null and NAME-R004 silently skips by design. Guessing a convention on a mid-migration project manufactures wrong findings. |
| "A one-letter loop variable `x` is lazy naming, so I'll flag it foundational." | NAME-R005 is scope-match: length proportional to scope. `x` in a ≤10-line body is appropriate; the same name at file scope is not. Scope size decides the verdict, not brevity alone. |
| "This is a judgment call I'm unsure about, so I'll emit it high confidence to be safe." | Confidence is an honesty axis, not a safety lever. Unsure means low/medium so `derivePriority` de-emphasizes it. Inflating confidence to hedge pollutes the report with false urgency. |
## Examples
### Example: Vague function name
**Input:** `src/orders/processor.ts`:
```ts
export function processData(orders: Order[]) { ... }
```
**Output (mock LLM):**
```
NAME-R002 [polish/medium/low] function processData:14
"processData" is a vague verb-pair where the operation and subject
are both unstated. Consider `applyDiscountsToOrders` or
`convertOrdersToInvoices` depending on the actual transform.
NAME-R001 [polish/medium/medium] function processData:14
The name predicts neither the input shape (orders) nor the operation.
```
(Real LLM responses vary; mock provider returns deterministic low-confidence findings for test determinism.)
### Example: Silent unit
**Input:**
```ts
const timeout = 5000;
```
**Output:**
```
NAME-R006 [foundational/medium/high] variable timeout:1
"timeout" implies a time measure but the unit is silent. Use
`timeoutMs` so the call site can't be misread as seconds.
```
### Example: Mixed-convention project — convention sampler returns null
**Input:** A project with 60% camelCase, 30% snake_case, 10% PascalCase variables. No >50% camelCase majority (60% IS >50%, so convention=camelCase). But with 45/40/15 split: no convention.
**Output:** convention-conformance rubric (NAME-R004) silently skips for the variables kind. Other rubrics still run.
## Gates
- **No autofix.** This is ceiling-judgment. v2's `align-naming` may add safe-rename codemods.
- **No NAMING.md authoring.** v1 derives convention from sampling.
- **No language support beyond TS/JS.** v1.x.
- **No modules / branches / commit subjects.** v1.x (and commit subjects go to copy-craft).
- **No graph persistence.** Phase 1 MVP posture (matches design-craft).
- **No deep/vision mode.** Naming is text-only.
## Escalation
- **When LLM cost is too high on a large project:** drop `maxIdentifiersPerFile` to 10 or `maxFiles` to 50. Cost = files × identifiers × rubrics × per-call cost.
- **When a rubric produces high false-positive rate:** v1 has no per-rubric disable; v1.x adds `craft.naming.disabledRubrics: ['NAME-R005']`. Until then: filter findings by `cite.rubricId` in your consumer.
- **When the convention sampler misidentifies a mid-migration project:** below 50% threshold returns null and convention rubric skips. Better silent skip than wrong findings. Wait until migration completes; until then disable NAME-R004 in v1.x or filter findings.
- **When you want naming critique for a single file (e.g. in CI on changed files):** use `--files <glob>` or call `critiqueNamesInFile()` via the cross-cutting API.
- **When you want module / branch / commit-subject naming today:** manual review. v1.x adds these surfaces.
## Status
**v1 — in implementation.** See:
- Spec: `docs/changes/craft-pipeline/naming-craft/proposal.md`
- Roadmap entry: part of the `craft-pipeline` initiative (the first member)
- Sibling: `harness-design-craft` (design-pipeline — the LLM-judgment template this follows)
- Future cross-cutters: docs-craft, test-craft, code-craft will call into naming-craft's `critiqueNamesInFile()` for their domain-specific naming critique.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!