@rune-kit/rune 2.10.0 → 2.11.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/LICENSE +21 -21
- package/README.md +8 -6
- package/commands/rune.md +168 -168
- package/contexts/dev.md +34 -34
- package/contexts/research.md +43 -43
- package/contexts/review.md +55 -55
- package/extensions/ai-ml/PACK.md +88 -88
- package/extensions/ai-ml/skills/ai-agents.md +172 -172
- package/extensions/ai-ml/skills/code-sandbox.md +187 -187
- package/extensions/ai-ml/skills/deep-research.md +146 -146
- package/extensions/ai-ml/skills/embedding-search.md +66 -66
- package/extensions/ai-ml/skills/fine-tuning-guide.md +74 -74
- package/extensions/ai-ml/skills/llm-architect.md +125 -125
- package/extensions/ai-ml/skills/llm-integration.md +64 -64
- package/extensions/ai-ml/skills/prompt-patterns.md +72 -72
- package/extensions/ai-ml/skills/rag-patterns.md +66 -66
- package/extensions/ai-ml/skills/web-extraction.md +114 -114
- package/extensions/analytics/PACK.md +92 -92
- package/extensions/analytics/skills/ab-testing.md +72 -72
- package/extensions/analytics/skills/dashboard-patterns.md +83 -83
- package/extensions/analytics/skills/data-validation.md +68 -68
- package/extensions/analytics/skills/funnel-analysis.md +81 -81
- package/extensions/analytics/skills/sql-patterns.md +57 -57
- package/extensions/analytics/skills/statistical-analysis.md +79 -79
- package/extensions/analytics/skills/tracking-setup.md +71 -71
- package/extensions/backend/PACK.md +104 -104
- package/extensions/backend/skills/api-patterns.md +84 -84
- package/extensions/backend/skills/async-pipeline.md +193 -193
- package/extensions/backend/skills/auth-patterns.md +97 -97
- package/extensions/backend/skills/background-jobs.md +133 -133
- package/extensions/backend/skills/caching-patterns.md +108 -108
- package/extensions/backend/skills/cli-generation.md +133 -133
- package/extensions/backend/skills/database-patterns.md +87 -87
- package/extensions/backend/skills/middleware-patterns.md +104 -104
- package/extensions/chrome-ext/PACK.md +93 -93
- package/extensions/chrome-ext/skills/cws-preflight.md +143 -143
- package/extensions/chrome-ext/skills/cws-publish.md +104 -104
- package/extensions/chrome-ext/skills/ext-ai-integration.md +251 -251
- package/extensions/chrome-ext/skills/ext-messaging.md +139 -139
- package/extensions/chrome-ext/skills/ext-storage.md +133 -133
- package/extensions/chrome-ext/skills/mv3-scaffold.md +164 -164
- package/extensions/content/PACK.md +96 -96
- package/extensions/content/skills/blog-patterns.md +88 -88
- package/extensions/content/skills/cms-integration.md +131 -131
- package/extensions/content/skills/content-scoring.md +107 -107
- package/extensions/content/skills/i18n.md +83 -83
- package/extensions/content/skills/mdx-authoring.md +137 -137
- package/extensions/content/skills/reference.md +1014 -1014
- package/extensions/content/skills/seo-patterns.md +67 -67
- package/extensions/content/skills/video-repurpose.md +153 -153
- package/extensions/devops/PACK.md +101 -101
- package/extensions/devops/skills/chaos-testing.md +67 -67
- package/extensions/devops/skills/ci-cd.md +75 -75
- package/extensions/devops/skills/docker.md +58 -58
- package/extensions/devops/skills/edge-serverless.md +163 -163
- package/extensions/devops/skills/infra-as-code.md +158 -158
- package/extensions/devops/skills/kubernetes.md +110 -110
- package/extensions/devops/skills/monitoring.md +57 -57
- package/extensions/devops/skills/server-setup.md +64 -64
- package/extensions/devops/skills/ssl-domain.md +42 -42
- package/extensions/ecommerce/PACK.md +116 -116
- package/extensions/ecommerce/skills/cart-system.md +79 -79
- package/extensions/ecommerce/skills/inventory-mgmt.md +102 -102
- package/extensions/ecommerce/skills/order-management.md +126 -126
- package/extensions/ecommerce/skills/payment-integration.md +472 -472
- package/extensions/ecommerce/skills/shopify-dev.md +69 -69
- package/extensions/ecommerce/skills/subscription-billing.md +93 -93
- package/extensions/ecommerce/skills/tax-compliance.md +117 -117
- package/extensions/gamedev/PACK.md +142 -142
- package/extensions/gamedev/skills/asset-pipeline.md +74 -74
- package/extensions/gamedev/skills/audio-system.md +129 -129
- package/extensions/gamedev/skills/camera-system.md +87 -87
- package/extensions/gamedev/skills/ecs.md +98 -98
- package/extensions/gamedev/skills/game-loops.md +72 -72
- package/extensions/gamedev/skills/input-system.md +199 -199
- package/extensions/gamedev/skills/multiplayer.md +180 -180
- package/extensions/gamedev/skills/particles.md +105 -105
- package/extensions/gamedev/skills/physics-engine.md +89 -89
- package/extensions/gamedev/skills/scene-management.md +146 -146
- package/extensions/gamedev/skills/threejs-patterns.md +90 -90
- package/extensions/gamedev/skills/webgl.md +71 -71
- package/extensions/mobile/PACK.md +106 -106
- package/extensions/mobile/skills/app-store-connect.md +152 -152
- package/extensions/mobile/skills/app-store-prep.md +66 -66
- package/extensions/mobile/skills/deep-linking.md +109 -109
- package/extensions/mobile/skills/flutter.md +60 -60
- package/extensions/mobile/skills/ios-build-pipeline.md +142 -142
- package/extensions/mobile/skills/native-bridge.md +66 -66
- package/extensions/mobile/skills/ota-updates.md +97 -97
- package/extensions/mobile/skills/push-notifications.md +111 -111
- package/extensions/mobile/skills/react-native.md +82 -82
- package/extensions/saas/PACK.md +116 -116
- package/extensions/saas/skills/billing-integration.md +200 -200
- package/extensions/saas/skills/feature-flags.md +130 -130
- package/extensions/saas/skills/multi-tenant.md +103 -103
- package/extensions/saas/skills/onboarding-flow.md +139 -139
- package/extensions/saas/skills/subscription-flow.md +95 -95
- package/extensions/saas/skills/team-management.md +144 -144
- package/extensions/security/PACK.md +99 -99
- package/extensions/security/skills/api-security.md +140 -140
- package/extensions/security/skills/compliance.md +68 -68
- package/extensions/security/skills/owasp-audit.md +64 -64
- package/extensions/security/skills/pentest-patterns.md +77 -77
- package/extensions/security/skills/secret-mgmt.md +65 -65
- package/extensions/security/skills/supply-chain.md +65 -65
- package/extensions/trading/PACK.md +80 -80
- package/extensions/trading/skills/chart-components.md +55 -55
- package/extensions/trading/skills/experiment-loop.md +125 -125
- package/extensions/trading/skills/fintech-patterns.md +47 -47
- package/extensions/trading/skills/indicator-library.md +58 -58
- package/extensions/trading/skills/quant-analysis.md +111 -111
- package/extensions/trading/skills/realtime-data.md +58 -58
- package/extensions/trading/skills/trade-logic.md +104 -104
- package/extensions/ui/PACK.md +130 -130
- package/extensions/ui/skills/a11y-audit.md +91 -91
- package/extensions/ui/skills/animation-patterns.md +127 -127
- package/extensions/ui/skills/component-patterns.md +100 -100
- package/extensions/ui/skills/design-decision.md +108 -108
- package/extensions/ui/skills/design-system.md +68 -68
- package/extensions/ui/skills/landing-patterns.md +155 -155
- package/extensions/ui/skills/palette-picker.md +173 -173
- package/extensions/ui/skills/react-health.md +90 -90
- package/extensions/ui/skills/type-system.md +125 -125
- package/extensions/ui/skills/web-vitals.md +153 -153
- package/extensions/zalo/PACK.md +145 -145
- package/extensions/zalo/skills/zalo-oa-mcp.md +317 -317
- package/extensions/zalo/skills/zalo-oa-messaging.md +429 -429
- package/extensions/zalo/skills/zalo-oa-setup.md +236 -236
- package/extensions/zalo/skills/zalo-oa-webhook.md +189 -189
- package/extensions/zalo/skills/zalo-personal-messaging.md +194 -194
- package/extensions/zalo/skills/zalo-personal-setup.md +153 -153
- package/extensions/zalo/skills/zalo-rate-guard.md +219 -219
- package/hooks/auto-format/index.cjs +48 -48
- package/hooks/hooks.json +111 -111
- package/hooks/post-session-reflect/index.cjs +189 -189
- package/hooks/pre-compact/index.cjs +95 -95
- package/hooks/run-hook.cmd +1 -1
- package/hooks/secrets-scan/index.cjs +100 -100
- package/hooks/session-start/index.cjs +71 -71
- package/hooks/typecheck/index.cjs +65 -65
- package/package.json +63 -63
- package/references/ui-pro-max-data/LICENSE-UI-PRO-MAX +21 -21
- package/references/ui-pro-max-data/charts.csv +26 -26
- package/references/ui-pro-max-data/colors.csv +161 -161
- package/references/ui-pro-max-data/styles.csv +68 -68
- package/references/ui-pro-max-data/typography.csv +74 -74
- package/references/ui-pro-max-data/ui-reasoning.csv +162 -162
- package/references/ui-pro-max-data/ux-guidelines.csv +99 -99
- package/skills/adversary/SKILL.md +283 -283
- package/skills/asset-creator/SKILL.md +157 -157
- package/skills/audit/SKILL.md +147 -2
- package/skills/autopsy/SKILL.md +335 -335
- package/skills/brainstorm/SKILL.md +342 -342
- package/skills/browser-pilot/SKILL.md +168 -168
- package/skills/constraint-check/SKILL.md +165 -165
- package/skills/context-engine/SKILL.md +404 -404
- package/skills/cook/SKILL.md +917 -863
- package/skills/db/SKILL.md +273 -273
- package/skills/debug/SKILL.md +465 -465
- package/skills/dependency-doctor/SKILL.md +265 -235
- package/skills/deploy/SKILL.md +274 -231
- package/skills/design/DESIGN-REFERENCE.md +365 -365
- package/skills/design/SKILL.md +589 -589
- package/skills/doc-processor/SKILL.md +254 -254
- package/skills/docs/SKILL.md +374 -374
- package/skills/docs-seeker/SKILL.md +177 -177
- package/skills/fix/SKILL.md +330 -330
- package/skills/git/SKILL.md +339 -339
- package/skills/hallucination-guard/SKILL.md +219 -219
- package/skills/incident/SKILL.md +254 -253
- package/skills/integrity-check/SKILL.md +169 -169
- package/skills/journal/SKILL.md +240 -240
- package/skills/launch/SKILL.md +344 -344
- package/skills/logic-guardian/SKILL.md +251 -251
- package/skills/marketing/SKILL.md +290 -289
- package/skills/mcp-builder/SKILL.md +425 -425
- package/skills/neural-memory/SKILL.md +362 -362
- package/skills/onboard/SKILL.md +404 -403
- package/skills/perf/SKILL.md +346 -346
- package/skills/plan/SKILL.md +433 -428
- package/skills/preflight/SKILL.md +415 -415
- package/skills/problem-solver/SKILL.md +380 -284
- package/skills/rescue/SKILL.md +474 -474
- package/skills/retro/SKILL.md +3 -1
- package/skills/review/SKILL.md +612 -588
- package/skills/review-intake/SKILL.md +249 -249
- package/skills/safeguard/SKILL.md +200 -200
- package/skills/sast/SKILL.md +190 -190
- package/skills/scaffold/SKILL.md +328 -287
- package/skills/scope-guard/SKILL.md +180 -180
- package/skills/scout/SKILL.md +263 -263
- package/skills/sentinel/SKILL.md +382 -381
- package/skills/sentinel-env/SKILL.md +254 -254
- package/skills/sequential-thinking/SKILL.md +234 -234
- package/skills/session-bridge/SKILL.md +543 -543
- package/skills/skill-forge/SKILL.md +581 -581
- package/skills/skill-router/SKILL.md +3 -0
- package/skills/surgeon/SKILL.md +215 -215
- package/skills/team/SKILL.md +556 -537
- package/skills/test/SKILL.md +614 -614
- package/skills/trend-scout/SKILL.md +145 -145
- package/skills/verification/SKILL.md +326 -326
- package/skills/video-creator/SKILL.md +201 -201
- package/skills/watchdog/SKILL.md +168 -168
- package/skills/worktree/SKILL.md +140 -140
|
@@ -1,95 +1,95 @@
|
|
|
1
|
-
// Rune Pre-Compact Hook
|
|
2
|
-
// Saves critical state BEFORE Claude auto-compacts context window.
|
|
3
|
-
// Prevents data loss from losing decisions, progress, and metrics mid-session.
|
|
4
|
-
//
|
|
5
|
-
// Captures: context-watch state, active task summary, decisions made this session.
|
|
6
|
-
// Writes snapshot to .rune/pre-compact-snapshot.md for post-compact recovery.
|
|
7
|
-
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const os = require('os');
|
|
11
|
-
|
|
12
|
-
const cwd = process.cwd();
|
|
13
|
-
const runeDir = path.join(cwd, '.rune');
|
|
14
|
-
|
|
15
|
-
// Read context-watch state (tool counts, session timing)
|
|
16
|
-
const hash = Buffer.from(cwd).toString('base64url').slice(0, 16);
|
|
17
|
-
const counterFile = path.join(os.tmpdir(), `rune-context-watch-${hash}.json`);
|
|
18
|
-
|
|
19
|
-
let watchState = null;
|
|
20
|
-
try {
|
|
21
|
-
const raw = fs.readFileSync(counterFile, 'utf-8');
|
|
22
|
-
watchState = JSON.parse(raw);
|
|
23
|
-
} catch {
|
|
24
|
-
// No counter file — session may be fresh
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Collect .rune/ state summaries
|
|
28
|
-
const stateFiles = ['progress.md', 'decisions.md', 'conventions.md'];
|
|
29
|
-
const summaries = [];
|
|
30
|
-
|
|
31
|
-
if (fs.existsSync(runeDir)) {
|
|
32
|
-
for (const file of stateFiles) {
|
|
33
|
-
const filePath = path.join(runeDir, file);
|
|
34
|
-
if (fs.existsSync(filePath)) {
|
|
35
|
-
try {
|
|
36
|
-
const content = fs.readFileSync(filePath, 'utf-8').trim();
|
|
37
|
-
if (content.length > 0) {
|
|
38
|
-
// Take first 50 lines to keep snapshot compact
|
|
39
|
-
const lines = content.split('\n').slice(0, 50);
|
|
40
|
-
summaries.push({ file, preview: lines.join('\n') });
|
|
41
|
-
}
|
|
42
|
-
} catch {
|
|
43
|
-
// Skip unreadable files
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Build snapshot
|
|
50
|
-
const snapshot = [];
|
|
51
|
-
snapshot.push('# Pre-Compact Snapshot');
|
|
52
|
-
snapshot.push(`Generated: ${new Date().toISOString()}`);
|
|
53
|
-
snapshot.push('');
|
|
54
|
-
|
|
55
|
-
if (watchState) {
|
|
56
|
-
snapshot.push('## Session Metrics');
|
|
57
|
-
snapshot.push(`- Tool calls: ${watchState.count || 0}`);
|
|
58
|
-
snapshot.push(`- Session start: ${watchState.sessionStart || 'unknown'}`);
|
|
59
|
-
if (watchState.toolCounts) {
|
|
60
|
-
const top5 = Object.entries(watchState.toolCounts)
|
|
61
|
-
.sort((a, b) => b[1] - a[1])
|
|
62
|
-
.slice(0, 5);
|
|
63
|
-
snapshot.push(`- Top tools: ${top5.map(([k, v]) => `${k}(${v})`).join(', ')}`);
|
|
64
|
-
}
|
|
65
|
-
snapshot.push('');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (summaries.length > 0) {
|
|
69
|
-
snapshot.push('## State Files (preview)');
|
|
70
|
-
for (const { file, preview } of summaries) {
|
|
71
|
-
snapshot.push(`### .rune/${file}`);
|
|
72
|
-
snapshot.push(preview);
|
|
73
|
-
snapshot.push('');
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Write snapshot if we have anything worth saving
|
|
78
|
-
if (watchState || summaries.length > 0) {
|
|
79
|
-
try {
|
|
80
|
-
if (!fs.existsSync(runeDir)) {
|
|
81
|
-
fs.mkdirSync(runeDir, { recursive: true });
|
|
82
|
-
}
|
|
83
|
-
fs.writeFileSync(
|
|
84
|
-
path.join(runeDir, 'pre-compact-snapshot.md'),
|
|
85
|
-
snapshot.join('\n')
|
|
86
|
-
);
|
|
87
|
-
console.log(`[Rune pre-compact] State snapshot saved (${watchState ? watchState.count : 0} tool calls, ${summaries.length} state files).`);
|
|
88
|
-
} catch (e) {
|
|
89
|
-
console.error(`[Rune pre-compact] Failed to save snapshot: ${e.message}`);
|
|
90
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
console.log('[Rune pre-compact] No state to snapshot — fresh session.');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
process.exit(0);
|
|
1
|
+
// Rune Pre-Compact Hook
|
|
2
|
+
// Saves critical state BEFORE Claude auto-compacts context window.
|
|
3
|
+
// Prevents data loss from losing decisions, progress, and metrics mid-session.
|
|
4
|
+
//
|
|
5
|
+
// Captures: context-watch state, active task summary, decisions made this session.
|
|
6
|
+
// Writes snapshot to .rune/pre-compact-snapshot.md for post-compact recovery.
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
|
|
12
|
+
const cwd = process.cwd();
|
|
13
|
+
const runeDir = path.join(cwd, '.rune');
|
|
14
|
+
|
|
15
|
+
// Read context-watch state (tool counts, session timing)
|
|
16
|
+
const hash = Buffer.from(cwd).toString('base64url').slice(0, 16);
|
|
17
|
+
const counterFile = path.join(os.tmpdir(), `rune-context-watch-${hash}.json`);
|
|
18
|
+
|
|
19
|
+
let watchState = null;
|
|
20
|
+
try {
|
|
21
|
+
const raw = fs.readFileSync(counterFile, 'utf-8');
|
|
22
|
+
watchState = JSON.parse(raw);
|
|
23
|
+
} catch {
|
|
24
|
+
// No counter file — session may be fresh
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Collect .rune/ state summaries
|
|
28
|
+
const stateFiles = ['progress.md', 'decisions.md', 'conventions.md'];
|
|
29
|
+
const summaries = [];
|
|
30
|
+
|
|
31
|
+
if (fs.existsSync(runeDir)) {
|
|
32
|
+
for (const file of stateFiles) {
|
|
33
|
+
const filePath = path.join(runeDir, file);
|
|
34
|
+
if (fs.existsSync(filePath)) {
|
|
35
|
+
try {
|
|
36
|
+
const content = fs.readFileSync(filePath, 'utf-8').trim();
|
|
37
|
+
if (content.length > 0) {
|
|
38
|
+
// Take first 50 lines to keep snapshot compact
|
|
39
|
+
const lines = content.split('\n').slice(0, 50);
|
|
40
|
+
summaries.push({ file, preview: lines.join('\n') });
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// Skip unreadable files
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Build snapshot
|
|
50
|
+
const snapshot = [];
|
|
51
|
+
snapshot.push('# Pre-Compact Snapshot');
|
|
52
|
+
snapshot.push(`Generated: ${new Date().toISOString()}`);
|
|
53
|
+
snapshot.push('');
|
|
54
|
+
|
|
55
|
+
if (watchState) {
|
|
56
|
+
snapshot.push('## Session Metrics');
|
|
57
|
+
snapshot.push(`- Tool calls: ${watchState.count || 0}`);
|
|
58
|
+
snapshot.push(`- Session start: ${watchState.sessionStart || 'unknown'}`);
|
|
59
|
+
if (watchState.toolCounts) {
|
|
60
|
+
const top5 = Object.entries(watchState.toolCounts)
|
|
61
|
+
.sort((a, b) => b[1] - a[1])
|
|
62
|
+
.slice(0, 5);
|
|
63
|
+
snapshot.push(`- Top tools: ${top5.map(([k, v]) => `${k}(${v})`).join(', ')}`);
|
|
64
|
+
}
|
|
65
|
+
snapshot.push('');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (summaries.length > 0) {
|
|
69
|
+
snapshot.push('## State Files (preview)');
|
|
70
|
+
for (const { file, preview } of summaries) {
|
|
71
|
+
snapshot.push(`### .rune/${file}`);
|
|
72
|
+
snapshot.push(preview);
|
|
73
|
+
snapshot.push('');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Write snapshot if we have anything worth saving
|
|
78
|
+
if (watchState || summaries.length > 0) {
|
|
79
|
+
try {
|
|
80
|
+
if (!fs.existsSync(runeDir)) {
|
|
81
|
+
fs.mkdirSync(runeDir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
fs.writeFileSync(
|
|
84
|
+
path.join(runeDir, 'pre-compact-snapshot.md'),
|
|
85
|
+
snapshot.join('\n')
|
|
86
|
+
);
|
|
87
|
+
console.log(`[Rune pre-compact] State snapshot saved (${watchState ? watchState.count : 0} tool calls, ${summaries.length} state files).`);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
console.error(`[Rune pre-compact] Failed to save snapshot: ${e.message}`);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
console.log('[Rune pre-compact] No state to snapshot — fresh session.');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
process.exit(0);
|
package/hooks/run-hook.cmd
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@node "%~dp0run-hook.js" %*
|
|
1
|
+
@node "%~dp0run-hook.js" %*
|
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
// Rune Secrets Scan Hook
|
|
2
|
-
// PreToolUse hook on Bash — intercepts git commit commands
|
|
3
|
-
// Scans staged files for hardcoded secrets before allowing commit
|
|
4
|
-
//
|
|
5
|
-
// Patterns based on gitleaks + common AI-generated secret leaks.
|
|
6
|
-
// Zero false-positive tolerance on BLOCK patterns.
|
|
7
|
-
|
|
8
|
-
const { execSync } = require('child_process');
|
|
9
|
-
|
|
10
|
-
// Only intercept git commit commands
|
|
11
|
-
const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}');
|
|
12
|
-
const command = (input.command || '').trim();
|
|
13
|
-
|
|
14
|
-
// Check if this is a git commit command
|
|
15
|
-
if (!/^git\s+commit\b/.test(command)) {
|
|
16
|
-
// Not a commit — pass through
|
|
17
|
-
process.exit(0);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Get staged files
|
|
21
|
-
let stagedFiles;
|
|
22
|
-
try {
|
|
23
|
-
stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf-8' })
|
|
24
|
-
.trim()
|
|
25
|
-
.split('\n')
|
|
26
|
-
.filter(f => f.length > 0);
|
|
27
|
-
} catch {
|
|
28
|
-
// Not in a git repo or no staged files — pass through
|
|
29
|
-
process.exit(0);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (stagedFiles.length === 0) {
|
|
33
|
-
process.exit(0);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Secret patterns (high-confidence, low false-positive)
|
|
37
|
-
const BLOCK_PATTERNS = [
|
|
38
|
-
{ name: 'AWS Access Key', regex: /AKIA[0-9A-Z]{16}/ },
|
|
39
|
-
{ name: 'GitHub Token', regex: /gh[ps]_[A-Za-z0-9_]{36,}/ },
|
|
40
|
-
{ name: 'Slack Token', regex: /xox[bpors]-[0-9a-zA-Z-]{10,}/ },
|
|
41
|
-
{ name: 'Stripe Key', regex: /[sr]k_(live|test)_[0-9a-zA-Z]{24,}/ },
|
|
42
|
-
{ name: 'SendGrid Key', regex: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/ },
|
|
43
|
-
{ name: 'Twilio Key', regex: /SK[0-9a-fA-F]{32}/ },
|
|
44
|
-
{ name: 'Firebase Key', regex: /AIza[0-9A-Za-z_-]{35}/ },
|
|
45
|
-
{ name: 'Private Key', regex: /-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/ },
|
|
46
|
-
{ name: 'OpenAI Key', regex: /sk-[A-Za-z0-9]{32,}/ },
|
|
47
|
-
{ name: 'Anthropic Key', regex: /sk-ant-[A-Za-z0-9_-]{32,}/ },
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
// Files to always skip (test fixtures, examples)
|
|
51
|
-
const SKIP_PATHS = /\/(test|tests|__tests__|fixtures|__mocks__|examples?|\.test\.|\.spec\.)/i;
|
|
52
|
-
|
|
53
|
-
// Scan staged content (not files on disk — staged content via git show)
|
|
54
|
-
const findings = [];
|
|
55
|
-
|
|
56
|
-
for (const file of stagedFiles) {
|
|
57
|
-
// Skip test/fixture files
|
|
58
|
-
if (SKIP_PATHS.test(file)) continue;
|
|
59
|
-
|
|
60
|
-
// Skip binary files and lockfiles
|
|
61
|
-
if (/\.(png|jpg|gif|ico|woff|ttf|lock|lockb)$/i.test(file)) continue;
|
|
62
|
-
|
|
63
|
-
let content;
|
|
64
|
-
try {
|
|
65
|
-
content = execSync(`git show ":${file}"`, { encoding: 'utf-8', maxBuffer: 1024 * 1024 });
|
|
66
|
-
} catch {
|
|
67
|
-
continue; // File deleted or binary
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const lines = content.split('\n');
|
|
71
|
-
for (let i = 0; i < lines.length; i++) {
|
|
72
|
-
const line = lines[i];
|
|
73
|
-
for (const pattern of BLOCK_PATTERNS) {
|
|
74
|
-
if (pattern.regex.test(line)) {
|
|
75
|
-
findings.push({
|
|
76
|
-
file,
|
|
77
|
-
line: i + 1,
|
|
78
|
-
pattern: pattern.name,
|
|
79
|
-
snippet: line.substring(0, 80).trim(),
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (findings.length > 0) {
|
|
87
|
-
console.error('\n\u{1F6A8} [Rune secrets-scan] BLOCKED — hardcoded secrets detected in staged files:\n');
|
|
88
|
-
for (const f of findings) {
|
|
89
|
-
console.error(` ${f.file}:${f.line} — ${f.pattern}`);
|
|
90
|
-
console.error(` ${f.snippet}...`);
|
|
91
|
-
}
|
|
92
|
-
console.error('\n Fix: Remove secrets, use environment variables instead.');
|
|
93
|
-
console.error(' Override: git commit --no-verify (NOT recommended)\n');
|
|
94
|
-
|
|
95
|
-
// BLOCK the commit
|
|
96
|
-
process.exit(2);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// No secrets found — allow commit
|
|
100
|
-
process.exit(0);
|
|
1
|
+
// Rune Secrets Scan Hook
|
|
2
|
+
// PreToolUse hook on Bash — intercepts git commit commands
|
|
3
|
+
// Scans staged files for hardcoded secrets before allowing commit
|
|
4
|
+
//
|
|
5
|
+
// Patterns based on gitleaks + common AI-generated secret leaks.
|
|
6
|
+
// Zero false-positive tolerance on BLOCK patterns.
|
|
7
|
+
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
|
|
10
|
+
// Only intercept git commit commands
|
|
11
|
+
const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}');
|
|
12
|
+
const command = (input.command || '').trim();
|
|
13
|
+
|
|
14
|
+
// Check if this is a git commit command
|
|
15
|
+
if (!/^git\s+commit\b/.test(command)) {
|
|
16
|
+
// Not a commit — pass through
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Get staged files
|
|
21
|
+
let stagedFiles;
|
|
22
|
+
try {
|
|
23
|
+
stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf-8' })
|
|
24
|
+
.trim()
|
|
25
|
+
.split('\n')
|
|
26
|
+
.filter(f => f.length > 0);
|
|
27
|
+
} catch {
|
|
28
|
+
// Not in a git repo or no staged files — pass through
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (stagedFiles.length === 0) {
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Secret patterns (high-confidence, low false-positive)
|
|
37
|
+
const BLOCK_PATTERNS = [
|
|
38
|
+
{ name: 'AWS Access Key', regex: /AKIA[0-9A-Z]{16}/ },
|
|
39
|
+
{ name: 'GitHub Token', regex: /gh[ps]_[A-Za-z0-9_]{36,}/ },
|
|
40
|
+
{ name: 'Slack Token', regex: /xox[bpors]-[0-9a-zA-Z-]{10,}/ },
|
|
41
|
+
{ name: 'Stripe Key', regex: /[sr]k_(live|test)_[0-9a-zA-Z]{24,}/ },
|
|
42
|
+
{ name: 'SendGrid Key', regex: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/ },
|
|
43
|
+
{ name: 'Twilio Key', regex: /SK[0-9a-fA-F]{32}/ },
|
|
44
|
+
{ name: 'Firebase Key', regex: /AIza[0-9A-Za-z_-]{35}/ },
|
|
45
|
+
{ name: 'Private Key', regex: /-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/ },
|
|
46
|
+
{ name: 'OpenAI Key', regex: /sk-[A-Za-z0-9]{32,}/ },
|
|
47
|
+
{ name: 'Anthropic Key', regex: /sk-ant-[A-Za-z0-9_-]{32,}/ },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
// Files to always skip (test fixtures, examples)
|
|
51
|
+
const SKIP_PATHS = /\/(test|tests|__tests__|fixtures|__mocks__|examples?|\.test\.|\.spec\.)/i;
|
|
52
|
+
|
|
53
|
+
// Scan staged content (not files on disk — staged content via git show)
|
|
54
|
+
const findings = [];
|
|
55
|
+
|
|
56
|
+
for (const file of stagedFiles) {
|
|
57
|
+
// Skip test/fixture files
|
|
58
|
+
if (SKIP_PATHS.test(file)) continue;
|
|
59
|
+
|
|
60
|
+
// Skip binary files and lockfiles
|
|
61
|
+
if (/\.(png|jpg|gif|ico|woff|ttf|lock|lockb)$/i.test(file)) continue;
|
|
62
|
+
|
|
63
|
+
let content;
|
|
64
|
+
try {
|
|
65
|
+
content = execSync(`git show ":${file}"`, { encoding: 'utf-8', maxBuffer: 1024 * 1024 });
|
|
66
|
+
} catch {
|
|
67
|
+
continue; // File deleted or binary
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const lines = content.split('\n');
|
|
71
|
+
for (let i = 0; i < lines.length; i++) {
|
|
72
|
+
const line = lines[i];
|
|
73
|
+
for (const pattern of BLOCK_PATTERNS) {
|
|
74
|
+
if (pattern.regex.test(line)) {
|
|
75
|
+
findings.push({
|
|
76
|
+
file,
|
|
77
|
+
line: i + 1,
|
|
78
|
+
pattern: pattern.name,
|
|
79
|
+
snippet: line.substring(0, 80).trim(),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (findings.length > 0) {
|
|
87
|
+
console.error('\n\u{1F6A8} [Rune secrets-scan] BLOCKED — hardcoded secrets detected in staged files:\n');
|
|
88
|
+
for (const f of findings) {
|
|
89
|
+
console.error(` ${f.file}:${f.line} — ${f.pattern}`);
|
|
90
|
+
console.error(` ${f.snippet}...`);
|
|
91
|
+
}
|
|
92
|
+
console.error('\n Fix: Remove secrets, use environment variables instead.');
|
|
93
|
+
console.error(' Override: git commit --no-verify (NOT recommended)\n');
|
|
94
|
+
|
|
95
|
+
// BLOCK the commit
|
|
96
|
+
process.exit(2);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// No secrets found — allow commit
|
|
100
|
+
process.exit(0);
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
// Rune Session Start Hook
|
|
2
|
-
// Loads and injects .rune/ state file CONTENTS into context at session start
|
|
3
|
-
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
|
|
8
|
-
const cwd = process.cwd();
|
|
9
|
-
const runeDir = path.join(cwd, '.rune');
|
|
10
|
-
|
|
11
|
-
// Initialize fresh session state (shared between context-watch and metrics-collector)
|
|
12
|
-
const hash = Buffer.from(cwd).toString('base64url').slice(0, 16);
|
|
13
|
-
const counterFile = path.join(os.tmpdir(), `rune-context-watch-${hash}.json`);
|
|
14
|
-
const now = new Date().toISOString();
|
|
15
|
-
const sessionId = `s-${now.slice(0, 10).replace(/-/g, '')}-${now.slice(11, 19).replace(/:/g, '')}`;
|
|
16
|
-
try {
|
|
17
|
-
fs.writeFileSync(counterFile, JSON.stringify({
|
|
18
|
-
count: 0, lastWarning: 0, sessionStart: now, sessionId, toolCounts: {}
|
|
19
|
-
}));
|
|
20
|
-
} catch { /* non-critical */ }
|
|
21
|
-
|
|
22
|
-
if (fs.existsSync(runeDir)) {
|
|
23
|
-
const stateFiles = [
|
|
24
|
-
'progress.md',
|
|
25
|
-
'decisions.md',
|
|
26
|
-
'conventions.md',
|
|
27
|
-
'RESCUE-STATE.md',
|
|
28
|
-
'DEVELOPER-GUIDE.md',
|
|
29
|
-
'logic-manifest.json'
|
|
30
|
-
];
|
|
31
|
-
const loaded = [];
|
|
32
|
-
|
|
33
|
-
for (const file of stateFiles) {
|
|
34
|
-
const filePath = path.join(runeDir, file);
|
|
35
|
-
if (fs.existsSync(filePath)) {
|
|
36
|
-
const content = fs.readFileSync(filePath, 'utf-8').trim();
|
|
37
|
-
if (content.length > 0) {
|
|
38
|
-
console.log(`\n=== .rune/${file} ===\n${content}`);
|
|
39
|
-
loaded.push(file);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Inject active behavioral context mode
|
|
45
|
-
const activeContextFile = path.join(runeDir, 'active-context.md');
|
|
46
|
-
if (fs.existsSync(activeContextFile)) {
|
|
47
|
-
try {
|
|
48
|
-
const mode = fs.readFileSync(activeContextFile, 'utf-8').trim();
|
|
49
|
-
if (mode) {
|
|
50
|
-
// Look for the context file in plugin's contexts/ directory
|
|
51
|
-
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || path.join(__dirname, '..', '..');
|
|
52
|
-
const contextFile = path.join(pluginRoot, 'contexts', `${mode}.md`);
|
|
53
|
-
if (fs.existsSync(contextFile)) {
|
|
54
|
-
const contextContent = fs.readFileSync(contextFile, 'utf-8').trim();
|
|
55
|
-
console.log(`\n=== Active Context: ${mode} mode ===\n${contextContent}`);
|
|
56
|
-
loaded.push(`active-context(${mode})`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
} catch {
|
|
60
|
-
// Non-critical — skip silently
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (loaded.length > 0) {
|
|
65
|
-
console.log(`\n[Rune: injected project state from ${loaded.join(', ')}]`);
|
|
66
|
-
} else {
|
|
67
|
-
console.log('[Rune: .rune/ directory found but no state files yet. Run /rune onboard to populate.]');
|
|
68
|
-
}
|
|
69
|
-
} else {
|
|
70
|
-
console.log('[Rune: No .rune/ directory found. Run /rune onboard to set up project context.]');
|
|
71
|
-
}
|
|
1
|
+
// Rune Session Start Hook
|
|
2
|
+
// Loads and injects .rune/ state file CONTENTS into context at session start
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
const runeDir = path.join(cwd, '.rune');
|
|
10
|
+
|
|
11
|
+
// Initialize fresh session state (shared between context-watch and metrics-collector)
|
|
12
|
+
const hash = Buffer.from(cwd).toString('base64url').slice(0, 16);
|
|
13
|
+
const counterFile = path.join(os.tmpdir(), `rune-context-watch-${hash}.json`);
|
|
14
|
+
const now = new Date().toISOString();
|
|
15
|
+
const sessionId = `s-${now.slice(0, 10).replace(/-/g, '')}-${now.slice(11, 19).replace(/:/g, '')}`;
|
|
16
|
+
try {
|
|
17
|
+
fs.writeFileSync(counterFile, JSON.stringify({
|
|
18
|
+
count: 0, lastWarning: 0, sessionStart: now, sessionId, toolCounts: {}
|
|
19
|
+
}));
|
|
20
|
+
} catch { /* non-critical */ }
|
|
21
|
+
|
|
22
|
+
if (fs.existsSync(runeDir)) {
|
|
23
|
+
const stateFiles = [
|
|
24
|
+
'progress.md',
|
|
25
|
+
'decisions.md',
|
|
26
|
+
'conventions.md',
|
|
27
|
+
'RESCUE-STATE.md',
|
|
28
|
+
'DEVELOPER-GUIDE.md',
|
|
29
|
+
'logic-manifest.json'
|
|
30
|
+
];
|
|
31
|
+
const loaded = [];
|
|
32
|
+
|
|
33
|
+
for (const file of stateFiles) {
|
|
34
|
+
const filePath = path.join(runeDir, file);
|
|
35
|
+
if (fs.existsSync(filePath)) {
|
|
36
|
+
const content = fs.readFileSync(filePath, 'utf-8').trim();
|
|
37
|
+
if (content.length > 0) {
|
|
38
|
+
console.log(`\n=== .rune/${file} ===\n${content}`);
|
|
39
|
+
loaded.push(file);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Inject active behavioral context mode
|
|
45
|
+
const activeContextFile = path.join(runeDir, 'active-context.md');
|
|
46
|
+
if (fs.existsSync(activeContextFile)) {
|
|
47
|
+
try {
|
|
48
|
+
const mode = fs.readFileSync(activeContextFile, 'utf-8').trim();
|
|
49
|
+
if (mode) {
|
|
50
|
+
// Look for the context file in plugin's contexts/ directory
|
|
51
|
+
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || path.join(__dirname, '..', '..');
|
|
52
|
+
const contextFile = path.join(pluginRoot, 'contexts', `${mode}.md`);
|
|
53
|
+
if (fs.existsSync(contextFile)) {
|
|
54
|
+
const contextContent = fs.readFileSync(contextFile, 'utf-8').trim();
|
|
55
|
+
console.log(`\n=== Active Context: ${mode} mode ===\n${contextContent}`);
|
|
56
|
+
loaded.push(`active-context(${mode})`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// Non-critical — skip silently
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (loaded.length > 0) {
|
|
65
|
+
console.log(`\n[Rune: injected project state from ${loaded.join(', ')}]`);
|
|
66
|
+
} else {
|
|
67
|
+
console.log('[Rune: .rune/ directory found but no state files yet. Run /rune onboard to populate.]');
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
console.log('[Rune: No .rune/ directory found. Run /rune onboard to set up project context.]');
|
|
71
|
+
}
|
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
// Rune TypeScript Check Hook
|
|
2
|
-
// PostToolUse hook on Edit/Write — runs tsc --noEmit after .ts/.tsx file edits
|
|
3
|
-
//
|
|
4
|
-
// Only runs if tsconfig.json exists in the project.
|
|
5
|
-
// Async hook — doesn't block the agent, just warns.
|
|
6
|
-
|
|
7
|
-
const { execSync } = require('child_process');
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
|
|
11
|
-
const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}');
|
|
12
|
-
const filePath = input.file_path || input.filePath || '';
|
|
13
|
-
|
|
14
|
-
// Only check TypeScript files
|
|
15
|
-
if (!/\.(ts|tsx)$/i.test(filePath)) {
|
|
16
|
-
process.exit(0);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Find tsconfig.json by walking up from the file
|
|
20
|
-
let dir = path.dirname(filePath);
|
|
21
|
-
let tsConfigDir = null;
|
|
22
|
-
|
|
23
|
-
for (let i = 0; i < 10; i++) {
|
|
24
|
-
if (fs.existsSync(path.join(dir, 'tsconfig.json'))) {
|
|
25
|
-
tsConfigDir = dir;
|
|
26
|
-
break;
|
|
27
|
-
}
|
|
28
|
-
const parent = path.dirname(dir);
|
|
29
|
-
if (parent === dir) break;
|
|
30
|
-
dir = parent;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (!tsConfigDir) {
|
|
34
|
-
// No tsconfig.json found — skip
|
|
35
|
-
process.exit(0);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Run tsc --noEmit
|
|
39
|
-
try {
|
|
40
|
-
execSync('npx tsc --noEmit --pretty 2>&1', {
|
|
41
|
-
cwd: tsConfigDir,
|
|
42
|
-
encoding: 'utf-8',
|
|
43
|
-
stdio: 'pipe',
|
|
44
|
-
timeout: 30000,
|
|
45
|
-
});
|
|
46
|
-
// Silent success
|
|
47
|
-
} catch (e) {
|
|
48
|
-
const output = (e.stdout || e.message || '').trim();
|
|
49
|
-
const errorLines = output.split('\n').filter(l => /error TS\d+/.test(l));
|
|
50
|
-
const errorCount = errorLines.length;
|
|
51
|
-
|
|
52
|
-
if (errorCount > 0) {
|
|
53
|
-
console.log(`\n[Rune typecheck] ${errorCount} TypeScript error(s) after editing ${path.basename(filePath)}:`);
|
|
54
|
-
// Show first 5 errors
|
|
55
|
-
errorLines.slice(0, 5).forEach(line => {
|
|
56
|
-
console.log(` ${line.trim()}`);
|
|
57
|
-
});
|
|
58
|
-
if (errorCount > 5) {
|
|
59
|
-
console.log(` ... and ${errorCount - 5} more. Run \`npx tsc --noEmit\` for full list.`);
|
|
60
|
-
}
|
|
61
|
-
console.log('');
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
process.exit(0);
|
|
1
|
+
// Rune TypeScript Check Hook
|
|
2
|
+
// PostToolUse hook on Edit/Write — runs tsc --noEmit after .ts/.tsx file edits
|
|
3
|
+
//
|
|
4
|
+
// Only runs if tsconfig.json exists in the project.
|
|
5
|
+
// Async hook — doesn't block the agent, just warns.
|
|
6
|
+
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}');
|
|
12
|
+
const filePath = input.file_path || input.filePath || '';
|
|
13
|
+
|
|
14
|
+
// Only check TypeScript files
|
|
15
|
+
if (!/\.(ts|tsx)$/i.test(filePath)) {
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Find tsconfig.json by walking up from the file
|
|
20
|
+
let dir = path.dirname(filePath);
|
|
21
|
+
let tsConfigDir = null;
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < 10; i++) {
|
|
24
|
+
if (fs.existsSync(path.join(dir, 'tsconfig.json'))) {
|
|
25
|
+
tsConfigDir = dir;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
const parent = path.dirname(dir);
|
|
29
|
+
if (parent === dir) break;
|
|
30
|
+
dir = parent;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!tsConfigDir) {
|
|
34
|
+
// No tsconfig.json found — skip
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Run tsc --noEmit
|
|
39
|
+
try {
|
|
40
|
+
execSync('npx tsc --noEmit --pretty 2>&1', {
|
|
41
|
+
cwd: tsConfigDir,
|
|
42
|
+
encoding: 'utf-8',
|
|
43
|
+
stdio: 'pipe',
|
|
44
|
+
timeout: 30000,
|
|
45
|
+
});
|
|
46
|
+
// Silent success
|
|
47
|
+
} catch (e) {
|
|
48
|
+
const output = (e.stdout || e.message || '').trim();
|
|
49
|
+
const errorLines = output.split('\n').filter(l => /error TS\d+/.test(l));
|
|
50
|
+
const errorCount = errorLines.length;
|
|
51
|
+
|
|
52
|
+
if (errorCount > 0) {
|
|
53
|
+
console.log(`\n[Rune typecheck] ${errorCount} TypeScript error(s) after editing ${path.basename(filePath)}:`);
|
|
54
|
+
// Show first 5 errors
|
|
55
|
+
errorLines.slice(0, 5).forEach(line => {
|
|
56
|
+
console.log(` ${line.trim()}`);
|
|
57
|
+
});
|
|
58
|
+
if (errorCount > 5) {
|
|
59
|
+
console.log(` ... and ${errorCount - 5} more. Run \`npx tsc --noEmit\` for full list.`);
|
|
60
|
+
}
|
|
61
|
+
console.log('');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
process.exit(0);
|