@grimoire-cc/cli 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/static/log-viewer.html +1 -0
- package/dist/static/static/log-viewer.html +1 -0
- package/package.json +1 -1
- package/packs/ts-pack/grimoire.json +33 -0
- package/packs/ts-pack/skills/grimoire:modern-typescript/SKILL.md +336 -0
- package/packs/ts-pack/skills/grimoire:modern-typescript/reference/modern-features.md +373 -0
- package/packs/ts-pack/skills/grimoire:modern-typescript/reference/patterns-and-idioms.md +477 -0
- package/packs/ts-pack/skills/grimoire:modern-typescript/reference/type-system.md +389 -0
|
@@ -117,6 +117,7 @@
|
|
|
117
117
|
align-items: center;
|
|
118
118
|
gap: 8px;
|
|
119
119
|
font-size: 13px;
|
|
120
|
+
min-width: 0;
|
|
120
121
|
}
|
|
121
122
|
.skill-card .skill-header .name { font-weight: 600; white-space: nowrap; }
|
|
122
123
|
.skill-card .skill-header .desc { font-size: 12px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
@@ -117,6 +117,7 @@
|
|
|
117
117
|
align-items: center;
|
|
118
118
|
gap: 8px;
|
|
119
119
|
font-size: 13px;
|
|
120
|
+
min-width: 0;
|
|
120
121
|
}
|
|
121
122
|
.skill-card .skill-header .name { font-weight: 600; white-space: nowrap; }
|
|
122
123
|
.skill-card .skill-header .desc { font-size: 12px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
package/package.json
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ts-pack",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"agents": [],
|
|
5
|
+
"skills": [
|
|
6
|
+
{
|
|
7
|
+
"name": "grimoire:modern-typescript",
|
|
8
|
+
"path": "skills/grimoire:modern-typescript",
|
|
9
|
+
"description": "Modern TypeScript best practices, patterns, and type system mastery for TS 5.7+. Use when writing TypeScript, reviewing TS code, designing types, configuring tsconfig, or asking about TypeScript patterns, generics, type safety, strict mode, idiomatic TypeScript, discriminated unions, branded types, or modern TS features.",
|
|
10
|
+
"triggers": {
|
|
11
|
+
"keywords": ["typescript", "tsconfig", "satisfies", "narrowing", "readonly"],
|
|
12
|
+
"file_extensions": [".ts", ".tsx", ".mts", ".cts"],
|
|
13
|
+
"patterns": [
|
|
14
|
+
"type.*safe",
|
|
15
|
+
"strict.*mode",
|
|
16
|
+
"discriminated.*union",
|
|
17
|
+
"branded.*type",
|
|
18
|
+
"modern.*typescript",
|
|
19
|
+
"typescript.*generic",
|
|
20
|
+
"typescript.*pattern",
|
|
21
|
+
"typescript.*best",
|
|
22
|
+
"type.*error",
|
|
23
|
+
"type.*guard",
|
|
24
|
+
"configure.*tsconfig",
|
|
25
|
+
"write.*typescript",
|
|
26
|
+
"review.*typescript",
|
|
27
|
+
"refactor.*type"
|
|
28
|
+
],
|
|
29
|
+
"file_paths": ["**/tsconfig*", "**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: grimoire:modern-typescript
|
|
3
|
+
description: "Modern TypeScript best practices, patterns, and type system mastery for TS 5.7+. Use when writing TypeScript, reviewing TS code, designing types, configuring tsconfig, or asking about TypeScript patterns, generics, type safety, strict mode, idiomatic TypeScript, discriminated unions, branded types, or modern TS features."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Modern TypeScript
|
|
7
|
+
|
|
8
|
+
Guidance for writing modern, idiomatic TypeScript (5.7+) following community best practices and the latest language features. Runtime-agnostic.
|
|
9
|
+
|
|
10
|
+
## Core Principles
|
|
11
|
+
|
|
12
|
+
### 1. Maximum Type Safety
|
|
13
|
+
|
|
14
|
+
- Enable `strict: true` — always, no exceptions
|
|
15
|
+
- Prefer `unknown` over `any` — force explicit narrowing
|
|
16
|
+
- Use `satisfies` over type assertions — preserves inference
|
|
17
|
+
- Annotate return types for public APIs — prevents accidental changes
|
|
18
|
+
- Let TypeScript infer where it can — avoid redundant annotations on locals
|
|
19
|
+
|
|
20
|
+
### 2. Leverage the Type System
|
|
21
|
+
|
|
22
|
+
- Model your domain with discriminated unions, not class hierarchies
|
|
23
|
+
- Use branded types for nominal typing (IDs, currencies, units)
|
|
24
|
+
- Prefer `readonly` by default — mutate only when necessary
|
|
25
|
+
- Use `as const` for literal types and exhaustive checks
|
|
26
|
+
- Template literal types for string-based APIs
|
|
27
|
+
|
|
28
|
+
### 3. Modern Language Features
|
|
29
|
+
|
|
30
|
+
- `using` / `await using` for resource management (TS 5.2+)
|
|
31
|
+
- `const` type parameters for literal inference (TS 5.0+)
|
|
32
|
+
- `satisfies` operator for constraint checking with inference (TS 5.0+)
|
|
33
|
+
- `NoInfer<T>` to control inference sites (TS 5.4+)
|
|
34
|
+
- Inferred type predicates for cleaner narrowing (TS 5.5+)
|
|
35
|
+
|
|
36
|
+
### 4. Code Style
|
|
37
|
+
|
|
38
|
+
- Functions over classes when no state is needed
|
|
39
|
+
- Composition over inheritance
|
|
40
|
+
- Small, focused modules with explicit exports
|
|
41
|
+
- `import type` for type-only imports
|
|
42
|
+
- Named exports over default exports
|
|
43
|
+
|
|
44
|
+
## Strict Configuration
|
|
45
|
+
|
|
46
|
+
Minimum recommended `tsconfig.json`:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"compilerOptions": {
|
|
51
|
+
"strict": true,
|
|
52
|
+
"target": "es2024",
|
|
53
|
+
"module": "nodenext",
|
|
54
|
+
"moduleResolution": "nodenext",
|
|
55
|
+
"isolatedModules": true,
|
|
56
|
+
"skipLibCheck": true,
|
|
57
|
+
"noUncheckedIndexedAccess": true,
|
|
58
|
+
"exactOptionalPropertyTypes": true,
|
|
59
|
+
"noUncheckedSideEffectImports": true,
|
|
60
|
+
"verbatimModuleSyntax": true
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Key flags explained:
|
|
66
|
+
|
|
67
|
+
- `noUncheckedIndexedAccess` — index signatures return `T | undefined`
|
|
68
|
+
- `exactOptionalPropertyTypes` — distinguishes `undefined` from missing
|
|
69
|
+
- `verbatimModuleSyntax` — enforces explicit `import type` syntax
|
|
70
|
+
- `noUncheckedSideEffectImports` — verifies side-effect imports resolve (TS 5.6+)
|
|
71
|
+
|
|
72
|
+
## Type System Essentials
|
|
73
|
+
|
|
74
|
+
### Prefer Narrow Types
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// Bad — too wide
|
|
78
|
+
function processStatus(status: string): void { /* ... */ }
|
|
79
|
+
|
|
80
|
+
// Good — constrained
|
|
81
|
+
type Status = "pending" | "active" | "archived";
|
|
82
|
+
function processStatus(status: Status): void { /* ... */ }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Discriminated Unions Over Conditionals
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Bad — boolean flags with optional fields
|
|
89
|
+
interface ApiResponse {
|
|
90
|
+
success: boolean;
|
|
91
|
+
data?: User;
|
|
92
|
+
error?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Good — each state is distinct and self-describing
|
|
96
|
+
type ApiResponse =
|
|
97
|
+
| { status: "success"; data: User }
|
|
98
|
+
| { status: "error"; error: string }
|
|
99
|
+
| { status: "loading" };
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Use `satisfies` for Validated Inference
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// Bad — loses literal types
|
|
106
|
+
const config: Record<string, string> = {
|
|
107
|
+
host: "localhost",
|
|
108
|
+
port: "3000",
|
|
109
|
+
};
|
|
110
|
+
// config.host is string
|
|
111
|
+
|
|
112
|
+
// Good — validates shape, keeps literals
|
|
113
|
+
const config = {
|
|
114
|
+
host: "localhost",
|
|
115
|
+
port: "3000",
|
|
116
|
+
} satisfies Record<string, string>;
|
|
117
|
+
// config.host is "localhost"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Branded Types for Nominal Safety
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
type UserId = string & { readonly __brand: unique symbol };
|
|
124
|
+
type OrderId = string & { readonly __brand: unique symbol };
|
|
125
|
+
|
|
126
|
+
function createUserId(id: string): UserId {
|
|
127
|
+
return id as UserId;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getUser(id: UserId): User { /* ... */ }
|
|
131
|
+
|
|
132
|
+
const userId = createUserId("u-123");
|
|
133
|
+
const orderId = createOrderId("o-456");
|
|
134
|
+
getUser(orderId); // Error! OrderId is not UserId
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `readonly` by Default
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// Prefer readonly for function parameters
|
|
141
|
+
function processItems(items: readonly Item[]): Result {
|
|
142
|
+
// items.push(...) would be an error
|
|
143
|
+
return items.map(transform);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Use Readonly<T> for objects
|
|
147
|
+
function updateConfig(
|
|
148
|
+
current: Readonly<Config>,
|
|
149
|
+
patch: Partial<Config>,
|
|
150
|
+
): Config {
|
|
151
|
+
return { ...current, ...patch };
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Const Type Parameters (TS 5.0+)
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// Without const — infers string[]
|
|
159
|
+
function createRoute<T extends readonly string[]>(parts: T) { /* ... */ }
|
|
160
|
+
createRoute(["users", "profile"]); // T is string[]
|
|
161
|
+
|
|
162
|
+
// With const — infers literal tuple
|
|
163
|
+
function createRoute<const T extends readonly string[]>(parts: T) { /* ... */ }
|
|
164
|
+
createRoute(["users", "profile"]); // T is readonly ["users", "profile"]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Resource Management with `using` (TS 5.2+)
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
function processFile(path: string): string {
|
|
171
|
+
using handle = openFile(path);
|
|
172
|
+
return handle.readAll();
|
|
173
|
+
// handle[Symbol.dispose]() called automatically
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function withConnection(): Promise<QueryResult> {
|
|
177
|
+
await using conn = await pool.getConnection();
|
|
178
|
+
return conn.query("SELECT ...");
|
|
179
|
+
// conn[Symbol.asyncDispose]() called automatically
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### NoInfer for Controlled Inference (TS 5.4+)
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// Without NoInfer — initial widens the union
|
|
187
|
+
function createFSM<S extends string>(
|
|
188
|
+
initial: S,
|
|
189
|
+
states: S[],
|
|
190
|
+
): void { /* ... */ }
|
|
191
|
+
createFSM("typo", ["idle", "running"]); // No error — S becomes "typo" | "idle" | "running"
|
|
192
|
+
|
|
193
|
+
// With NoInfer — only states drives inference
|
|
194
|
+
function createFSM<S extends string>(
|
|
195
|
+
initial: NoInfer<S>,
|
|
196
|
+
states: S[],
|
|
197
|
+
): void { /* ... */ }
|
|
198
|
+
createFSM("typo", ["idle", "running"]); // Error: "typo" not assignable
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Error Handling
|
|
202
|
+
|
|
203
|
+
### Use Result Types Over Exceptions
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
type Result<T, E = Error> =
|
|
207
|
+
| { ok: true; value: T }
|
|
208
|
+
| { ok: false; error: E };
|
|
209
|
+
|
|
210
|
+
function parseConfig(raw: string): Result<Config, ParseError> {
|
|
211
|
+
try {
|
|
212
|
+
const data = JSON.parse(raw);
|
|
213
|
+
return { ok: true, value: validate(data) };
|
|
214
|
+
} catch (e) {
|
|
215
|
+
return { ok: false, error: new ParseError(e) };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Caller must handle both cases
|
|
220
|
+
const result = parseConfig(input);
|
|
221
|
+
if (result.ok) {
|
|
222
|
+
useConfig(result.value);
|
|
223
|
+
} else {
|
|
224
|
+
log(result.error);
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Type-Safe Error Narrowing
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
// Always narrow unknown in catch
|
|
232
|
+
try {
|
|
233
|
+
riskyOperation();
|
|
234
|
+
} catch (error: unknown) {
|
|
235
|
+
if (error instanceof NetworkError) {
|
|
236
|
+
retry(error.url);
|
|
237
|
+
} else if (error instanceof ValidationError) {
|
|
238
|
+
showErrors(error.fields);
|
|
239
|
+
} else {
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Module Patterns
|
|
246
|
+
|
|
247
|
+
### Explicit Named Exports
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
// Prefer named exports
|
|
251
|
+
export function createUser(data: UserInput): User { /* ... */ }
|
|
252
|
+
export type { User, UserInput };
|
|
253
|
+
|
|
254
|
+
// Avoid default exports — harder to rename, worse tree-shaking
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Barrel Files — Use Sparingly
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
// index.ts — only for public API surface
|
|
261
|
+
export { createUser, updateUser } from "./user.js";
|
|
262
|
+
export type { User, UserInput } from "./types.js";
|
|
263
|
+
|
|
264
|
+
// Don't re-export everything — it defeats tree-shaking
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Type-Only Import Enforcement
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// With verbatimModuleSyntax, this is enforced:
|
|
271
|
+
import type { User } from "./types.js"; // Type-only — erased
|
|
272
|
+
import { createUser } from "./user.js"; // Value — kept
|
|
273
|
+
import { type Config, loadConfig } from "./config.js"; // Mixed
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Common Anti-Patterns
|
|
277
|
+
|
|
278
|
+
| Anti-Pattern | Better Alternative |
|
|
279
|
+
|---|---|
|
|
280
|
+
| `any` | `unknown` with narrowing |
|
|
281
|
+
| Type assertions (`as T`) | `satisfies`, type guards |
|
|
282
|
+
| `enum` | Union types or `as const` objects |
|
|
283
|
+
| Class hierarchies for data | Discriminated unions |
|
|
284
|
+
| `!` non-null assertion | Proper null checks or `?.` |
|
|
285
|
+
| `Function` type | Specific signature `(arg: T) => R` |
|
|
286
|
+
| `Object` / `{}` type | `Record<string, unknown>` |
|
|
287
|
+
| Nested ternaries in types | Named helper types |
|
|
288
|
+
| `@ts-ignore` | `@ts-expect-error` (fails when fixed) |
|
|
289
|
+
| `interface extends` chains | Intersection types or composition |
|
|
290
|
+
|
|
291
|
+
## When to Annotate vs. Infer
|
|
292
|
+
|
|
293
|
+
**Annotate:**
|
|
294
|
+
|
|
295
|
+
- Function return types (public API)
|
|
296
|
+
- Complex object parameters
|
|
297
|
+
- Generic constraints
|
|
298
|
+
- Exported constants with specific types
|
|
299
|
+
|
|
300
|
+
**Let TypeScript infer:**
|
|
301
|
+
|
|
302
|
+
- Local variables
|
|
303
|
+
- Array method chains (`.map`, `.filter`)
|
|
304
|
+
- Simple function returns (private/internal)
|
|
305
|
+
- `const` declarations with literal values
|
|
306
|
+
|
|
307
|
+
## Exhaustive Checking
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
function assertNever(value: never): never {
|
|
311
|
+
throw new Error(`Unexpected value: ${value}`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function handleStatus(status: Status): string {
|
|
315
|
+
switch (status) {
|
|
316
|
+
case "pending": return "Waiting...";
|
|
317
|
+
case "active": return "Running";
|
|
318
|
+
case "archived": return "Done";
|
|
319
|
+
default: return assertNever(status); // Compile error if a case is missed
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Deep Reference
|
|
325
|
+
|
|
326
|
+
For detailed guides on specific topics:
|
|
327
|
+
|
|
328
|
+
- **[Type System Patterns](reference/type-system.md)** — Generics, conditional types, mapped types, template literals, type guards
|
|
329
|
+
- **[Code Patterns & Idioms](reference/patterns-and-idioms.md)** — Functional patterns, builder pattern, immutable state, async patterns
|
|
330
|
+
- **[Modern Features Guide](reference/modern-features.md)** — Comprehensive TS 5.0-5.9 feature reference
|
|
331
|
+
|
|
332
|
+
## Limitations
|
|
333
|
+
|
|
334
|
+
- This skill provides guidance, not enforcement — use ESLint + typescript-eslint for automated checks
|
|
335
|
+
- Patterns are runtime-agnostic — some may need adaptation for specific frameworks
|
|
336
|
+
- TypeScript evolves rapidly — check the [official release notes](https://devblogs.microsoft.com/typescript/) for the latest
|