@rune-kit/rune 2.2.0 → 2.2.2

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.
Files changed (199) hide show
  1. package/README.md +72 -40
  2. package/compiler/__tests__/pack-split.test.js +145 -0
  3. package/compiler/bin/rune.js +26 -9
  4. package/compiler/doctor.js +42 -0
  5. package/compiler/emitter.js +27 -4
  6. package/compiler/parser.js +41 -3
  7. package/compiler/transformer.js +10 -6
  8. package/compiler/transforms/compliance.js +40 -0
  9. package/docs/ANTIGRAVITY-GAP-ANALYSIS.md +369 -0
  10. package/docs/ARCHITECTURE.md +332 -0
  11. package/docs/COMMUNITY-PACKS.md +109 -0
  12. package/docs/CONTRIBUTING-L4.md +215 -0
  13. package/docs/CROSS-IDE-ANALYSIS.md +164 -0
  14. package/docs/EXTENSION-TEMPLATE.md +108 -0
  15. package/docs/MESH-RULES.md +34 -0
  16. package/docs/MULTI-PLATFORM.md +804 -0
  17. package/docs/SKILL-DEPTH-AUDIT.md +191 -0
  18. package/docs/SKILL-TEMPLATE.md +72 -0
  19. package/docs/TRADE-MATRIX.md +327 -0
  20. package/docs/VERSIONING.md +91 -0
  21. package/docs/VISION.md +263 -0
  22. package/docs/assets/demo-subtitles.srt +215 -0
  23. package/docs/assets/end-card.html +276 -0
  24. package/docs/assets/mesh-diagram.html +654 -0
  25. package/docs/assets/thumbnail.html +295 -0
  26. package/docs/guides/cli.md +403 -0
  27. package/docs/guides/index.html +1346 -0
  28. package/docs/index.html +674 -0
  29. package/docs/references/claudekit-analysis.md +414 -0
  30. package/docs/references/voltagent-analysis.md +189 -0
  31. package/docs/script.js +277 -0
  32. package/docs/skills/index.html +832 -0
  33. package/docs/style.css +583 -0
  34. package/docs/video-demo-plan.md +172 -0
  35. package/extensions/ai-ml/PACK.md +38 -474
  36. package/extensions/ai-ml/skills/ai-agents.md +172 -0
  37. package/extensions/ai-ml/skills/code-sandbox.md +187 -0
  38. package/extensions/ai-ml/skills/deep-research.md +146 -0
  39. package/extensions/ai-ml/skills/embedding-search.md +66 -0
  40. package/extensions/ai-ml/skills/fine-tuning-guide.md +74 -0
  41. package/extensions/ai-ml/skills/llm-architect.md +125 -0
  42. package/extensions/ai-ml/skills/llm-integration.md +64 -0
  43. package/extensions/ai-ml/skills/prompt-patterns.md +72 -0
  44. package/extensions/ai-ml/skills/rag-patterns.md +66 -0
  45. package/extensions/ai-ml/skills/web-extraction.md +114 -0
  46. package/extensions/analytics/PACK.md +19 -484
  47. package/extensions/analytics/skills/ab-testing.md +72 -0
  48. package/extensions/analytics/skills/dashboard-patterns.md +83 -0
  49. package/extensions/analytics/skills/data-validation.md +68 -0
  50. package/extensions/analytics/skills/funnel-analysis.md +81 -0
  51. package/extensions/analytics/skills/sql-patterns.md +57 -0
  52. package/extensions/analytics/skills/statistical-analysis.md +79 -0
  53. package/extensions/analytics/skills/tracking-setup.md +71 -0
  54. package/extensions/backend/PACK.md +44 -618
  55. package/extensions/backend/skills/api-patterns.md +84 -0
  56. package/extensions/backend/skills/async-pipeline.md +193 -0
  57. package/extensions/backend/skills/auth-patterns.md +97 -0
  58. package/extensions/backend/skills/background-jobs.md +133 -0
  59. package/extensions/backend/skills/caching-patterns.md +108 -0
  60. package/extensions/backend/skills/cli-generation.md +133 -0
  61. package/extensions/backend/skills/database-patterns.md +87 -0
  62. package/extensions/backend/skills/middleware-patterns.md +104 -0
  63. package/extensions/chrome-ext/PACK.md +19 -921
  64. package/extensions/chrome-ext/skills/cws-preflight.md +143 -0
  65. package/extensions/chrome-ext/skills/cws-publish.md +104 -0
  66. package/extensions/chrome-ext/skills/ext-ai-integration.md +251 -0
  67. package/extensions/chrome-ext/skills/ext-messaging.md +139 -0
  68. package/extensions/chrome-ext/skills/ext-storage.md +133 -0
  69. package/extensions/chrome-ext/skills/mv3-scaffold.md +164 -0
  70. package/extensions/content/PACK.md +43 -335
  71. package/extensions/content/skills/blog-patterns.md +88 -0
  72. package/extensions/content/skills/cms-integration.md +131 -0
  73. package/extensions/content/skills/content-scoring.md +107 -0
  74. package/extensions/content/skills/i18n.md +83 -0
  75. package/extensions/content/skills/mdx-authoring.md +137 -0
  76. package/extensions/content/skills/reference.md +1014 -0
  77. package/extensions/content/skills/seo-patterns.md +67 -0
  78. package/extensions/content/skills/video-repurpose.md +153 -0
  79. package/extensions/devops/PACK.md +38 -457
  80. package/extensions/devops/skills/chaos-testing.md +67 -0
  81. package/extensions/devops/skills/ci-cd.md +75 -0
  82. package/extensions/devops/skills/docker.md +58 -0
  83. package/extensions/devops/skills/edge-serverless.md +163 -0
  84. package/extensions/devops/skills/infra-as-code.md +158 -0
  85. package/extensions/devops/skills/kubernetes.md +110 -0
  86. package/extensions/devops/skills/monitoring.md +57 -0
  87. package/extensions/devops/skills/server-setup.md +64 -0
  88. package/extensions/devops/skills/ssl-domain.md +42 -0
  89. package/extensions/ecommerce/PACK.md +62 -226
  90. package/extensions/ecommerce/skills/cart-system.md +79 -0
  91. package/extensions/ecommerce/skills/inventory-mgmt.md +102 -0
  92. package/extensions/ecommerce/skills/order-management.md +126 -0
  93. package/extensions/ecommerce/skills/payment-integration.md +472 -0
  94. package/extensions/ecommerce/skills/shopify-dev.md +69 -0
  95. package/extensions/ecommerce/skills/subscription-billing.md +93 -0
  96. package/extensions/ecommerce/skills/tax-compliance.md +117 -0
  97. package/extensions/gamedev/PACK.md +66 -317
  98. package/extensions/gamedev/skills/asset-pipeline.md +74 -0
  99. package/extensions/gamedev/skills/audio-system.md +129 -0
  100. package/extensions/gamedev/skills/camera-system.md +87 -0
  101. package/extensions/gamedev/skills/ecs.md +98 -0
  102. package/extensions/gamedev/skills/game-loops.md +72 -0
  103. package/extensions/gamedev/skills/input-system.md +199 -0
  104. package/extensions/gamedev/skills/multiplayer.md +180 -0
  105. package/extensions/gamedev/skills/particles.md +105 -0
  106. package/extensions/gamedev/skills/physics-engine.md +89 -0
  107. package/extensions/gamedev/skills/scene-management.md +146 -0
  108. package/extensions/gamedev/skills/threejs-patterns.md +90 -0
  109. package/extensions/gamedev/skills/webgl.md +71 -0
  110. package/extensions/mobile/PACK.md +56 -223
  111. package/extensions/mobile/skills/app-store-connect.md +152 -0
  112. package/extensions/mobile/skills/app-store-prep.md +66 -0
  113. package/extensions/mobile/skills/deep-linking.md +109 -0
  114. package/extensions/mobile/skills/flutter.md +60 -0
  115. package/extensions/mobile/skills/ios-build-pipeline.md +142 -0
  116. package/extensions/mobile/skills/native-bridge.md +66 -0
  117. package/extensions/mobile/skills/ota-updates.md +97 -0
  118. package/extensions/mobile/skills/push-notifications.md +111 -0
  119. package/extensions/mobile/skills/react-native.md +82 -0
  120. package/extensions/saas/PACK.md +26 -720
  121. package/extensions/saas/skills/billing-integration.md +121 -0
  122. package/extensions/saas/skills/feature-flags.md +130 -0
  123. package/extensions/saas/skills/multi-tenant.md +103 -0
  124. package/extensions/saas/skills/onboarding-flow.md +139 -0
  125. package/extensions/saas/skills/subscription-flow.md +95 -0
  126. package/extensions/saas/skills/team-management.md +144 -0
  127. package/extensions/security/PACK.md +10 -448
  128. package/extensions/security/skills/api-security.md +140 -0
  129. package/extensions/security/skills/compliance.md +68 -0
  130. package/extensions/security/skills/owasp-audit.md +64 -0
  131. package/extensions/security/skills/pentest-patterns.md +77 -0
  132. package/extensions/security/skills/secret-mgmt.md +65 -0
  133. package/extensions/security/skills/supply-chain.md +65 -0
  134. package/extensions/trading/PACK.md +18 -535
  135. package/extensions/trading/skills/chart-components.md +55 -0
  136. package/extensions/trading/skills/experiment-loop.md +125 -0
  137. package/extensions/trading/skills/fintech-patterns.md +47 -0
  138. package/extensions/trading/skills/indicator-library.md +58 -0
  139. package/extensions/trading/skills/quant-analysis.md +111 -0
  140. package/extensions/trading/skills/realtime-data.md +58 -0
  141. package/extensions/trading/skills/trade-logic.md +104 -0
  142. package/extensions/ui/PACK.md +36 -853
  143. package/extensions/ui/skills/a11y-audit.md +91 -0
  144. package/extensions/ui/skills/animation-patterns.md +106 -0
  145. package/extensions/ui/skills/component-patterns.md +75 -0
  146. package/extensions/ui/skills/design-decision.md +108 -0
  147. package/extensions/ui/skills/design-system.md +68 -0
  148. package/extensions/ui/skills/landing-patterns.md +155 -0
  149. package/extensions/ui/skills/palette-picker.md +173 -0
  150. package/extensions/ui/skills/react-health.md +90 -0
  151. package/extensions/ui/skills/type-system.md +125 -0
  152. package/extensions/ui/skills/web-vitals.md +153 -0
  153. package/extensions/zalo/PACK.md +117 -0
  154. package/extensions/zalo/skills/zalo-oa-mcp.md +317 -0
  155. package/extensions/zalo/skills/zalo-oa-messaging.md +429 -0
  156. package/extensions/zalo/skills/zalo-oa-setup.md +236 -0
  157. package/extensions/zalo/skills/zalo-oa-webhook.md +189 -0
  158. package/extensions/zalo/skills/zalo-personal-messaging.md +194 -0
  159. package/extensions/zalo/skills/zalo-personal-setup.md +153 -0
  160. package/extensions/zalo/skills/zalo-rate-guard.md +219 -0
  161. package/hooks/.gitkeep +0 -0
  162. package/hooks/auto-format/index.cjs +48 -0
  163. package/hooks/context-watch/index.cjs +68 -0
  164. package/hooks/hooks.json +99 -0
  165. package/hooks/metrics-collector/index.cjs +42 -0
  166. package/hooks/post-session-reflect/index.cjs +153 -0
  167. package/hooks/pre-compact/index.cjs +95 -0
  168. package/hooks/pre-tool-guard/index.cjs +68 -0
  169. package/hooks/run-hook +17 -0
  170. package/hooks/run-hook.cjs +16 -0
  171. package/hooks/run-hook.cmd +1 -0
  172. package/hooks/secrets-scan/index.cjs +100 -0
  173. package/hooks/session-start/index.cjs +65 -0
  174. package/hooks/typecheck/index.cjs +65 -0
  175. package/package.json +9 -4
  176. package/references/ui-pro-max-data/LICENSE-UI-PRO-MAX +21 -0
  177. package/references/ui-pro-max-data/charts.csv +26 -0
  178. package/references/ui-pro-max-data/colors.csv +162 -0
  179. package/references/ui-pro-max-data/styles.csv +85 -0
  180. package/references/ui-pro-max-data/typography.csv +74 -0
  181. package/references/ui-pro-max-data/ui-reasoning.csv +162 -0
  182. package/references/ui-pro-max-data/ux-guidelines.csv +100 -0
  183. package/skills/ba/SKILL.md +10 -0
  184. package/skills/brainstorm/SKILL.md +63 -1
  185. package/skills/completion-gate/SKILL.md +34 -1
  186. package/skills/context-engine/SKILL.md +13 -0
  187. package/skills/cook/SKILL.md +155 -5
  188. package/skills/debug/SKILL.md +56 -1
  189. package/skills/design/SKILL.md +11 -0
  190. package/skills/fix/SKILL.md +26 -1
  191. package/skills/mcp-builder/SKILL.md +48 -1
  192. package/skills/plan/SKILL.md +23 -6
  193. package/skills/review/SKILL.md +44 -5
  194. package/skills/review-intake/SKILL.md +17 -1
  195. package/skills/skill-forge/SKILL.md +38 -3
  196. package/skills/skill-router/SKILL.md +89 -7
  197. package/skills/team/SKILL.md +24 -1
  198. package/skills/test/SKILL.md +28 -1
  199. package/skills/verification/SKILL.md +98 -1
@@ -0,0 +1,219 @@
1
+ ---
2
+ name: zalo-rate-guard
3
+ pack: "@rune/zalo"
4
+ description: Rate limiting patterns for Zalo OA and personal APIs — token bucket per endpoint, exponential backoff, queue management, quota monitoring, anti-ban strategies.
5
+ model: sonnet
6
+ tools: "Read, Glob, Grep, Bash, Write, Edit"
7
+ ---
8
+
9
+ # zalo-rate-guard
10
+
11
+ Shared rate limiting layer for both Track A (OA API) and Track B (Personal via zca-js).
12
+ Zalo has **undocumented rate limits** — no official RPM/QPM numbers published.
13
+ Exceeding limits: throttled (429) → warned → OA suspended / account banned.
14
+ Neither `zalo-php-sdk`, `zalo-java-sdk`, nor `zca-js` implement any rate limiting.
15
+ This skill fills that gap.
16
+
17
+ ---
18
+
19
+ ## Estimated Safe Limits
20
+
21
+ **Track A — OA API:**
22
+
23
+ | Endpoint | Safe RPM | Burst | Notes |
24
+ |----------|----------|-------|-------|
25
+ | Send CS message | 200 | 10 | Per OA, includes all message types |
26
+ | Send broadcast | 50 | 5 | Monthly quota based on follower count |
27
+ | Get user profile | 300 | 20 | Cacheable — use name cache |
28
+ | Get follower list | 100 | 10 | Paginated, cache results |
29
+ | Upload media | 60 | 5 | Large payloads, slower |
30
+ | Global | 500 | 30 | Total across all endpoints |
31
+
32
+ **Track B — Personal (zca-js):**
33
+
34
+ | Action | Safe RPM | Burst | Notes |
35
+ |--------|----------|-------|-------|
36
+ | Send message (DM) | 30 | 5 | Much lower than OA — personal account |
37
+ | Send message (group) | 20 | 3 | Groups are more scrutinized |
38
+ | Friend operations | 10 | 2 | Add/remove friend is very sensitive |
39
+ | Profile lookups | 60 | 10 | Less sensitive, still cache |
40
+ | Global | 100 | 15 | Err on side of caution |
41
+
42
+ ---
43
+
44
+ ## Token Bucket Implementation
45
+
46
+ ```typescript
47
+ import PQueue from 'p-queue'
48
+
49
+ interface RateLimitConfig {
50
+ rpm: number // requests per minute
51
+ burst: number // max concurrent
52
+ retryAfter: number // ms to wait on 429
53
+ }
54
+
55
+ const LIMITS: Record<string, RateLimitConfig> = {
56
+ 'oa:send_message': { rpm: 200, burst: 10, retryAfter: 5000 },
57
+ 'oa:broadcast': { rpm: 50, burst: 5, retryAfter: 10000 },
58
+ 'oa:get_profile': { rpm: 300, burst: 20, retryAfter: 3000 },
59
+ 'oa:upload': { rpm: 60, burst: 5, retryAfter: 5000 },
60
+ 'personal:send_dm': { rpm: 30, burst: 5, retryAfter: 10000 },
61
+ 'personal:send_grp': { rpm: 20, burst: 3, retryAfter: 15000 },
62
+ 'personal:friend': { rpm: 10, burst: 2, retryAfter: 30000 },
63
+ }
64
+
65
+ export class ZaloRateLimiter {
66
+ private queues = new Map<string, PQueue>()
67
+
68
+ constructor() {
69
+ for (const [key, config] of Object.entries(LIMITS)) {
70
+ this.queues.set(key, new PQueue({
71
+ concurrency: config.burst,
72
+ intervalCap: config.rpm,
73
+ interval: 60_000, // per minute window
74
+ }))
75
+ }
76
+ }
77
+
78
+ async execute<T>(endpoint: string, fn: () => Promise<T>): Promise<T> {
79
+ const queue = this.queues.get(endpoint)
80
+ if (!queue) throw new Error(`Unknown endpoint: ${endpoint}`)
81
+ return queue.add(fn) as Promise<T>
82
+ }
83
+
84
+ queueSize(endpoint: string): number {
85
+ return this.queues.get(endpoint)?.size ?? 0
86
+ }
87
+
88
+ pending(endpoint: string): number {
89
+ return this.queues.get(endpoint)?.pending ?? 0
90
+ }
91
+ }
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Exponential Backoff on 429
97
+
98
+ ```typescript
99
+ export async function withBackoff<T>(
100
+ fn: () => Promise<T>,
101
+ maxRetries = 3,
102
+ baseDelay = 1000
103
+ ): Promise<T> {
104
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
105
+ try {
106
+ return await fn()
107
+ } catch (error: any) {
108
+ const isRateLimit = error?.status === 429 || error?.error_code === 429
109
+ if (!isRateLimit || attempt === maxRetries) throw error
110
+ const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 1000
111
+ console.warn(`[zalo-rate-guard] Rate limited. Retry ${attempt + 1}/${maxRetries} in ${Math.round(delay)}ms`)
112
+ await new Promise(r => setTimeout(r, delay))
113
+ }
114
+ }
115
+ throw new Error('Unreachable')
116
+ }
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Quota Monitoring (OA Broadcast)
122
+
123
+ Broadcast quota is a **hard monthly limit** — exceeding it silently drops messages, no error returned.
124
+
125
+ ```typescript
126
+ interface QuotaTracker {
127
+ monthly_limit: number // based on follower count + OA level
128
+ used: number
129
+ resets_at: Date // 1st of each month
130
+ }
131
+
132
+ export function canBroadcast(tracker: QuotaTracker, recipientCount: number): boolean {
133
+ const remaining = tracker.monthly_limit - tracker.used
134
+ if (recipientCount > remaining) {
135
+ console.error(
136
+ `[zalo-rate-guard] Broadcast quota insufficient: need ${recipientCount}, have ${remaining}/${tracker.monthly_limit}`
137
+ )
138
+ return false
139
+ }
140
+ return true
141
+ }
142
+
143
+ export function trackBroadcastUsed(tracker: QuotaTracker, sent: number): QuotaTracker {
144
+ return { ...tracker, used: tracker.used + sent }
145
+ }
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Integration Pattern
151
+
152
+ ```typescript
153
+ // Singleton — shared across the app
154
+ export const limiter = new ZaloRateLimiter()
155
+
156
+ // Track A: OA message send with rate limiting
157
+ export async function sendOaMessage(userId: string, text: string) {
158
+ return limiter.execute('oa:send_message', () =>
159
+ withBackoff(() =>
160
+ oaApiCall('/message/cs', {
161
+ recipient: { user_id: userId },
162
+ message: { text },
163
+ })
164
+ )
165
+ )
166
+ }
167
+
168
+ // Track B: Personal DM with rate limiting + human jitter
169
+ export async function sendPersonalMessage(threadId: string, text: string) {
170
+ const jitter = 500 + Math.random() * 1500 // 500–2000ms
171
+ await new Promise(r => setTimeout(r, jitter))
172
+ return limiter.execute('personal:send_dm', () =>
173
+ withBackoff(() => api.sendMessage(text, threadId, 'User'))
174
+ )
175
+ }
176
+
177
+ // Track B: Friend operation — highest-risk, extra jitter
178
+ export async function addFriend(userId: string) {
179
+ const jitter = 2000 + Math.random() * 3000 // 2–5s
180
+ await new Promise(r => setTimeout(r, jitter))
181
+ return limiter.execute('personal:friend', () =>
182
+ withBackoff(() => api.sendFriendRequest(userId), 2, 5000)
183
+ )
184
+ }
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Anti-Ban Strategies
190
+
191
+ **Track A (OA):**
192
+ 1. Stay under safe RPM limits (table above)
193
+ 2. Exponential backoff on ALL 429 responses — never retry immediately
194
+ 3. Cache user profiles — avoid repeated lookups for the same user
195
+ 4. Spread broadcasts over time — don't burst the entire follower list at once
196
+ 5. Monitor quota before each broadcast batch — stop before hitting monthly limit
197
+ 6. Use `appsecret_proof` on all requests — proves you're the legitimate app owner
198
+
199
+ **Track B (Personal):**
200
+ 1. Much lower limits than OA — personal accounts are watched more closely
201
+ 2. Add human-like jitter: 500–2000ms random delay between messages (not optional)
202
+ 3. Avoid 3–6 AM (VN timezone) — traffic at those hours flags automated activity
203
+ 4. Never change profile info programmatically — triggers manual review
204
+ 5. Friend operations are highest-risk — max 10 RPM, prefer lower in practice
205
+ 6. Keep sessions long-lived — repeated login/logout is a strong ban signal
206
+ 7. Use a consistent device fingerprint (`userAgent` + `IMEI`) per account
207
+ 8. On `DuplicateConnection` (error 3000): wait 30s before reconnecting, never spam reconnects
208
+
209
+ ---
210
+
211
+ ## Sharp Edges
212
+
213
+ - Rate limits are **estimated** — Zalo does not publish official numbers; treat all figures as conservative targets
214
+ - `p-queue` `intervalCap` applies per window, not per request — test behavior under burst
215
+ - 429 without backoff = accelerating toward ban, not slowing down
216
+ - Broadcast quota overflow **silently drops messages** — no 429, no error, just lost sends
217
+ - Stale cached profile data is acceptable; hitting rate limits for fresh data is not
218
+ - Personal account friend operations are the single highest-risk action — handle with care
219
+ - Human jitter for personal track is a survival strategy, not a nice-to-have
package/hooks/.gitkeep ADDED
File without changes
@@ -0,0 +1,48 @@
1
+ // Rune Auto-Format Hook
2
+ // PostToolUse hook on Edit/Write — runs Prettier on JS/TS files after modification
3
+ //
4
+ // Only runs if Prettier is available in the project.
5
+ // Silent pass-through if not applicable.
6
+
7
+ const { execSync } = require('child_process');
8
+ const path = require('path');
9
+
10
+ const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}');
11
+ const filePath = input.file_path || input.filePath || '';
12
+
13
+ // Only format JS/TS/JSON/CSS files
14
+ if (!/\.(js|jsx|ts|tsx|json|css|scss|md|html|yaml|yml)$/i.test(filePath)) {
15
+ process.exit(0);
16
+ }
17
+
18
+ // Check if file exists and is within a project with Prettier
19
+ const dir = path.dirname(filePath);
20
+
21
+ // Try to find prettier in the project
22
+ let hasPrettier = false;
23
+ try {
24
+ execSync('npx prettier --version', { cwd: dir, encoding: 'utf-8', stdio: 'pipe', timeout: 5000 });
25
+ hasPrettier = true;
26
+ } catch {
27
+ // No Prettier available — skip silently
28
+ }
29
+
30
+ if (!hasPrettier) {
31
+ process.exit(0);
32
+ }
33
+
34
+ // Run Prettier on the file
35
+ try {
36
+ execSync(`npx prettier --write "${filePath}"`, {
37
+ cwd: dir,
38
+ encoding: 'utf-8',
39
+ stdio: 'pipe',
40
+ timeout: 10000,
41
+ });
42
+ // Silent success — formatted files are seamless
43
+ } catch (e) {
44
+ // Prettier failed — non-critical, just warn
45
+ console.log(`[Rune auto-format] Prettier failed on ${path.basename(filePath)}: ${e.message.split('\n')[0]}`);
46
+ }
47
+
48
+ process.exit(0);
@@ -0,0 +1,68 @@
1
+ // Rune Context Watch Hook
2
+ // Lightweight tool call counter — detects context pressure and suggests rune:context-engine
3
+ // Runs as PreToolUse hook on Edit/Write (high-cost operations)
4
+ //
5
+ // H3 Intelligence: also tracks tool type distribution and session start timestamp
6
+ // for metrics aggregation at session end.
7
+ //
8
+ // Uses a temp file counter (survives across hook invocations within same session).
9
+ // Zero overhead: just reads/increments a number. No token cost.
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const os = require('os');
14
+
15
+ // Counter file scoped to current working directory (hash of cwd)
16
+ const cwd = process.cwd();
17
+ const hash = Buffer.from(cwd).toString('base64url').slice(0, 16);
18
+ const counterFile = path.join(os.tmpdir(), `rune-context-watch-${hash}.json`);
19
+
20
+ // Thresholds
21
+ const FIRST_WARNING = 40;
22
+ const REPEAT_INTERVAL = 20;
23
+ const CRITICAL_THRESHOLD = 80;
24
+
25
+ // Detect tool type from environment
26
+ const toolName = process.env.CLAUDE_TOOL_NAME || 'unknown';
27
+
28
+ // Read current state
29
+ let state = { count: 0, lastWarning: 0, sessionStart: null, toolCounts: {} };
30
+ try {
31
+ const raw = fs.readFileSync(counterFile, 'utf-8');
32
+ state = JSON.parse(raw);
33
+ // Ensure toolCounts exists (upgrade from old format)
34
+ if (!state.toolCounts) state.toolCounts = {};
35
+ if (!state.sessionStart) state.sessionStart = new Date().toISOString();
36
+ } catch {
37
+ // First run or corrupted — start fresh
38
+ state.sessionStart = new Date().toISOString();
39
+ }
40
+
41
+ // Increment total and per-tool counters
42
+ state.count += 1;
43
+ state.toolCounts[toolName] = (state.toolCounts[toolName] || 0) + 1;
44
+
45
+ // Check thresholds
46
+ const count = state.count;
47
+ const sinceLast = count - state.lastWarning;
48
+
49
+ if (count >= CRITICAL_THRESHOLD && sinceLast >= REPEAT_INTERVAL) {
50
+ console.log(`\nšŸ”“ [Rune context-watch] ${count} tool calls — context likely RED (>85%).`);
51
+ console.log(' RECOMMENDED: Invoke rune:context-engine for state save + /compact.');
52
+ console.log(' Risk: auto-compaction may lose critical decisions without state save.\n');
53
+ state.lastWarning = count;
54
+ } else if (count >= FIRST_WARNING && sinceLast >= REPEAT_INTERVAL) {
55
+ console.log(`\n🟔 [Rune context-watch] ${count} tool calls — context filling up.`);
56
+ console.log(' Consider invoking rune:context-engine at the next logical boundary.');
57
+ console.log(' Or run /compact manually if at a good stopping point.\n');
58
+ state.lastWarning = count;
59
+ }
60
+
61
+ // Persist
62
+ try {
63
+ fs.writeFileSync(counterFile, JSON.stringify(state));
64
+ } catch {
65
+ // Non-critical — counter resets next run
66
+ }
67
+
68
+ process.exit(0);
@@ -0,0 +1,99 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "startup|resume|clear|compact",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cjs\" session-start",
10
+ "async": false
11
+ }
12
+ ]
13
+ }
14
+ ],
15
+ "PreToolUse": [
16
+ {
17
+ "matcher": "Read|Write|Edit",
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cjs\" pre-tool-guard",
22
+ "async": false
23
+ }
24
+ ]
25
+ },
26
+ {
27
+ "matcher": "Edit|Write",
28
+ "hooks": [
29
+ {
30
+ "type": "command",
31
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cjs\" context-watch",
32
+ "async": true
33
+ }
34
+ ]
35
+ },
36
+ {
37
+ "matcher": "Bash",
38
+ "hooks": [
39
+ {
40
+ "type": "command",
41
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cjs\" secrets-scan",
42
+ "async": false
43
+ }
44
+ ]
45
+ }
46
+ ],
47
+ "PostToolUse": [
48
+ {
49
+ "matcher": "Edit|Write",
50
+ "hooks": [
51
+ {
52
+ "type": "command",
53
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cjs\" auto-format",
54
+ "async": true
55
+ },
56
+ {
57
+ "type": "command",
58
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cjs\" typecheck",
59
+ "async": true
60
+ }
61
+ ]
62
+ },
63
+ {
64
+ "matcher": "Skill",
65
+ "hooks": [
66
+ {
67
+ "type": "command",
68
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cjs\" metrics-collector",
69
+ "async": true
70
+ }
71
+ ]
72
+ }
73
+ ],
74
+ "PreCompact": [
75
+ {
76
+ "matcher": ".*",
77
+ "hooks": [
78
+ {
79
+ "type": "command",
80
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cjs\" pre-compact",
81
+ "async": false
82
+ }
83
+ ]
84
+ }
85
+ ],
86
+ "Stop": [
87
+ {
88
+ "matcher": ".*",
89
+ "hooks": [
90
+ {
91
+ "type": "command",
92
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cjs\" post-session-reflect",
93
+ "async": false
94
+ }
95
+ ]
96
+ }
97
+ ]
98
+ }
99
+ }
@@ -0,0 +1,42 @@
1
+ // Rune Metrics Collector Hook
2
+ // PostToolUse on Skill — captures skill invocations for H3 mesh analytics
3
+ // Append-only JSONL to tmpdir. Flushed to .rune/metrics/ at session end.
4
+ // Async: true — never blocks skill execution.
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+
10
+ const cwd = process.cwd();
11
+ const hash = Buffer.from(cwd).toString('base64url').slice(0, 16);
12
+ const metricsFile = path.join(os.tmpdir(), `rune-metrics-${hash}.jsonl`);
13
+
14
+ // Extract skill name from tool input
15
+ const toolInput = process.env.CLAUDE_TOOL_INPUT || '';
16
+ let skillName = 'unknown';
17
+ try {
18
+ const parsed = JSON.parse(toolInput);
19
+ // Skill tool input has { skill: "rune:cook" } or { skill: "cook" }
20
+ const raw = parsed.skill || parsed.name || '';
21
+ skillName = raw.replace(/^rune:/, '');
22
+ } catch {
23
+ // If not JSON, try raw string match
24
+ const match = toolInput.match(/(?:rune:)?([a-z][\w-]*)/i);
25
+ if (match) skillName = match[1];
26
+ }
27
+
28
+ if (skillName && skillName !== 'unknown') {
29
+ const entry = JSON.stringify({
30
+ ts: new Date().toISOString(),
31
+ skill: skillName,
32
+ event: 'invoke'
33
+ });
34
+
35
+ try {
36
+ fs.appendFileSync(metricsFile, entry + '\n');
37
+ } catch {
38
+ // Non-critical — metrics are best-effort
39
+ }
40
+ }
41
+
42
+ process.exit(0);
@@ -0,0 +1,153 @@
1
+ // Rune Post-Session Reflect Hook
2
+ // 1. Flushes session metrics from tmpdir to .rune/metrics/ (H3 Intelligence)
3
+ // 2. Displays structured self-review checklist at session end (Stop event)
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+
9
+ const cwd = process.cwd();
10
+ const hash = Buffer.from(cwd).toString('base64url').slice(0, 16);
11
+
12
+ // === H3: Flush Session Metrics ===
13
+
14
+ const metricsJsonl = path.join(os.tmpdir(), `rune-metrics-${hash}.jsonl`);
15
+ const counterFile = path.join(os.tmpdir(), `rune-context-watch-${hash}.json`);
16
+ const runeMetricsDir = path.join(cwd, '.rune', 'metrics');
17
+
18
+ try {
19
+ flushMetrics();
20
+ } catch (e) {
21
+ // Metrics flush is best-effort — never block session end
22
+ // Silently ignore errors
23
+ }
24
+
25
+ function flushMetrics() {
26
+ // Read session skill invocations from tmpdir JSONL
27
+ let skillEvents = [];
28
+ if (fs.existsSync(metricsJsonl)) {
29
+ const lines = fs.readFileSync(metricsJsonl, 'utf-8').trim().split('\n').filter(Boolean);
30
+ for (const line of lines) {
31
+ try { skillEvents.push(JSON.parse(line)); } catch { /* skip malformed */ }
32
+ }
33
+ }
34
+
35
+ // Read context-watch state for tool counts and session timing
36
+ let watchState = { count: 0, sessionStart: null, toolCounts: {} };
37
+ if (fs.existsSync(counterFile)) {
38
+ try {
39
+ watchState = JSON.parse(fs.readFileSync(counterFile, 'utf-8'));
40
+ } catch { /* use defaults */ }
41
+ }
42
+
43
+ // Nothing to flush if no data
44
+ if (skillEvents.length === 0 && watchState.count === 0) return;
45
+
46
+ // Ensure .rune/metrics/ exists
47
+ fs.mkdirSync(runeMetricsDir, { recursive: true });
48
+
49
+ const now = new Date().toISOString();
50
+ const sessionStart = watchState.sessionStart || now;
51
+ const durationMin = Math.round((new Date(now) - new Date(sessionStart)) / 60000);
52
+
53
+ // Build skill usage map
54
+ const skillCounts = {};
55
+ const skillChain = [];
56
+ for (const evt of skillEvents) {
57
+ skillCounts[evt.skill] = (skillCounts[evt.skill] || 0) + 1;
58
+ skillChain.push(evt.skill);
59
+ }
60
+
61
+ // Determine primary skill (most invoked)
62
+ const primarySkill = Object.entries(skillCounts)
63
+ .sort((a, b) => b[1] - a[1])[0]?.[0] || 'none';
64
+
65
+ // Generate session ID
66
+ const sessionId = `s-${now.slice(0, 10).replace(/-/g, '')}-${now.slice(11, 19).replace(/:/g, '')}`;
67
+
68
+ // 1. Append to sessions.jsonl
69
+ const sessionEntry = {
70
+ id: sessionId,
71
+ date: now.slice(0, 10),
72
+ duration_min: durationMin,
73
+ tool_calls: watchState.count,
74
+ tool_distribution: watchState.toolCounts,
75
+ skill_invocations: skillEvents.length,
76
+ skills_used: Object.keys(skillCounts),
77
+ primary_skill: primarySkill
78
+ };
79
+
80
+ const sessionsFile = path.join(runeMetricsDir, 'sessions.jsonl');
81
+ fs.appendFileSync(sessionsFile, JSON.stringify(sessionEntry) + '\n');
82
+
83
+ // Cap at 100 sessions (rotate oldest)
84
+ try {
85
+ const allLines = fs.readFileSync(sessionsFile, 'utf-8').trim().split('\n').filter(Boolean);
86
+ if (allLines.length > 100) {
87
+ fs.writeFileSync(sessionsFile, allLines.slice(-100).join('\n') + '\n');
88
+ }
89
+ } catch { /* cap is best-effort */ }
90
+
91
+ // 2. Merge into skills.json (running totals)
92
+ const skillsFile = path.join(runeMetricsDir, 'skills.json');
93
+ let skillsData = { version: 1, updated: now, skills: {} };
94
+ if (fs.existsSync(skillsFile)) {
95
+ try {
96
+ skillsData = JSON.parse(fs.readFileSync(skillsFile, 'utf-8'));
97
+ } catch { /* start fresh */ }
98
+ }
99
+
100
+ for (const [skill, count] of Object.entries(skillCounts)) {
101
+ if (!skillsData.skills[skill]) {
102
+ skillsData.skills[skill] = { total_invocations: 0, last_used: now.slice(0, 10) };
103
+ }
104
+ skillsData.skills[skill].total_invocations += count;
105
+ skillsData.skills[skill].last_used = now.slice(0, 10);
106
+ }
107
+ skillsData.updated = now;
108
+
109
+ fs.writeFileSync(skillsFile, JSON.stringify(skillsData, null, 2) + '\n');
110
+
111
+ // 3. Append to chains.jsonl
112
+ if (skillChain.length > 0) {
113
+ const chainsFile = path.join(runeMetricsDir, 'chains.jsonl');
114
+ const chainEntry = {
115
+ session: sessionId,
116
+ chain: skillChain,
117
+ depth: skillChain.length
118
+ };
119
+ fs.appendFileSync(chainsFile, JSON.stringify(chainEntry) + '\n');
120
+ }
121
+
122
+ // 4. Cleanup tmpdir files
123
+ try { fs.unlinkSync(metricsJsonl); } catch { /* already gone */ }
124
+ // Note: counterFile is cleaned by session-start hook on next session
125
+
126
+ // Report metrics flush
127
+ const skillList = Object.entries(skillCounts)
128
+ .sort((a, b) => b[1] - a[1])
129
+ .map(([s, c]) => `${s}(${c})`)
130
+ .join(', ');
131
+
132
+ console.log(`\nšŸ“Š [Rune metrics] Session ${sessionId} — ${durationMin}min, ${watchState.count} tool calls, ${skillEvents.length} skill invocations`);
133
+ if (skillList) console.log(` Skills: ${skillList}`);
134
+ console.log(` Saved to .rune/metrics/\n`);
135
+ }
136
+
137
+ // === Original: Verification Checklist ===
138
+
139
+ console.log(`
140
+ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
141
+ │ Rune Session End — Verification Checklist │
142
+ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
143
+ │ Before closing this session, confirm: │
144
+ │ │
145
+ │ ā–” All TodoWrite tasks marked complete? │
146
+ │ ā–” Tests ran and passing? │
147
+ │ ā–” No hardcoded secrets introduced? │
148
+ │ ā–” If schema changed: migration + rollback exist? │
149
+ │ ā–” Verification ran (lint + types + build)? │
150
+ │ │
151
+ │ If any item is unclear → address it now. │
152
+ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
153
+ `);