@rune-kit/rune 2.3.1 → 2.3.3
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/README.md +1 -1
- package/docs/index.html +1 -1
- package/package.json +1 -1
- package/skills/db/SKILL.md +5 -1
- package/skills/db/references/scaling-reference.md +271 -0
- package/skills/perf/SKILL.md +6 -1
- package/skills/perf/references/cost-reference.md +205 -0
- package/skills/perf/references/scalability-reference.md +378 -0
- package/skills/skill-forge/SKILL.md +5 -1
- package/skills/skill-forge/references/claude-skill-reference.md +182 -0
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Mesh: A ↔ B ↔ C (B fails = A reaches C via D→E)
|
|
|
32
32
|
D ↔ E ↔ F
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
## What's New (v2.3.
|
|
35
|
+
## What's New (v2.3.3)
|
|
36
36
|
|
|
37
37
|
- **UI/UX Pro Max Integration** — 161 palettes, 84 styles, 73 font pairings, 161 reasoning rules from [UI/UX Pro Max](https://github.com/nextlevelbuilder/ui-ux-pro-max-skill) (MIT, 42.8k★) meshed into `design` skill + `@rune/ui` pack
|
|
38
38
|
- **11 Core Skill Enrichments** — deviation rules, repair operators, evidence quality gate, decision classification, debug knowledge base, CSO discipline, YAGNI pushback, formal pause/resume
|
package/docs/index.html
CHANGED
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
<canvas id="mesh-canvas"></canvas>
|
|
63
63
|
</div>
|
|
64
64
|
<div class="hero-content">
|
|
65
|
-
<p class="hero-badge">v2.3.
|
|
65
|
+
<p class="hero-badge">v2.3.3 — 59 skills • 8 platforms • MIT</p>
|
|
66
66
|
<h1>Less skills.<br><span class="accent">Deeper connections.</span></h1>
|
|
67
67
|
<p class="hero-sub">A mesh ecosystem for AI coding assistants. Skills call each other bidirectionally, forming resilient workflows that adapt when things go wrong.</p>
|
|
68
68
|
<div class="hero-actions">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rune-kit/rune",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.3",
|
|
4
4
|
"description": "59-skill mesh for AI coding assistants — 5-layer architecture, 200+ connections, 8 platforms (Claude Code, Cursor, Windsurf, Antigravity, Codex, OpenCode, OpenClaw, Generic)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/skills/db/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: db
|
|
|
3
3
|
description: Database workflow specialist. Generates migration files with rollback scripts, detects breaking schema changes, and validates query parameterization.
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.2.0"
|
|
7
7
|
layer: L2
|
|
8
8
|
model: sonnet
|
|
9
9
|
group: development
|
|
@@ -35,6 +35,10 @@ Database workflow specialist. Handles the parts of database work that cause prod
|
|
|
35
35
|
- `deploy` (L2): pre-deploy migration safety check
|
|
36
36
|
- `audit` (L2): database health dimension
|
|
37
37
|
|
|
38
|
+
## References
|
|
39
|
+
|
|
40
|
+
- `references/scaling-reference.md` — Index strategies, query optimization, N+1 prevention, connection pooling, read replicas, partitioning, sharding, denormalization. Load when scaling, performance, or indexing context detected.
|
|
41
|
+
|
|
38
42
|
## Executable Steps
|
|
39
43
|
|
|
40
44
|
### Step 1 — Discovery
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# Database Scaling Reference
|
|
2
|
+
|
|
3
|
+
> Loaded by `db` skill when scaling, indexing, or performance optimization context is detected.
|
|
4
|
+
> Source patterns: production-proven PostgreSQL scaling strategies.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Indexing Strategy
|
|
9
|
+
|
|
10
|
+
### Index Type Selection
|
|
11
|
+
|
|
12
|
+
| Type | Use Case | Example |
|
|
13
|
+
|------|----------|---------|
|
|
14
|
+
| **B-tree** (default) | Equality, range, sorting | `WHERE id = 1`, `ORDER BY date` |
|
|
15
|
+
| **GIN** | Arrays, JSONB, full-text search | `WHERE tags @> ARRAY['a']` |
|
|
16
|
+
| **GiST** | Geometric, spatial, nearest-neighbor | PostGIS, `ORDER BY location <->` |
|
|
17
|
+
| **BRIN** | Large tables with natural ordering | Time-series > 100GB |
|
|
18
|
+
|
|
19
|
+
### Compound Index Rules
|
|
20
|
+
|
|
21
|
+
Order columns: **equality → range → sort**
|
|
22
|
+
|
|
23
|
+
```sql
|
|
24
|
+
-- Query: WHERE customer_id = X AND status IN (...) AND created_at > Y ORDER BY id
|
|
25
|
+
CREATE INDEX idx_orders ON orders(customer_id, status, created_at, id);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Partial Indexes (index what you query)
|
|
29
|
+
|
|
30
|
+
```sql
|
|
31
|
+
-- Only index active users (90% smaller, 90% faster writes)
|
|
32
|
+
CREATE INDEX idx_active_users ON users(email) WHERE status = 'active';
|
|
33
|
+
|
|
34
|
+
-- Only index pending orders
|
|
35
|
+
CREATE INDEX idx_pending_orders ON orders(customer_id) WHERE status = 'pending';
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Covering Indexes (avoid heap lookups)
|
|
39
|
+
|
|
40
|
+
```sql
|
|
41
|
+
-- Query only needs total and date — include them in index
|
|
42
|
+
CREATE INDEX idx_orders_covering ON orders(customer_id)
|
|
43
|
+
INCLUDE (total, created_at);
|
|
44
|
+
|
|
45
|
+
-- Index-only scan — no heap access needed
|
|
46
|
+
SELECT total, created_at FROM orders WHERE customer_id = 123;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Index Anti-Patterns
|
|
50
|
+
|
|
51
|
+
- Indexing every column (slows writes, wastes storage)
|
|
52
|
+
- Indexing low-cardinality columns alone (status with 3 values → useless)
|
|
53
|
+
- Missing indexes on foreign keys (kills JOIN performance)
|
|
54
|
+
- Unused indexes (check `pg_stat_user_indexes` for `idx_scan = 0`)
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Query Optimization
|
|
59
|
+
|
|
60
|
+
### EXPLAIN ANALYZE — Always Profile
|
|
61
|
+
|
|
62
|
+
```sql
|
|
63
|
+
EXPLAIN (ANALYZE, BUFFERS, TIMING ON)
|
|
64
|
+
SELECT * FROM orders WHERE customer_id = 123 AND created_at > NOW() - INTERVAL '30d';
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Red flags in output:**
|
|
68
|
+
- `Seq Scan` on large table → missing index
|
|
69
|
+
- `Filter` removes most rows → index should be more selective
|
|
70
|
+
- `Sort` with high cost → add index that matches ORDER BY
|
|
71
|
+
- `Nested Loop` with large outer table → consider hash/merge join
|
|
72
|
+
|
|
73
|
+
### pg_stat_statements — Find Problem Queries
|
|
74
|
+
|
|
75
|
+
```sql
|
|
76
|
+
-- Top 10 slowest queries by total time
|
|
77
|
+
SELECT query, calls, total_exec_time / 1000 AS total_sec,
|
|
78
|
+
mean_exec_time AS avg_ms,
|
|
79
|
+
100.0 * shared_blks_hit / NULLIF(shared_blks_hit + shared_blks_read, 0) AS cache_pct
|
|
80
|
+
FROM pg_stat_statements
|
|
81
|
+
ORDER BY total_exec_time DESC LIMIT 10;
|
|
82
|
+
|
|
83
|
+
-- N+1 detector: queries called many times with low individual cost
|
|
84
|
+
SELECT query, calls, mean_exec_time AS avg_ms
|
|
85
|
+
FROM pg_stat_statements
|
|
86
|
+
WHERE calls > 1000 AND mean_exec_time < 5
|
|
87
|
+
ORDER BY calls DESC LIMIT 10;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## N+1 Query Prevention
|
|
93
|
+
|
|
94
|
+
### Prisma
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// BAD: 1 + N queries
|
|
98
|
+
const users = await prisma.user.findMany();
|
|
99
|
+
for (const u of users) {
|
|
100
|
+
u.posts = await prisma.post.findMany({ where: { authorId: u.id } });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// GOOD: 2 queries (parallel fetch)
|
|
104
|
+
const users = await prisma.user.findMany({ include: { posts: true } });
|
|
105
|
+
|
|
106
|
+
// GOOD: Single query (database JOIN)
|
|
107
|
+
const users = await prisma.user.findMany({
|
|
108
|
+
relationLoadStrategy: 'join',
|
|
109
|
+
include: { posts: { select: { id: true, title: true } } }
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Drizzle (no N+1 by design)
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const results = await db.select().from(users)
|
|
117
|
+
.leftJoin(posts, eq(users.id, posts.userId));
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Detection
|
|
121
|
+
|
|
122
|
+
Enable query logging — look for **query count = 1 + N** where N = row count.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// Prisma query logging
|
|
126
|
+
const prisma = new PrismaClient({ log: [{ emit: 'event', level: 'query' }] });
|
|
127
|
+
prisma.$on('query', (e) => {
|
|
128
|
+
if (e.duration > 100) console.warn(`SLOW: ${e.duration}ms — ${e.query}`);
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Connection Pooling
|
|
135
|
+
|
|
136
|
+
### Sizing Formula
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
pool_size = (CPU cores × 2) + effective_spindle_count
|
|
140
|
+
|
|
141
|
+
4-core → 9 connections
|
|
142
|
+
8-core → 17 connections
|
|
143
|
+
16-core → 33 connections
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### PgBouncer Configuration
|
|
147
|
+
|
|
148
|
+
```ini
|
|
149
|
+
[pgbouncer]
|
|
150
|
+
pool_mode = transaction # Release after each transaction
|
|
151
|
+
default_pool_size = 25 # Per database/user pair
|
|
152
|
+
min_pool_size = 10 # Keep warm
|
|
153
|
+
reserve_pool_size = 5 # Overflow buffer
|
|
154
|
+
max_client_connections = 1000 # Total client limit
|
|
155
|
+
server_idle_timeout = 600 # Close idle server connections (10min)
|
|
156
|
+
query_wait_timeout = 120 # Max wait for available connection
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Warning signs:** Pool utilization > 80%, rising wait times, connection timeouts.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Read Replicas
|
|
164
|
+
|
|
165
|
+
### When to Add
|
|
166
|
+
|
|
167
|
+
- Read:write ratio exceeds **10:1**
|
|
168
|
+
- Primary CPU consistently > 70% from read queries
|
|
169
|
+
- Analytics queries competing with transactional reads
|
|
170
|
+
|
|
171
|
+
### Routing Pattern
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const primaryPool = new Pool({ host: 'primary.db.com' });
|
|
175
|
+
const replicaPool = new Pool({ host: 'replica.db.com' });
|
|
176
|
+
|
|
177
|
+
async function query(sql: string, params: unknown[], opts?: { write?: boolean }) {
|
|
178
|
+
const pool = opts?.write ? primaryPool : replicaPool;
|
|
179
|
+
return pool.query(sql, params);
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Replication Lag
|
|
184
|
+
|
|
185
|
+
- Typical: 5-50ms for well-tuned async replication
|
|
186
|
+
- **Read-your-writes consistency**: After a write, read from primary for that user's session
|
|
187
|
+
- **Analytics queries**: Always safe on replicas (stale data acceptable)
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Partitioning
|
|
192
|
+
|
|
193
|
+
### When to Partition
|
|
194
|
+
|
|
195
|
+
- Table exceeds **100GB** or **100M+ rows**
|
|
196
|
+
- Queries always filter on a specific column (date, tenant)
|
|
197
|
+
- Need fast deletion of old data (`DROP` partition vs `DELETE`)
|
|
198
|
+
|
|
199
|
+
### Range Partitioning (time-series)
|
|
200
|
+
|
|
201
|
+
```sql
|
|
202
|
+
CREATE TABLE events (
|
|
203
|
+
id BIGSERIAL, created_at TIMESTAMPTZ NOT NULL, data JSONB
|
|
204
|
+
) PARTITION BY RANGE (created_at);
|
|
205
|
+
|
|
206
|
+
CREATE TABLE events_2025_01 PARTITION OF events
|
|
207
|
+
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
|
|
208
|
+
|
|
209
|
+
-- Delete old data instantly
|
|
210
|
+
DROP TABLE events_2024_01; -- 0.01s vs DELETE minutes
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Hash Partitioning (even distribution)
|
|
214
|
+
|
|
215
|
+
```sql
|
|
216
|
+
CREATE TABLE orders (id BIGSERIAL, customer_id INT, data JSONB)
|
|
217
|
+
PARTITION BY HASH (customer_id);
|
|
218
|
+
|
|
219
|
+
CREATE TABLE orders_0 PARTITION OF orders FOR VALUES WITH (MODULUS 4, REMAINDER 0);
|
|
220
|
+
CREATE TABLE orders_1 PARTITION OF orders FOR VALUES WITH (MODULUS 4, REMAINDER 1);
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Rule:** Keep partition count under 500 — query planning overhead grows with partition count.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Sharding Decision Matrix
|
|
228
|
+
|
|
229
|
+
### When to Shard (Last Resort)
|
|
230
|
+
|
|
231
|
+
| Strategy | Pros | Cons |
|
|
232
|
+
|----------|------|------|
|
|
233
|
+
| **Hash-based** | Even distribution | No range queries on shard key |
|
|
234
|
+
| **Range-based** | Range queries work | Hot shards if distribution skewed |
|
|
235
|
+
| **Tenant-based** | Strong isolation | Uneven if tenants vary in size |
|
|
236
|
+
|
|
237
|
+
### Before Sharding, Try These First
|
|
238
|
+
|
|
239
|
+
1. Add proper indexes
|
|
240
|
+
2. Fix N+1 queries
|
|
241
|
+
3. Add read replicas
|
|
242
|
+
4. Add caching layer
|
|
243
|
+
5. Partition large tables
|
|
244
|
+
6. Vertical scaling (bigger instance)
|
|
245
|
+
7. Archive old data
|
|
246
|
+
8. Optimize queries with EXPLAIN ANALYZE
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Denormalization Decision Matrix
|
|
251
|
+
|
|
252
|
+
| Scenario | Keep Normalized | Denormalize |
|
|
253
|
+
|----------|----------------|-------------|
|
|
254
|
+
| High write volume | Yes | No |
|
|
255
|
+
| High read volume, slow joins | No | Yes |
|
|
256
|
+
| Data consistency critical | Yes | No |
|
|
257
|
+
| Analytics / reporting | No | Yes (materialized views) |
|
|
258
|
+
|
|
259
|
+
### Materialized Views
|
|
260
|
+
|
|
261
|
+
```sql
|
|
262
|
+
CREATE MATERIALIZED VIEW order_stats AS
|
|
263
|
+
SELECT customer_id, COUNT(*) AS order_count, SUM(total) AS total_spent
|
|
264
|
+
FROM orders GROUP BY customer_id;
|
|
265
|
+
|
|
266
|
+
-- Refresh periodically (CONCURRENTLY allows reads during refresh)
|
|
267
|
+
REFRESH MATERIALIZED VIEW CONCURRENTLY order_stats;
|
|
268
|
+
CREATE UNIQUE INDEX ON order_stats(customer_id);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Rule:** Start normalized. Denormalize specific queries only when EXPLAIN shows expensive joins that caching can't solve.
|
package/skills/perf/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: perf
|
|
|
3
3
|
description: Performance regression gate. Detects N+1 queries, sync-in-async, missing indexes, memory leaks, and bundle bloat before they reach production.
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.2.0"
|
|
7
7
|
layer: L2
|
|
8
8
|
model: sonnet
|
|
9
9
|
group: quality
|
|
@@ -38,6 +38,11 @@ Performance regression gate. Analyzes code changes for patterns that cause measu
|
|
|
38
38
|
- `review` (L2): performance patterns detected in diff
|
|
39
39
|
- `deploy` (L2): pre-deploy perf regression check
|
|
40
40
|
|
|
41
|
+
## References
|
|
42
|
+
|
|
43
|
+
- `references/cost-reference.md` — Cost priority hierarchy, quick wins checklist, instance right-sizing, data transfer traps, serverless optimization, observability cost control, managed vs self-hosted matrix, unit economics tracking. Load when cost analysis or FinOps context detected.
|
|
44
|
+
- `references/scalability-reference.md` — Bottleneck identification flow, performance thresholds, API patterns (cursor pagination, rate limiting, circuit breaker, graceful shutdown), caching strategies, queue-based load leveling, concurrency patterns, K8s HPA, CDN headers, load testing. Load when scaling or infrastructure optimization context detected.
|
|
45
|
+
|
|
41
46
|
## Executable Steps
|
|
42
47
|
|
|
43
48
|
### Step 1 — Scope
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Cost Optimization Reference
|
|
2
|
+
|
|
3
|
+
> Loaded by `perf` skill when cost analysis, cloud spending, or FinOps context is detected.
|
|
4
|
+
> Patterns: production-proven cost reduction strategies with dollar-impact estimates.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Cost Priority Hierarchy
|
|
9
|
+
|
|
10
|
+
Optimize in this order — higher tiers save 10x more than lower:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
1. Architecture choices (10x impact — monolith vs micro, serverless vs containers)
|
|
14
|
+
2. Data transfer (NAT Gateway, cross-region, CDN vs origin)
|
|
15
|
+
3. Compute right-sizing (instance types, spot/reserved, autoscaling)
|
|
16
|
+
4. Database optimization (query tuning, connection pooling, read replicas)
|
|
17
|
+
5. Caching layer (Redis, CDN, in-memory — ROI depends on hit rate)
|
|
18
|
+
6. Storage tiering (S3 lifecycle, cold storage, cleanup)
|
|
19
|
+
7. Bundle/asset size (tree-shaking, image optimization, compression)
|
|
20
|
+
8. Observability costs (log sampling, trace sampling, retention)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Quick Wins Checklist
|
|
26
|
+
|
|
27
|
+
| Fix | Typical Savings | Effort |
|
|
28
|
+
|-----|----------------|--------|
|
|
29
|
+
| S3 Intelligent-Tiering on infrequent buckets | 40-70% storage | 5 min |
|
|
30
|
+
| Fix N+1 queries (Prisma `include`) | 10-100x query reduction | 30 min |
|
|
31
|
+
| WebP/AVIF images (Sharp pipeline) | 25-80% bandwidth | 1 hr |
|
|
32
|
+
| Add CDN for static assets | 50-90% origin traffic | 1 hr |
|
|
33
|
+
| NAT Gateway → VPC endpoints for AWS services | $30-100/mo per service | 30 min |
|
|
34
|
+
| Log sampling (10% INFO, 100% ERROR) | 50-80% observability bill | 30 min |
|
|
35
|
+
| Gzip/Brotli response compression | 70-90% transfer size | 15 min |
|
|
36
|
+
| Lambda memory right-sizing (power tuning) | 10-40% Lambda cost | 1 hr |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Instance Right-Sizing
|
|
41
|
+
|
|
42
|
+
### AWS EC2 Strategy
|
|
43
|
+
|
|
44
|
+
| Strategy | Savings | Commitment |
|
|
45
|
+
|----------|---------|-----------|
|
|
46
|
+
| On-Demand | Baseline | None |
|
|
47
|
+
| Spot Instances | 60-90% | Can be interrupted |
|
|
48
|
+
| Reserved (1-yr) | ~40% | 1 year |
|
|
49
|
+
| Reserved (3-yr) | ~60% | 3 years |
|
|
50
|
+
| Savings Plans | ~30-40% | Flexible commitment |
|
|
51
|
+
|
|
52
|
+
### Kubernetes Right-Sizing
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
# Set requests = actual P95 usage, limits = 2x requests
|
|
56
|
+
resources:
|
|
57
|
+
requests:
|
|
58
|
+
cpu: "250m" # Based on actual P95 CPU usage
|
|
59
|
+
memory: "256Mi" # Based on actual P95 memory
|
|
60
|
+
limits:
|
|
61
|
+
cpu: "500m"
|
|
62
|
+
memory: "512Mi"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Check actual usage: `kubectl top pods --sort-by=cpu`
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Data Transfer Cost Traps
|
|
70
|
+
|
|
71
|
+
### NAT Gateway Problem
|
|
72
|
+
|
|
73
|
+
NAT Gateway: **$0.045/GB + $0.045/hr** — one of the most expensive AWS surprises.
|
|
74
|
+
|
|
75
|
+
**Fix:** Replace with VPC endpoints for AWS services:
|
|
76
|
+
```
|
|
77
|
+
S3 Gateway Endpoint → Free (gateway type)
|
|
78
|
+
DynamoDB Gateway Endpoint → Free (gateway type)
|
|
79
|
+
SQS/SNS Interface Endpoint → $0.01/hr (still 4x cheaper than NAT)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Cross-Region Transfer
|
|
83
|
+
|
|
84
|
+
- Same region, same AZ: **Free**
|
|
85
|
+
- Same region, different AZ: **$0.01/GB**
|
|
86
|
+
- Cross-region: **$0.02/GB**
|
|
87
|
+
- Internet egress: **$0.09/GB** (first 10TB)
|
|
88
|
+
|
|
89
|
+
**Rule:** Keep services in the same AZ when possible. Use CloudFront for global distribution.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Serverless Optimization
|
|
94
|
+
|
|
95
|
+
### Lambda Memory Tuning
|
|
96
|
+
|
|
97
|
+
Lambda CPU scales linearly with memory. Sometimes MORE memory = CHEAPER (faster execution):
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
128MB, 3000ms = 375,000 GB-ms = $0.00000625
|
|
101
|
+
512MB, 800ms = 400,000 GB-ms = $0.00000667 (similar cost, 4x faster)
|
|
102
|
+
1024MB, 400ms = 400,000 GB-ms = $0.00000667 (same cost, 7.5x faster)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Use [AWS Lambda Power Tuning](https://github.com/alexcasalboni/aws-lambda-power-tuning) to find optimal memory.
|
|
106
|
+
|
|
107
|
+
### Cold Start Reduction
|
|
108
|
+
|
|
109
|
+
| Technique | Impact |
|
|
110
|
+
|-----------|--------|
|
|
111
|
+
| Smaller deployment package | -100-500ms |
|
|
112
|
+
| Use Graviton (arm64) | -200ms + 20% cheaper |
|
|
113
|
+
| Provisioned concurrency | Eliminates cold starts |
|
|
114
|
+
| Lazy-load heavy SDKs | -200-1000ms |
|
|
115
|
+
| Use ESM instead of CJS | -100-300ms (Node.js) |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Observability Cost Control
|
|
120
|
+
|
|
121
|
+
### Log Sampling
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// Sample 10% of INFO logs, keep 100% of errors
|
|
125
|
+
const shouldLog = (level: string): boolean => {
|
|
126
|
+
if (level === 'error' || level === 'warn') return true;
|
|
127
|
+
return Math.random() < 0.1; // 10% sampling
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Trace Sampling
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// 100% errors and slow requests, 10% normal
|
|
135
|
+
const shouldTrace = (duration: number, isError: boolean): boolean => {
|
|
136
|
+
if (isError) return true;
|
|
137
|
+
if (duration > 1000) return true; // Slow requests
|
|
138
|
+
return Math.random() < 0.1;
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### High-Cardinality Metrics — NEVER DO
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// BAD: Creates millions of unique time series
|
|
146
|
+
metrics.counter('requests', { userId: req.userId }); // ← kills Datadog bill
|
|
147
|
+
|
|
148
|
+
// GOOD: Low-cardinality labels only
|
|
149
|
+
metrics.counter('requests', { endpoint: '/api/users', method: 'GET', status: '200' });
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Retention Policies
|
|
153
|
+
|
|
154
|
+
| Data Type | Recommended Retention |
|
|
155
|
+
|-----------|----------------------|
|
|
156
|
+
| Raw logs | 7-14 days |
|
|
157
|
+
| Aggregated metrics | 90 days |
|
|
158
|
+
| Error logs | 30-90 days |
|
|
159
|
+
| Traces | 7 days |
|
|
160
|
+
| Audit logs | 1-7 years (compliance) |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Managed vs Self-Hosted Decision Matrix
|
|
165
|
+
|
|
166
|
+
| Service | Self-Host When | Stay Managed When |
|
|
167
|
+
|---------|---------------|-------------------|
|
|
168
|
+
| Auth | >200K MAU ($4K+/mo managed) | <200K MAU or need compliance |
|
|
169
|
+
| Search | >500K records or >$500/mo | <500K records, need instant setup |
|
|
170
|
+
| Database | >$500/mo RDS bill | Need HA, backups, patching handled |
|
|
171
|
+
| Email | Almost never | Always (deliverability is hard) |
|
|
172
|
+
| Monitoring | >$1K/mo Datadog | Need APM + distributed tracing |
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Unit Economics Tracking
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Track cost per unit to detect inefficiency trends
|
|
180
|
+
interface UnitEconomics {
|
|
181
|
+
costPerRequest: number; // Total infra / total requests
|
|
182
|
+
costPerUser: number; // Total infra / MAU
|
|
183
|
+
costPerTransaction: number; // Total infra / transactions
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Alert if unit cost increases >20% month-over-month
|
|
187
|
+
function checkCostTrend(current: number, previous: number): void {
|
|
188
|
+
const increase = (current - previous) / previous;
|
|
189
|
+
if (increase > 0.2) {
|
|
190
|
+
alert(`Unit cost increased ${(increase * 100).toFixed(0)}% — investigate`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Cost Optimization Priority Matrix
|
|
198
|
+
|
|
199
|
+
| Effort | Low Savings (<$100/mo) | Med Savings ($100-500) | High Savings (>$500) |
|
|
200
|
+
|--------|----------------------|----------------------|---------------------|
|
|
201
|
+
| **Low** | Log retention | Image optimization | S3 tiering |
|
|
202
|
+
| **Medium** | Bundle analysis | Caching layer | Instance right-sizing |
|
|
203
|
+
| **High** | — | Query optimization | Architecture redesign |
|
|
204
|
+
|
|
205
|
+
**Rule:** Start top-right (high savings, low effort), work diagonally down-left.
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# Scalability Reference
|
|
2
|
+
|
|
3
|
+
> Loaded by `perf` skill when scaling, load handling, or infrastructure optimization context is detected.
|
|
4
|
+
> Patterns: production-proven scalability strategies for Node.js/TypeScript applications.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Bottleneck Identification Flow
|
|
9
|
+
|
|
10
|
+
Profile BEFORE optimizing. Follow this decision tree:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Is database the bottleneck (>50% of response time)?
|
|
14
|
+
├── YES → Index optimization, connection pooling, read replicas, query caching
|
|
15
|
+
│ See db/references/scaling-reference.md
|
|
16
|
+
└── NO
|
|
17
|
+
Is external API the bottleneck?
|
|
18
|
+
├── YES → Circuit breaker, caching, parallel requests, queue background
|
|
19
|
+
└── NO
|
|
20
|
+
Is it CPU-bound?
|
|
21
|
+
├── YES → Worker threads, horizontal scaling, algorithm optimization
|
|
22
|
+
└── NO
|
|
23
|
+
Is it memory pressure?
|
|
24
|
+
├── YES → Leak detection, streaming, pagination, bounded caches
|
|
25
|
+
└── NO → Measure again with better instrumentation
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Performance Thresholds
|
|
29
|
+
|
|
30
|
+
| Metric | Healthy | Warning | Critical |
|
|
31
|
+
|--------|---------|---------|----------|
|
|
32
|
+
| p99 latency | <200ms | 200-500ms | >500ms |
|
|
33
|
+
| DB cache hit ratio | >95% | 90-95% | <90% |
|
|
34
|
+
| Connection pool utilization | <70% | 70-85% | >85% |
|
|
35
|
+
| CPU utilization | <60% | 60-80% | >80% |
|
|
36
|
+
| Memory utilization | <70% | 70-85% | >85% |
|
|
37
|
+
| Error rate | <0.1% | 0.1-1% | >1% |
|
|
38
|
+
| Queue depth | <100 | 100-1000 | >1000 |
|
|
39
|
+
| Queue wait time | <1s | 1-10s | >10s |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## API Scalability Patterns
|
|
44
|
+
|
|
45
|
+
### Cursor-Based Pagination
|
|
46
|
+
|
|
47
|
+
Offset pagination breaks at scale (`OFFSET 100000` = scan 100K rows). Use cursor-based:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Cursor-based — consistent performance at any depth
|
|
51
|
+
async function listOrders(cursor?: string, limit = 20) {
|
|
52
|
+
const where = cursor ? { id: { gt: cursor } } : {};
|
|
53
|
+
const items = await prisma.order.findMany({
|
|
54
|
+
where,
|
|
55
|
+
take: limit + 1, // Fetch one extra to detect hasMore
|
|
56
|
+
orderBy: { id: 'asc' },
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const hasMore = items.length > limit;
|
|
60
|
+
const data = hasMore ? items.slice(0, -1) : items;
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
data,
|
|
64
|
+
cursor: data.at(-1)?.id ?? null,
|
|
65
|
+
hasMore,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Rate Limiting (Tiered)
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const rateLimits = {
|
|
74
|
+
free: { requests: 100, window: '15m' },
|
|
75
|
+
pro: { requests: 1000, window: '15m' },
|
|
76
|
+
enterprise: { requests: 10000, window: '15m' },
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Response headers (always include)
|
|
80
|
+
// X-RateLimit-Limit: 1000
|
|
81
|
+
// X-RateLimit-Remaining: 847
|
|
82
|
+
// X-RateLimit-Reset: 1699234567
|
|
83
|
+
// Retry-After: 30 (only on 429)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Circuit Breaker
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import CircuitBreaker from 'opossum';
|
|
90
|
+
|
|
91
|
+
const breaker = new CircuitBreaker(callExternalAPI, {
|
|
92
|
+
timeout: 3000, // Fail if function takes > 3s
|
|
93
|
+
errorThresholdPercentage: 50, // Open if 50% fail
|
|
94
|
+
resetTimeout: 30000, // Try again after 30s
|
|
95
|
+
volumeThreshold: 10, // Min calls before tripping
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
breaker.fallback(() => cachedResponse);
|
|
99
|
+
breaker.on('open', () => logger.warn('Circuit breaker OPEN'));
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Graceful Shutdown
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
async function shutdown(signal: string) {
|
|
106
|
+
logger.info(`${signal} received — starting graceful shutdown`);
|
|
107
|
+
|
|
108
|
+
// 1. Stop accepting new connections
|
|
109
|
+
server.close();
|
|
110
|
+
|
|
111
|
+
// 2. Finish in-flight requests (with timeout)
|
|
112
|
+
await Promise.race([
|
|
113
|
+
finishInflightRequests(),
|
|
114
|
+
new Promise(resolve => setTimeout(resolve, 30000)),
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
// 3. Close DB connections
|
|
118
|
+
await prisma.$disconnect();
|
|
119
|
+
await redis.quit();
|
|
120
|
+
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
125
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Caching Strategies
|
|
131
|
+
|
|
132
|
+
### Strategy Selection
|
|
133
|
+
|
|
134
|
+
| Strategy | When to Use | Trade-off |
|
|
135
|
+
|----------|-------------|-----------|
|
|
136
|
+
| **Cache-aside** | Most common. App checks cache, falls back to DB | May serve stale data |
|
|
137
|
+
| **Write-through** | Cache updated on every write | Higher write latency |
|
|
138
|
+
| **Write-behind** | Cache updated async after write | Risk of data loss |
|
|
139
|
+
| **Read-through** | Cache auto-fetches on miss | Cache library dependency |
|
|
140
|
+
|
|
141
|
+
### TTL Guidelines
|
|
142
|
+
|
|
143
|
+
| Data Type | TTL | Rationale |
|
|
144
|
+
|-----------|-----|-----------|
|
|
145
|
+
| Static config | 1 hour | Rarely changes |
|
|
146
|
+
| User profile | 5 min | Changes occasionally |
|
|
147
|
+
| Product catalog | 15 min | Balance freshness vs load |
|
|
148
|
+
| Session data | 30 min sliding | Security + UX |
|
|
149
|
+
| Rate limit counters | Match window | Exact timing matters |
|
|
150
|
+
| Search results | 60s | High-read, low-freshness need |
|
|
151
|
+
|
|
152
|
+
### Cache Invalidation Patterns
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// 1. TTL-based (simplest, eventual consistency)
|
|
156
|
+
await redis.setex(`user:${id}`, 300, JSON.stringify(user));
|
|
157
|
+
|
|
158
|
+
// 2. Explicit invalidation (strongest consistency)
|
|
159
|
+
await prisma.user.update({ where: { id }, data });
|
|
160
|
+
await redis.del(`user:${id}`);
|
|
161
|
+
|
|
162
|
+
// 3. Tag-based (invalidate groups)
|
|
163
|
+
await redis.sadd('tag:users', `user:${id}`, `user:${id}:posts`);
|
|
164
|
+
// On invalidate:
|
|
165
|
+
const keys = await redis.smembers('tag:users');
|
|
166
|
+
await redis.del(...keys);
|
|
167
|
+
|
|
168
|
+
// 4. Versioned keys (zero-downtime cache migration)
|
|
169
|
+
const version = 'v2';
|
|
170
|
+
await redis.setex(`user:${version}:${id}`, 300, data);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Queue-Based Load Leveling
|
|
176
|
+
|
|
177
|
+
### When to Use Queues
|
|
178
|
+
|
|
179
|
+
- Request takes > 500ms to process
|
|
180
|
+
- External API with rate limits
|
|
181
|
+
- Spiky traffic patterns
|
|
182
|
+
- Fire-and-forget operations (email, webhooks, analytics)
|
|
183
|
+
|
|
184
|
+
### BullMQ Pattern
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { Queue, Worker } from 'bullmq';
|
|
188
|
+
|
|
189
|
+
const emailQueue = new Queue('emails', { connection: redis });
|
|
190
|
+
|
|
191
|
+
// Producer — returns immediately
|
|
192
|
+
await emailQueue.add('welcome', { userId, email }, {
|
|
193
|
+
attempts: 3,
|
|
194
|
+
backoff: { type: 'exponential', delay: 1000 },
|
|
195
|
+
removeOnComplete: 1000, // Keep last 1000 completed
|
|
196
|
+
removeOnFail: 5000, // Keep last 5000 failed
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Consumer — processes in background
|
|
200
|
+
const worker = new Worker('emails', async (job) => {
|
|
201
|
+
await sendEmail(job.data.email, 'Welcome!');
|
|
202
|
+
}, {
|
|
203
|
+
connection: redis,
|
|
204
|
+
concurrency: 5, // Process 5 emails in parallel
|
|
205
|
+
limiter: { max: 10, duration: 1000 }, // Rate limit: 10/sec
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Concurrency Patterns
|
|
212
|
+
|
|
213
|
+
### Worker Threads (CPU-intensive)
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
|
|
217
|
+
|
|
218
|
+
if (isMainThread) {
|
|
219
|
+
const worker = new Worker(__filename, { workerData: { input } });
|
|
220
|
+
worker.on('message', (result) => resolve(result));
|
|
221
|
+
worker.on('error', reject);
|
|
222
|
+
} else {
|
|
223
|
+
const result = heavyComputation(workerData.input);
|
|
224
|
+
parentPort?.postMessage(result);
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Backpressure (prevent OOM)
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
// Bounded queue — reject when full instead of consuming infinite memory
|
|
232
|
+
class BoundedQueue<T> {
|
|
233
|
+
private queue: T[] = [];
|
|
234
|
+
constructor(private maxSize: number) {}
|
|
235
|
+
|
|
236
|
+
enqueue(item: T): boolean {
|
|
237
|
+
if (this.queue.length >= this.maxSize) return false; // Apply backpressure
|
|
238
|
+
this.queue.push(item);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Semaphore (limit concurrent operations)
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
class Semaphore {
|
|
248
|
+
private current = 0;
|
|
249
|
+
private queue: (() => void)[] = [];
|
|
250
|
+
|
|
251
|
+
constructor(private max: number) {}
|
|
252
|
+
|
|
253
|
+
async acquire(): Promise<void> {
|
|
254
|
+
if (this.current < this.max) {
|
|
255
|
+
this.current++;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
return new Promise((resolve) => this.queue.push(resolve));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
release(): void {
|
|
262
|
+
this.current--;
|
|
263
|
+
const next = this.queue.shift();
|
|
264
|
+
if (next) { this.current++; next(); }
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Usage: limit to 10 concurrent DB connections
|
|
269
|
+
const sem = new Semaphore(10);
|
|
270
|
+
await sem.acquire();
|
|
271
|
+
try { await db.query(sql); }
|
|
272
|
+
finally { sem.release(); }
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Deployment Scalability
|
|
278
|
+
|
|
279
|
+
### Kubernetes HPA
|
|
280
|
+
|
|
281
|
+
```yaml
|
|
282
|
+
apiVersion: autoscaling/v2
|
|
283
|
+
kind: HorizontalPodAutoscaler
|
|
284
|
+
metadata:
|
|
285
|
+
name: api-hpa
|
|
286
|
+
spec:
|
|
287
|
+
scaleTargetRef:
|
|
288
|
+
apiVersion: apps/v1
|
|
289
|
+
kind: Deployment
|
|
290
|
+
name: api
|
|
291
|
+
minReplicas: 2
|
|
292
|
+
maxReplicas: 20
|
|
293
|
+
metrics:
|
|
294
|
+
- type: Resource
|
|
295
|
+
resource:
|
|
296
|
+
name: cpu
|
|
297
|
+
target:
|
|
298
|
+
type: Utilization
|
|
299
|
+
averageUtilization: 70
|
|
300
|
+
behavior:
|
|
301
|
+
scaleUp:
|
|
302
|
+
stabilizationWindowSeconds: 60
|
|
303
|
+
scaleDown:
|
|
304
|
+
stabilizationWindowSeconds: 300 # Slow scale-down prevents flapping
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### CDN Cache-Control Headers
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
# Immutable assets (hashed filenames)
|
|
311
|
+
Cache-Control: public, max-age=31536000, immutable
|
|
312
|
+
|
|
313
|
+
# API responses (short cache + revalidation)
|
|
314
|
+
Cache-Control: public, max-age=60, stale-while-revalidate=30
|
|
315
|
+
|
|
316
|
+
# Private user data
|
|
317
|
+
Cache-Control: private, no-store
|
|
318
|
+
|
|
319
|
+
# HTML pages (revalidate every time)
|
|
320
|
+
Cache-Control: no-cache
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Zero-Downtime Database Migrations
|
|
324
|
+
|
|
325
|
+
3-phase safe migration pattern:
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
Phase 1: ADD new column (nullable) → deploy
|
|
329
|
+
Phase 2: Deploy code that writes to BOTH old + new columns → backfill
|
|
330
|
+
Phase 3: Drop old column → deploy
|
|
331
|
+
|
|
332
|
+
NEVER in one step:
|
|
333
|
+
- Rename column (breaks running code)
|
|
334
|
+
- Change type (data loss risk)
|
|
335
|
+
- Add NOT NULL without default (fails on existing rows)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Load Testing
|
|
341
|
+
|
|
342
|
+
### k6 Quick Start
|
|
343
|
+
|
|
344
|
+
```javascript
|
|
345
|
+
import http from 'k6/http';
|
|
346
|
+
import { check, sleep } from 'k6';
|
|
347
|
+
|
|
348
|
+
export const options = {
|
|
349
|
+
stages: [
|
|
350
|
+
{ duration: '1m', target: 50 }, // Ramp up
|
|
351
|
+
{ duration: '3m', target: 50 }, // Steady state
|
|
352
|
+
{ duration: '1m', target: 0 }, // Ramp down
|
|
353
|
+
],
|
|
354
|
+
thresholds: {
|
|
355
|
+
http_req_duration: ['p(99)<500'], // 99th percentile < 500ms
|
|
356
|
+
http_req_failed: ['rate<0.01'], // Error rate < 1%
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
export default function () {
|
|
361
|
+
const res = http.get('https://api.example.com/orders');
|
|
362
|
+
check(res, { 'status 200': (r) => r.status === 200 });
|
|
363
|
+
sleep(1);
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### What to Measure
|
|
368
|
+
|
|
369
|
+
| Metric | Target | Why |
|
|
370
|
+
|--------|--------|-----|
|
|
371
|
+
| p50 latency | <100ms | Typical user experience |
|
|
372
|
+
| p99 latency | <500ms | Worst-case user experience |
|
|
373
|
+
| Throughput (RPS) | >baseline | Capacity headroom |
|
|
374
|
+
| Error rate | <0.1% | Reliability |
|
|
375
|
+
| CPU during load | <80% | Scaling headroom |
|
|
376
|
+
| Memory during load | <70% | Leak detection |
|
|
377
|
+
|
|
378
|
+
**Rule:** Always observe p99, not p50. Median latency hides tail latency that kills user experience.
|
|
@@ -3,7 +3,7 @@ name: skill-forge
|
|
|
3
3
|
description: Use when creating new Rune skills, editing existing skills, or verifying skill quality before deployment. Applies TDD discipline to skill authoring — test before write, verify before ship.
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
|
-
version: "1.
|
|
6
|
+
version: "1.6.0"
|
|
7
7
|
layer: L2
|
|
8
8
|
model: opus
|
|
9
9
|
group: creation
|
|
@@ -34,6 +34,10 @@ The skill that builds skills. Applies Test-Driven Development to skill authoring
|
|
|
34
34
|
|
|
35
35
|
- `cook` (L1): when the feature being built IS a new skill
|
|
36
36
|
|
|
37
|
+
## References
|
|
38
|
+
|
|
39
|
+
- `references/claude-skill-reference.md` — Claude Code skill system: frontmatter fields, variables, shell injection, invocation control matrix, skill type patterns (task/research/knowledge/dynamic), file structure, and quality checklist. Load when creating or editing any skill.
|
|
40
|
+
|
|
37
41
|
## Workflow
|
|
38
42
|
|
|
39
43
|
### Phase 1 — DISCOVER
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Claude Code Skill System Reference
|
|
2
|
+
|
|
3
|
+
> Loaded by `skill-forge` when creating or editing skills. Covers frontmatter fields, variables, shell injection, invocation control, and skill type patterns.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Frontmatter Fields
|
|
8
|
+
|
|
9
|
+
### Required
|
|
10
|
+
|
|
11
|
+
| Field | Type | Description |
|
|
12
|
+
|-------|------|-------------|
|
|
13
|
+
| `name` | string | Kebab-case identifier. Becomes the `/slash-command`. Must match directory name. |
|
|
14
|
+
| `description` | string | What the skill does. Claude uses this for auto-activation matching. Include action verbs and trigger phrases. |
|
|
15
|
+
|
|
16
|
+
### Optional
|
|
17
|
+
|
|
18
|
+
| Field | Type | Default | Description |
|
|
19
|
+
|-------|------|---------|-------------|
|
|
20
|
+
| `argument-hint` | string | — | Shown in autocomplete after command name. Use `[brackets]`. |
|
|
21
|
+
| `user-invocable` | boolean | `true` | If `false`, no slash command — auto-activate only. |
|
|
22
|
+
| `auto-activate` | boolean | `true` | If `false`, slash command only — never auto-activates. |
|
|
23
|
+
| `allowed-tools` | string[] | all | Whitelist of tools the skill can use. |
|
|
24
|
+
| `disallowed-tools` | string[] | none | Blacklist of tools. |
|
|
25
|
+
|
|
26
|
+
### Invocation Control Matrix
|
|
27
|
+
|
|
28
|
+
| `user-invocable` | `auto-activate` | Behavior |
|
|
29
|
+
|-------------------|-----------------|----------|
|
|
30
|
+
| `true` (default) | `true` (default) | Full access: slash command + auto-activates |
|
|
31
|
+
| `true` | `false` | Slash command only, never auto-activates |
|
|
32
|
+
| `false` | `true` | Auto-activate only, no slash command |
|
|
33
|
+
| `false` | `false` | Never runs — avoid this combination |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Variables
|
|
38
|
+
|
|
39
|
+
Replaced at invocation time before Claude sees the content.
|
|
40
|
+
|
|
41
|
+
| Variable | Description |
|
|
42
|
+
|----------|-------------|
|
|
43
|
+
| `$ARGUMENTS` | Everything after `/command`. Empty if no arguments. |
|
|
44
|
+
| `$0` | Alias for `$ARGUMENTS` |
|
|
45
|
+
| `${CLAUDE_SKILL_DIR}` | Absolute path to skill directory. Use for referencing supporting files. |
|
|
46
|
+
| `${CLAUDE_SESSION_ID}` | Unique ID for current Claude Code session. |
|
|
47
|
+
|
|
48
|
+
**Usage in SKILL.md:**
|
|
49
|
+
```markdown
|
|
50
|
+
The user wants: $ARGUMENTS
|
|
51
|
+
Read `${CLAUDE_SKILL_DIR}/reference.md` for details.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Shell Injection
|
|
57
|
+
|
|
58
|
+
Embed live command output using `` !`command` `` syntax:
|
|
59
|
+
|
|
60
|
+
```markdown
|
|
61
|
+
Current branch: !`git branch --show-current`
|
|
62
|
+
Node version: !`node --version`
|
|
63
|
+
Changed files: !`git diff main --name-only`
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**How it works:**
|
|
67
|
+
- Commands execute when skill activates (not at definition time)
|
|
68
|
+
- Output replaces the `` !`command` `` inline
|
|
69
|
+
- Commands run in current working directory
|
|
70
|
+
- Keep commands fast — slow commands delay skill activation
|
|
71
|
+
- Don't use for side effects — skill content is for context, not execution
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Skill Type Patterns
|
|
76
|
+
|
|
77
|
+
### Task Skill (side effects)
|
|
78
|
+
|
|
79
|
+
Performs actions — deploy, commit, publish. Use `auto-activate: false` for destructive operations.
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
---
|
|
83
|
+
name: deploy
|
|
84
|
+
description: Deploy application to production or staging. Use when user wants to deploy, ship, release.
|
|
85
|
+
argument-hint: [environment: staging|production]
|
|
86
|
+
auto-activate: false
|
|
87
|
+
---
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Research Skill (information gathering)
|
|
91
|
+
|
|
92
|
+
Gathers and synthesizes information. Uses Agent subagents for parallel research.
|
|
93
|
+
|
|
94
|
+
```yaml
|
|
95
|
+
---
|
|
96
|
+
name: deep-research
|
|
97
|
+
description: Perform deep research on a topic using web search and analysis.
|
|
98
|
+
argument-hint: [topic or question to research]
|
|
99
|
+
---
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Knowledge Skill (pure reference)
|
|
103
|
+
|
|
104
|
+
Provides reference context. Auto-activates when relevant, no slash command needed.
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
---
|
|
108
|
+
name: api-conventions
|
|
109
|
+
description: API design conventions for this project. Use when writing API endpoints or reviewing API code.
|
|
110
|
+
user-invocable: false
|
|
111
|
+
---
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Dynamic Context Skill (live state)
|
|
115
|
+
|
|
116
|
+
Uses shell injection to inject current project state.
|
|
117
|
+
|
|
118
|
+
```markdown
|
|
119
|
+
Branch: !`git branch --show-current`
|
|
120
|
+
Changed files: !`git diff main --name-only`
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## File Structure & Loading
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
skills/my-skill/
|
|
129
|
+
├── SKILL.md # Required — loaded on activation
|
|
130
|
+
├── reference.md # Optional — read on-demand via ${CLAUDE_SKILL_DIR}
|
|
131
|
+
├── examples.md # Optional — read on-demand
|
|
132
|
+
└── scripts/ # Optional — bundled scripts, never auto-loaded
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Loading behavior:**
|
|
136
|
+
- **SKILL.md**: Always loaded when skill activates
|
|
137
|
+
- **Supporting `.md` files**: NOT auto-loaded. Only read when SKILL.md references them via `${CLAUDE_SKILL_DIR}`
|
|
138
|
+
- **Scripts**: Available on disk, never auto-loaded
|
|
139
|
+
|
|
140
|
+
This means: keep SKILL.md focused (under 300 lines), put detailed references in separate files that load lazily.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Skill Quality Checklist
|
|
145
|
+
|
|
146
|
+
1. **Name**: kebab-case, matches directory name
|
|
147
|
+
2. **Description**: includes trigger phrases Claude can match against
|
|
148
|
+
3. **SKILL.md under 300 lines**: move details to reference files
|
|
149
|
+
4. **Supporting files referenced via `${CLAUDE_SKILL_DIR}`**: never hardcode paths
|
|
150
|
+
5. **`$ARGUMENTS` used for user input**: contains everything after slash command
|
|
151
|
+
6. **One skill per concern**: don't bundle unrelated functionality
|
|
152
|
+
7. **Templates are minimal**: show patterns, not complete applications
|
|
153
|
+
8. **Tool restrictions only when safety-critical**: don't over-constrain
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Anti-Patterns
|
|
158
|
+
|
|
159
|
+
| Don't | Do Instead |
|
|
160
|
+
|-------|-----------|
|
|
161
|
+
| Giant monolith SKILL.md (>300 lines) | Split into supporting reference files |
|
|
162
|
+
| Vague description ("Helps with stuff") | Action-oriented with trigger phrases |
|
|
163
|
+
| Hardcoded paths to supporting files | Use `${CLAUDE_SKILL_DIR}` |
|
|
164
|
+
| Over-engineering frontmatter | Most skills only need name + description |
|
|
165
|
+
| Duplicating built-in Claude behavior | Only create skills for project-specific patterns |
|
|
166
|
+
| Missing argument-hint | Users won't know what to type after `/command` |
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Rune-Specific Conventions
|
|
171
|
+
|
|
172
|
+
When creating Rune skills (vs generic Claude Code skills):
|
|
173
|
+
|
|
174
|
+
| Field | Rune Convention |
|
|
175
|
+
|-------|----------------|
|
|
176
|
+
| Frontmatter | Add `metadata:` block with author, version, layer, model, group, tools |
|
|
177
|
+
| Layer | L0 (router), L1 (orchestrator), L2 (workflow), L3 (utility), L4 (extension pack) |
|
|
178
|
+
| Connections | Document `Calls` (outbound) and `Called By` (inbound) sections |
|
|
179
|
+
| Model | haiku (scan), sonnet (code), opus (architecture) |
|
|
180
|
+
| Version | semver — bump on enrichment |
|
|
181
|
+
| References | Store in `references/` subdirectory |
|
|
182
|
+
| Cross-references | Use `rune:skill-name` syntax |
|