# naming-craft

> Naming Craft

- Skill: `intense-visions/naming-craft` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add intense-visions/naming-craft`
- Raw SKILL.md: https://api.skillmd.com/api/skills/intense-visions/naming-craft/raw
- Safety review: pending (external: skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Intense-Visions (https://skillmd.com/u/intense-visions)
- Updated: 2026-08-19
- Page: https://skillmd.com/skills/intense-visions/naming-craft

---

# 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.

