Initial commit
This commit is contained in:
14
.claude-plugin/plugin.json
Normal file
14
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "langs",
|
||||
"description": "Language-specific coding skills including React, TypeScript, and more. Provides best practices, patterns, and expert guidance for writing clean, type-safe, and maintainable code in modern programming languages and frameworks.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "codethread"
|
||||
},
|
||||
"skills": [
|
||||
"./skills"
|
||||
],
|
||||
"hooks": [
|
||||
"./hooks"
|
||||
]
|
||||
}
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# langs
|
||||
|
||||
Language-specific coding skills including React, TypeScript, and more. Provides best practices, patterns, and expert guidance for writing clean, type-safe, and maintainable code in modern programming languages and frameworks.
|
||||
17
hooks/hooks.json
Normal file
17
hooks/hooks.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"description": "Suggests test files when TypeScript source files are read",
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Read",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/test-file-suggest.ts",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
88
hooks/test-file-suggest.ts
Executable file
88
hooks/test-file-suggest.ts
Executable file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env bun
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { basename, dirname, join } from 'node:path';
|
||||
import type { PostToolUseHookInput, SyncHookJSONOutput } from '@anthropic-ai/claude-agent-sdk';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// Read input from stdin
|
||||
const input = readFileSync(0, 'utf-8');
|
||||
const data: PostToolUseHookInput = JSON.parse(input);
|
||||
|
||||
// Check if the tool was Read
|
||||
if (data.tool_name !== 'Read') {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Get the file path from tool input
|
||||
const toolInput = data.tool_input as Record<string, unknown>;
|
||||
const filePath = toolInput.file_path as string;
|
||||
if (!filePath) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Check if it's a TypeScript file (but not already a test file)
|
||||
const isTypeScriptFile =
|
||||
(filePath.endsWith('.ts') || filePath.endsWith('.tsx')) &&
|
||||
!filePath.endsWith('.test.ts') &&
|
||||
!filePath.endsWith('.test.tsx') &&
|
||||
!filePath.endsWith('.spec.ts') &&
|
||||
!filePath.endsWith('.spec.tsx');
|
||||
|
||||
if (!isTypeScriptFile) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Look for corresponding test files
|
||||
const dir = dirname(filePath);
|
||||
const file = basename(filePath);
|
||||
const nameWithoutExt = file.replace(/\.tsx?$/, '');
|
||||
|
||||
const testPatterns = [
|
||||
`${nameWithoutExt}.test.ts`,
|
||||
`${nameWithoutExt}.test.tsx`,
|
||||
`${nameWithoutExt}.spec.ts`,
|
||||
`${nameWithoutExt}.spec.tsx`,
|
||||
];
|
||||
|
||||
const foundTestFiles: string[] = [];
|
||||
for (const pattern of testPatterns) {
|
||||
const testPath = join(dir, pattern);
|
||||
if (existsSync(testPath)) {
|
||||
foundTestFiles.push(testPath);
|
||||
}
|
||||
}
|
||||
|
||||
// If test files were found, suggest them
|
||||
if (foundTestFiles.length > 0) {
|
||||
let context = '<plugin-langs-suggestion>\n';
|
||||
context += `Source file: ${filePath}\n\n`;
|
||||
context += 'Related test file(s):\n';
|
||||
for (const testFile of foundTestFiles) {
|
||||
context += ` → ${testFile}\n`;
|
||||
}
|
||||
context += '</plugin-langs-suggestion>';
|
||||
|
||||
// Return JSON with hookSpecificOutput for PostToolUse
|
||||
const output: SyncHookJSONOutput = {
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'PostToolUse',
|
||||
additionalContext: context,
|
||||
},
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(output));
|
||||
}
|
||||
|
||||
// Exit 0 = success
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
console.error('Error in test-file-suggest hook:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Uncaught error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
65
plugin.lock.json
Normal file
65
plugin.lock.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||
"pluginId": "gh:codethread/claude-code-plugins:plugins/langs",
|
||||
"normalized": {
|
||||
"repo": null,
|
||||
"ref": "refs/tags/v20251128.0",
|
||||
"commit": "545dac5621e10bffa5392a4562ec355813b823ce",
|
||||
"treeHash": "9676a8312ac68ead6b425b93287653a54fde7faba0abc5aa0b83f1edaa03156a",
|
||||
"generatedAt": "2025-11-28T10:15:44.202694Z",
|
||||
"toolVersion": "publish_plugins.py@0.2.0"
|
||||
},
|
||||
"origin": {
|
||||
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||
"branch": "master",
|
||||
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||
},
|
||||
"manifest": {
|
||||
"name": "langs",
|
||||
"description": "Language-specific coding skills including React, TypeScript, and more. Provides best practices, patterns, and expert guidance for writing clean, type-safe, and maintainable code in modern programming languages and frameworks.",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"content": {
|
||||
"files": [
|
||||
{
|
||||
"path": "README.md",
|
||||
"sha256": "a96ec0b0a3026851a466ea508849fd05a41d6d5091c98c788c56e8444fe6acd8"
|
||||
},
|
||||
{
|
||||
"path": "hooks/test-file-suggest.ts",
|
||||
"sha256": "55e810f33672b732a3098bec2678367a8edc4f44dc0a065d86e9b321d67fc6f3"
|
||||
},
|
||||
{
|
||||
"path": "hooks/hooks.json",
|
||||
"sha256": "d7cb066cc9cdca0369eddea591cf0cff05f5594fa3f02279e879b62169670ad2"
|
||||
},
|
||||
{
|
||||
"path": ".claude-plugin/plugin.json",
|
||||
"sha256": "b21d75695425dc9177a2c06f51ce878a929624b80d43da68bdecf2aebdd4ae7c"
|
||||
},
|
||||
{
|
||||
"path": "skills/lang-react/SKILL.md",
|
||||
"sha256": "c7e486ae06c868abc0be8c55a86232007a5e23f54e2b2e82749c1d4cd167fa4b"
|
||||
},
|
||||
{
|
||||
"path": "skills/lang-react/CLAUDE.md",
|
||||
"sha256": "0f39b684d381ec0ffbba0833b6d178711f9007222de0238920d568295fabb014"
|
||||
},
|
||||
{
|
||||
"path": "skills/lang-typescript/SKILL.md",
|
||||
"sha256": "686085982a5b278152b12eaa8638ea3640f6ebb7573e60f780e854a66c4de90b"
|
||||
},
|
||||
{
|
||||
"path": "skills/lang-typescript/references/best-practices-2025.md",
|
||||
"sha256": "3abc35772c1dbe08b503c55e439bc2297d4a141c559321a73c21db7ae4385d69"
|
||||
}
|
||||
],
|
||||
"dirSha256": "9676a8312ac68ead6b425b93287653a54fde7faba0abc5aa0b83f1edaa03156a"
|
||||
},
|
||||
"security": {
|
||||
"scannedAt": null,
|
||||
"scannerVersion": null,
|
||||
"flags": []
|
||||
}
|
||||
}
|
||||
1
skills/lang-react/CLAUDE.md
Normal file
1
skills/lang-react/CLAUDE.md
Normal file
@@ -0,0 +1 @@
|
||||
this is the lang-claude CLAUDE.md file - did you read it?
|
||||
135
skills/lang-react/SKILL.md
Normal file
135
skills/lang-react/SKILL.md
Normal file
@@ -0,0 +1,135 @@
|
||||
---
|
||||
name: lang-react
|
||||
description: Build React SPAs where components are declarative UI consuming external state (Zustand/XState/TanStack Query). Logic lives in stores, not components.
|
||||
---
|
||||
|
||||
# React Expert
|
||||
|
||||
## Core Philosophy
|
||||
|
||||
Components consume external state, contain no logic:
|
||||
|
||||
- External hooks at top (Zustand, XState, TanStack Query)
|
||||
- No useState/useReducer/useEffect for complex logic
|
||||
- Inline actions unless repeated 2+ times
|
||||
- Test stores/machines (unit tests), not components (E2E only)
|
||||
|
||||
## State Management Stack
|
||||
|
||||
| State Type | Solution |
|
||||
| --------------------- | ---------------------------- |
|
||||
| **Remote (REST)** | TanStack Query |
|
||||
| **Remote (GraphQL)** | Apollo Client |
|
||||
| **Application state** | Zustand |
|
||||
| **Complex machines** | XState |
|
||||
| **Local UI state** | useState (rare, last resort) |
|
||||
|
||||
## Component Pattern
|
||||
|
||||
```typescript
|
||||
// ✅ External hooks → Early returns → JSX
|
||||
function UserProfile({ userId }: { userId: string }) {
|
||||
const { user, isLoading } = useUser(userId);
|
||||
const { updateProfile } = useUserActions();
|
||||
|
||||
if (isLoading) return <Spinner />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{user.name}</h1>
|
||||
<button onClick={() => updateProfile({ email: 'new@example.com' })}>
|
||||
Update
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Testing: Stores, Not Components
|
||||
|
||||
| What | Tool | Why |
|
||||
| --------------- | ---------- | ------------------------- |
|
||||
| Zustand stores | Vitest | Test without React |
|
||||
| XState machines | Vitest | Deterministic transitions |
|
||||
| Critical flows | Playwright | Real browser |
|
||||
| Components | Never | Logic should be in stores |
|
||||
|
||||
```typescript
|
||||
// Test store directly
|
||||
const { login } = useAuthStore.getState();
|
||||
await login({ email: "test@example.com", password: "pass" });
|
||||
expect(useAuthStore.getState().user).toBeDefined();
|
||||
```
|
||||
|
||||
## Unique Patterns
|
||||
|
||||
### Prop Ordering: Simple → Complex
|
||||
|
||||
```tsx
|
||||
<Table
|
||||
data={items}
|
||||
loading={isLoading}
|
||||
sortable
|
||||
onSort={handleSort}
|
||||
renderRow={(item) => <Row>{item.name}</Row>}
|
||||
/>
|
||||
```
|
||||
|
||||
### Inline Props for Type Inference
|
||||
|
||||
```tsx
|
||||
// ✅ Inline - TypeScript infers types
|
||||
<SearchableList
|
||||
items={budgets}
|
||||
renderItem={(budget) => <Card name={budget.name} />}
|
||||
/>;
|
||||
|
||||
// ❌ Extract only if repeated 2+ times
|
||||
const renderItem = (budget: Budget) => <Card name={budget.name} />;
|
||||
```
|
||||
|
||||
### Pattern Matching for Variants
|
||||
|
||||
```tsx
|
||||
import { match } from "ts-pattern";
|
||||
|
||||
{
|
||||
match(state)
|
||||
.with({ _tag: "loading" }, () => <Spinner />)
|
||||
.with({ _tag: "success" }, (s) => <Data value={s.data} />)
|
||||
.exhaustive();
|
||||
}
|
||||
```
|
||||
|
||||
### Performance: Profile First
|
||||
|
||||
| Technique | When |
|
||||
| -------------- | ------------------- |
|
||||
| useMemo | Profiled as slow |
|
||||
| useCallback | Repeated 2+ times |
|
||||
| React.memo | Props rarely change |
|
||||
| Code splitting | Route-level |
|
||||
|
||||
### Styled-Components: Consolidate with CSS Selectors
|
||||
|
||||
```typescript
|
||||
// ✅ Single component with CSS selectors
|
||||
const Table = styled.table`
|
||||
thead {
|
||||
background: ${(p) => p.theme.colors.header};
|
||||
}
|
||||
tbody tr:hover {
|
||||
background: ${(p) => p.theme.colors.hover};
|
||||
}
|
||||
td {
|
||||
padding: ${(p) => p.theme.space.md};
|
||||
}
|
||||
`;
|
||||
|
||||
// ❌ Separate components for each element
|
||||
const TableHeader = styled.thead`...`;
|
||||
const TableRow = styled.tr`...`;
|
||||
const TableCell = styled.td`...`;
|
||||
```
|
||||
|
||||
## Version 1.0.1
|
||||
228
skills/lang-typescript/SKILL.md
Normal file
228
skills/lang-typescript/SKILL.md
Normal file
@@ -0,0 +1,228 @@
|
||||
---
|
||||
name: lang-typescript
|
||||
description: Write clean, type-safe TypeScript code using modern patterns, strict configuration, and best practices. Use when writing TypeScript code, configuring projects, or solving type-related challenges.
|
||||
---
|
||||
|
||||
# TypeScript Expert
|
||||
|
||||
Write clean, type-safe TypeScript code that leverages the full power of the type system to catch bugs at compile time.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
|
||||
- Writing or refactoring TypeScript code
|
||||
- Configuring TypeScript projects (tsconfig.json)
|
||||
- Solving complex type-related challenges
|
||||
- Choosing between type system patterns
|
||||
- Validating external data with types
|
||||
|
||||
## Core Workflow
|
||||
|
||||
### 1. Type Decision Tree
|
||||
|
||||
**Choose the right construct:**
|
||||
|
||||
| Use Case | Use | Not |
|
||||
| ----------------- | -------------------- | -------------------- |
|
||||
| Object shapes | `interface` | `type` |
|
||||
| Unions/primitives | `type` | `interface` |
|
||||
| Dynamic data | `unknown` | `any` |
|
||||
| State machines | Discriminated unions | Complex conditionals |
|
||||
| Domain types | Branded types | Plain primitives |
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
// ✅ Correct choices
|
||||
interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
} // Object shape
|
||||
type Status = "idle" | "loading" | "success"; // Union
|
||||
type USD = number & { readonly __brand: "USD" }; // Branded type
|
||||
|
||||
// ❌ Wrong choices
|
||||
type User = { id: number }; // Use interface
|
||||
interface Status {
|
||||
/* ... */
|
||||
} // Can't do unions
|
||||
```
|
||||
|
||||
### 2. State Modeling Pattern
|
||||
|
||||
For finite states, always use discriminated unions to eliminate impossible states:
|
||||
|
||||
```typescript
|
||||
type ApiState =
|
||||
| { status: "idle" }
|
||||
| { status: "loading" }
|
||||
| { status: "success"; data: string }
|
||||
| { status: "error"; message: string };
|
||||
|
||||
// Exhaustiveness checking
|
||||
function handle(state: ApiState) {
|
||||
switch (state.status) {
|
||||
case "success":
|
||||
return state.data;
|
||||
case "error":
|
||||
return state.message;
|
||||
case "idle":
|
||||
return "Not started";
|
||||
case "loading":
|
||||
return "Loading...";
|
||||
default:
|
||||
const _exhaustive: never = state; // Compiler error if cases missing
|
||||
throw new Error("Unhandled state");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Validation Workflow
|
||||
|
||||
**Always validate external data with runtime checks:**
|
||||
|
||||
1. **Simple validation:** Type guards
|
||||
2. **Complex validation:** Schema libraries (Zod, Yup)
|
||||
|
||||
```typescript
|
||||
// Type guard pattern
|
||||
function isUser(data: unknown): data is User {
|
||||
return (
|
||||
typeof data === "object" &&
|
||||
data !== null &&
|
||||
"id" in data &&
|
||||
typeof (data as any).id === "number"
|
||||
);
|
||||
}
|
||||
|
||||
// Schema validation (preferred for APIs)
|
||||
import { z } from "zod";
|
||||
|
||||
const UserSchema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
email: z.string().email(),
|
||||
});
|
||||
|
||||
type User = z.infer<typeof UserSchema>;
|
||||
const user = UserSchema.parse(apiData); // Runtime validation + types
|
||||
```
|
||||
|
||||
**Never use type assertions without validation:**
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: No runtime check
|
||||
const user = data as User;
|
||||
|
||||
// ✅ GOOD: Validated first
|
||||
if (isUser(data)) {
|
||||
// data is User here
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Configuration Checklist
|
||||
|
||||
For new projects, enable strict mode plus:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Why these matter:**
|
||||
|
||||
- `noUncheckedIndexedAccess` - Prevents undefined access bugs
|
||||
- `exactOptionalPropertyTypes` - Distinguishes missing from undefined
|
||||
- `moduleResolution: "bundler"` - Optimized for Vite/esbuild
|
||||
|
||||
### 5. Code Organization Rules
|
||||
|
||||
**Barrel files:** Avoid for internal code (75% faster builds)
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: Internal barrel
|
||||
// src/components/index.ts
|
||||
export * from "./Button";
|
||||
|
||||
// ✅ GOOD: Direct imports + path aliases
|
||||
// tsconfig.json: "paths": { "@/components/*": ["src/components/*"] }
|
||||
import { Button } from "@/components/Button";
|
||||
```
|
||||
|
||||
**Only use barrel files for:**
|
||||
|
||||
- Library entry points
|
||||
- Public APIs
|
||||
- Small modules (<10 exports)
|
||||
|
||||
## Quick Reference Patterns
|
||||
|
||||
### Utility Types
|
||||
|
||||
```typescript
|
||||
type UserPreview = Pick<User, "id" | "name">; // Extract subset
|
||||
type PublicUser = Omit<User, "email">; // Remove fields
|
||||
type UpdateDto = Partial<User>; // Make optional
|
||||
type CompleteUser = Required<User>; // Make required
|
||||
type ImmutableUser = Readonly<User>; // Make readonly
|
||||
type UserType = ReturnType<typeof getUser>; // Extract return type
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```typescript
|
||||
// Catch with unknown
|
||||
try {
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
/* ... */
|
||||
}
|
||||
}
|
||||
|
||||
// Result types for expected failures
|
||||
type Result<T, E = Error> =
|
||||
| { success: true; value: T }
|
||||
| { success: false; error: E };
|
||||
```
|
||||
|
||||
### Readonly Patterns
|
||||
|
||||
```typescript
|
||||
const config: Readonly<Config> = {
|
||||
/* ... */
|
||||
};
|
||||
const numbers: readonly number[] = [1, 2, 3];
|
||||
const ROUTES = { home: "/" } as const; // Deep readonly + literal types
|
||||
```
|
||||
|
||||
## When to Consult Detailed References
|
||||
|
||||
For comprehensive information on advanced patterns, configuration options, or specific features, read:
|
||||
|
||||
- `references/best-practices-2025.md` - Full TypeScript best practices guide
|
||||
|
||||
The reference includes:
|
||||
|
||||
- Advanced type patterns (conditional types, mapped types, branded types)
|
||||
- Complete tsconfig.json options
|
||||
- Modern features (decorators, const type parameters)
|
||||
- Common anti-patterns and solutions
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
Before completing TypeScript code:
|
||||
|
||||
- [ ] External data validated (not just typed)
|
||||
- [ ] No `any` types (or explicitly justified)
|
||||
- [ ] State machines use discriminated unions
|
||||
- [ ] Utility types used where applicable
|
||||
- [ ] Readonly applied to prevent mutations
|
||||
- [ ] Exhaustiveness checks with `never`
|
||||
2061
skills/lang-typescript/references/best-practices-2025.md
Normal file
2061
skills/lang-typescript/references/best-practices-2025.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user