| name | coding-typescript |
| description | ALWAYS invoke this skill when writing or fixing implementation code for TypeScript. |
| allowed-tools | Read, Write, Bash, Glob, Grep, Edit |
<accessing_skill_files>
When this skill is invoked, Claude Code provides the base directory in the loading message:
Base directory for this skill: {skill_dir}
Use this path to access skill files:
- References:
{skill_dir}/references/
- Workflows:
{skill_dir}/workflows/
IMPORTANT: Do NOT search the project directory for skill files.
</accessing_skill_files>
<essential_principles>
NO MOCKING. DEPENDENCY INJECTION. BEHAVIOR ONLY. TEST FIRST.
- Use dependency injection, NEVER mocking frameworks
- Test behavior (what the code does), not implementation (how it does it)
- Run all verification tools before declaring completion
- Type safety first:
strict: true, no any without justification
</essential_principles>
<mandatory_code_patterns>
These patterns are enforced by the reviewer. Violations will be REJECTED.
Constants
All literal values (strings, numbers) must be module-level constants:
function validateScore(score: number): boolean {
return score >= 0 && score <= 100;
}
const MIN_SCORE = 0;
const MAX_SCORE = 100;
function validateScore(score: number): boolean {
return score >= MIN_SCORE && score <= MAX_SCORE;
}
Share constants between code and tests — tests import from the module under test:
export const MIN_SCORE = 0;
export const MAX_SCORE = 100;
import { MIN_SCORE, validateScore } from "@/scoring";
it("rejects below minimum", () => {
expect(validateScore(MIN_SCORE - 1)).toBe(false);
});
Dependency Injection
External dependencies must be injected, not imported directly:
import { execa } from "execa";
async function syncFiles(src: string, dest: string): Promise<boolean> {
const result = await execa("rsync", [src, dest]);
return result.exitCode === 0;
}
interface SyncDeps {
execa: typeof execa;
}
async function syncFiles(
src: string,
dest: string,
deps: SyncDeps,
): Promise<boolean> {
const result = await deps.execa("rsync", [src, dest]);
return result.exitCode === 0;
}
</mandatory_code_patterns>
<hierarchy_of_authority>
Where to look for guidance, in order of precedence:
| Priority | Source | What It Provides |
|---|
| 1 | docs/, README.md | Project architecture, design decisions, intended APIs |
| 2 | CLAUDE.md | Project-specific rules for Claude |
| 3 | ADRs/PDRs, specs | Documented decisions and requirements |
| 4 | This skill (SKILL.md) | Generic TypeScript best practices |
| 5 | Existing code (reference) | Evidence of implementation, NOT authority |
CRITICAL: Existing code is NOT authoritative.
- Documentation describes intent — what SHOULD be done
- Existing code shows implementation — what WAS done (may be legacy, wrong, or outdated)
- When docs and code conflict, docs win
- When no docs exist, ASK before copying existing patterns
Never copy patterns from existing code without verifying they match documented intent.
</hierarchy_of_authority>
<codebase_discovery>
BEFORE writing any code, discover what already exists.
Phase 0: Discovery (MANDATORY)
Run these searches before implementation:
Read: README.md, docs/, CLAUDE.md, CONTRIBUTING.md
Read: package.json → dependencies, devDependencies
Grep: function names, class names, patterns similar to your task
Glob: files in similar directories (src/utils/, src/services/, etc.)
Read: existing files in the same directory you'll write to
What to Discover
| Question | How to Find |
|---|
| What libraries are available? | package.json → dependencies |
| How does this project handle X? | Grep for similar patterns |
| What utilities already exist? | Glob for **/utils/**, **/helpers/**, **/fixtures/**, **/harnesses/** |
| What's the naming convention? | Read 3-5 files in the target directory |
| What error classes exist? | Grep for extends Error |
| What logging pattern is used? | Grep for logger, console.log, debug |
| How are configs structured? | Glob for **/*.config.*, **/config/** |
Discovery Anti-Patterns
import _ from "lodash";
const logger = console;
function fetch_user_by_id() {}
class MyError extends Error {}
Discovery Checklist
Before writing code, confirm:
If discovery reveals existing patterns that conflict with this skill's guidance, follow the project's documented patterns.
</codebase_discovery>
<testing_methodology>
For complete testing methodology, invoke /testing-typescript skill.
The /testing-typescript skill provides:
- Detailed test level selection criteria
- Dependency injection patterns (NO MOCKING)
- Behavior-only testing approach
- Test organization for debuggability
- Test co-location in Outcome Engineering framework
Quick Reference - Testing Levels:
| Level | Infrastructure | When to Use |
|---|
| 1 (Unit) | Node.js + Git + temp fixtures | Pure logic, FS ops, git operations |
| 2 (Integration) | Project-specific binaries/tools | Claude Code, Hugo, Caddy, TypeScript compiler |
| 3 (E2E) | External deps (GitHub, network, Chrome) | Full workflows with external services |
NO MOCKING — Use Dependency Injection Instead:
vi.mock("execa", () => ({ execa: vi.fn() }));
interface CommandDeps {
execa: typeof execa;
}
it("GIVEN valid args WHEN running THEN returns success", async () => {
const deps: CommandDeps = {
execa: vi.fn().mockResolvedValue({ exitCode: 0 }),
};
const result = await runCommand(args, deps);
expect(result.success).toBe(true);
});
</testing_levels>
<context_loading>
BEFORE ANY IMPLEMENTATION: Load complete specification context.
If working on a spec-tree work item (enabler/outcome):
- Invoke
spec-tree:contextualizing FIRST with the node path
- If context loading fails: ABORT - do not proceed until all required documents exist
- If context loading succeeds: Proceed with implementation using loaded context
The spec-tree:contextualizing skill provides:
- Complete ancestor hierarchy (product → all ancestor nodes → target)
- All ADRs/PDRs at every level along the path
- Lower-index siblings (they constrain the target via dependency encoding)
- Target node spec with typed assertions
Example invocation:
spec-tree:contextualizing spx/32-cli.enabler/54-commands.outcome
If spec-tree:contextualizing returns an error: The error message will specify which document is missing and how to create it. Create the missing document before proceeding with implementation.
If NOT working on spec-tree work item: Proceed directly to implementation mode with provided spec.
</context_loading>
<two_modes>
You operate in one of two modes depending on your input:
| Input | Mode | Workflow |
|---|
| Spec (ADR/PDR, node spec) | Implementation | workflows/implementation.md |
| Rejection feedback from reviewer | Remediation | workflows/remediation.md |
Determine your mode from the input, then follow the appropriate workflow.
</two_modes>
<core_principles>
-
Spec Is Law: The specification is your contract. Implement exactly what it says.
-
Test-Driven Development: Write tests first or alongside code. Tests prove correctness.
-
Type Safety First: Use strict TypeScript with strict: true. No any without justification.
-
Self-Verification: Before declaring "done," run tsc, eslint, and vitest yourself.
-
Humility: Your code must pass review. Write code that will survive adversarial review.
-
Clean Architecture: Dependency injection, single responsibility, no circular imports, no deep relative imports.
</core_principles>
<reference_index>
| File | Purpose |
|---|
references/outcome-engineering-patterns.md | Subprocess, resource cleanup, config |
references/test-patterns.md | Debuggability-first test organization |
references/verification-checklist.md | Pre-submission verification |
</reference_index>
<workflows_index>
| Workflow | Purpose |
|---|
workflows/implementation.md | TDD phases, code standards |
workflows/remediation.md | Fix issues from review feedback |
</workflows_index>
<what_not_to_do>
Never Self-Approve: Always submit for review.
Never Skip Tests: Write tests first. No exceptions.
Never Ignore Type Errors:
const result = someFunction();
const result: ExpectedType = someFunction();
Never Hardcode Secrets:
const API_KEY = "sk-1234567890abcdef";
const API_KEY = process.env.API_KEY;
if (!API_KEY) throw new Error("API_KEY required");
Never Use Deep Relative Imports:
Before writing any import, ask: "Is this a module-internal file (same module, moves together) or infrastructure (lib/, tests/helpers/, shared/)?"
import { helper } from "../../../../../../tests/helpers/tree-builder";
import { Logger } from "../../../../lib/logging";
import { Config } from "../../../shared/config";
import { Logger } from "@lib/logging";
import { Config } from "@shared/config";
import { helper } from "@testing/helpers/tree-builder";
Depth Rules:
./sibling — ✅ OK (same directory, module-internal)
../parent — ⚠️ Review (is it truly module-internal?)
../../ or deeper — ❌ REJECT (use path alias)
Configure tsconfig.json:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@testing/*": ["tests/*"],
"@lib/*": ["lib/*"]
}
}
}
</what_not_to_do>
<tool_invocation>
npx tsc --noEmit
npx eslint src/ test/
npx eslint src/ test/ --fix
npx vitest run --coverage
</tool_invocation>
<success_criteria>
Your implementation is ready for review when:
Your code will face an adversarial reviewer with zero tolerance. Write code that will survive that scrutiny.
</success_criteria>