@su-record/vibe 2.4.56 → 2.4.58
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/CLAUDE.md +7 -18
- package/agents/compounder.md +1 -1
- package/agents/implementer.md +2 -1
- package/agents/simplifier.md +2 -1
- package/commands/vibe.run.md +4 -1
- package/commands/vibe.spec.md +56 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +0 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/postinstall.js +32 -1
- package/dist/cli/postinstall.js.map +1 -1
- package/dist/cli/setup.d.ts +18 -4
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +107 -27
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/types.d.ts +6 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/languages/csharp-unity.md +516 -0
- package/languages/gdscript-godot.md +470 -0
- package/languages/ruby-rails.md +489 -0
- package/languages/typescript-angular.md +433 -0
- package/languages/typescript-astro.md +416 -0
- package/languages/typescript-electron.md +407 -0
- package/languages/typescript-nestjs.md +524 -0
- package/languages/typescript-svelte.md +407 -0
- package/languages/typescript-tauri.md +366 -0
- package/package.json +1 -1
- package/skills/vibe-capabilities.md +1 -1
- package/vibe/constitution.md +130 -97
- package/vibe/rules/core/communication-guide.md +50 -56
- package/vibe/rules/core/development-philosophy.md +35 -36
- package/vibe/rules/core/quick-start.md +66 -85
- package/vibe/rules/quality/bdd-contract-testing.md +94 -89
- package/vibe/rules/quality/checklist.md +132 -132
- package/vibe/rules/quality/testing-strategy.md +132 -129
- package/vibe/rules/standards/anti-patterns.md +74 -74
- package/vibe/rules/standards/code-structure.md +44 -44
- package/vibe/rules/standards/complexity-metrics.md +63 -62
- package/vibe/rules/standards/naming-conventions.md +72 -72
- package/vibe/templates/constitution-template.md +153 -95
- package/vibe/templates/contract-backend-template.md +41 -32
- package/vibe/templates/contract-frontend-template.md +35 -30
- package/vibe/templates/feature-template.md +33 -33
- package/vibe/templates/spec-template.md +118 -96
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
# ⚡ TypeScript + Electron 품질 규칙
|
|
2
|
+
|
|
3
|
+
## 핵심 원칙 (core에서 상속)
|
|
4
|
+
|
|
5
|
+
```markdown
|
|
6
|
+
✅ 단일 책임 (SRP)
|
|
7
|
+
✅ 중복 제거 (DRY)
|
|
8
|
+
✅ 재사용성
|
|
9
|
+
✅ 낮은 복잡도
|
|
10
|
+
✅ 함수 ≤ 30줄
|
|
11
|
+
✅ 중첩 ≤ 3단계
|
|
12
|
+
✅ Cyclomatic complexity ≤ 10
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Electron 아키텍처 이해
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─────────────────────────────────────────────┐
|
|
19
|
+
│ Main Process (Node.js) │
|
|
20
|
+
│ - 앱 생명주기 관리 │
|
|
21
|
+
│ - 시스템 API (파일, 네트워크) │
|
|
22
|
+
│ - BrowserWindow 생성/관리 │
|
|
23
|
+
├─────────────────────────────────────────────┤
|
|
24
|
+
│ Preload Script (격리된 컨텍스트) │
|
|
25
|
+
│ - contextBridge로 API 노출 │
|
|
26
|
+
│ - Main ↔ Renderer 브릿지 │
|
|
27
|
+
├─────────────────────────────────────────────┤
|
|
28
|
+
│ Renderer Process (Chromium) │
|
|
29
|
+
│ - UI 렌더링 (React/Vue/etc) │
|
|
30
|
+
│ - window.electronAPI 사용 │
|
|
31
|
+
└─────────────────────────────────────────────┘
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## TypeScript/Electron 특화 규칙
|
|
35
|
+
|
|
36
|
+
### 1. 프로세스 분리 필수
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// ❌ Renderer에서 직접 Node.js 사용 (보안 취약)
|
|
40
|
+
// nodeIntegration: true 금지!
|
|
41
|
+
|
|
42
|
+
// ✅ Main Process (main.ts)
|
|
43
|
+
import { app, BrowserWindow, ipcMain } from 'electron';
|
|
44
|
+
import path from 'path';
|
|
45
|
+
|
|
46
|
+
function createWindow(): BrowserWindow {
|
|
47
|
+
const win = new BrowserWindow({
|
|
48
|
+
width: 800,
|
|
49
|
+
height: 600,
|
|
50
|
+
webPreferences: {
|
|
51
|
+
preload: path.join(__dirname, 'preload.js'),
|
|
52
|
+
contextIsolation: true, // 필수!
|
|
53
|
+
nodeIntegration: false, // 필수!
|
|
54
|
+
sandbox: true // 권장
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
win.loadFile('index.html');
|
|
59
|
+
return win;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
app.whenReady().then(createWindow);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 2. Preload Script 패턴
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// preload.ts
|
|
69
|
+
import { contextBridge, ipcRenderer } from 'electron';
|
|
70
|
+
|
|
71
|
+
// ✅ 타입 정의
|
|
72
|
+
interface ElectronAPI {
|
|
73
|
+
readFile: (path: string) => Promise<string>;
|
|
74
|
+
writeFile: (path: string, content: string) => Promise<void>;
|
|
75
|
+
onFileChanged: (callback: (path: string) => void) => () => void;
|
|
76
|
+
platform: NodeJS.Platform;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ✅ 안전하게 API 노출
|
|
80
|
+
contextBridge.exposeInMainWorld('electronAPI', {
|
|
81
|
+
readFile: (path: string) => ipcRenderer.invoke('read-file', path),
|
|
82
|
+
writeFile: (path: string, content: string) =>
|
|
83
|
+
ipcRenderer.invoke('write-file', path, content),
|
|
84
|
+
onFileChanged: (callback: (path: string) => void) => {
|
|
85
|
+
const handler = (_event: Electron.IpcRendererEvent, path: string) => callback(path);
|
|
86
|
+
ipcRenderer.on('file-changed', handler);
|
|
87
|
+
return () => ipcRenderer.removeListener('file-changed', handler);
|
|
88
|
+
},
|
|
89
|
+
platform: process.platform
|
|
90
|
+
} satisfies ElectronAPI);
|
|
91
|
+
|
|
92
|
+
// ✅ 타입 선언 (renderer에서 사용)
|
|
93
|
+
declare global {
|
|
94
|
+
interface Window {
|
|
95
|
+
electronAPI: ElectronAPI;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 3. IPC 통신 타입 안전성
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// shared/ipc-types.ts
|
|
104
|
+
export interface IpcChannels {
|
|
105
|
+
'read-file': { args: [string]; return: string };
|
|
106
|
+
'write-file': { args: [string, string]; return: void };
|
|
107
|
+
'get-app-info': { args: []; return: AppInfo };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface AppInfo {
|
|
111
|
+
version: string;
|
|
112
|
+
name: string;
|
|
113
|
+
paths: {
|
|
114
|
+
userData: string;
|
|
115
|
+
temp: string;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// main.ts
|
|
120
|
+
import { ipcMain } from 'electron';
|
|
121
|
+
import fs from 'fs/promises';
|
|
122
|
+
|
|
123
|
+
// ✅ 타입 안전한 핸들러
|
|
124
|
+
ipcMain.handle('read-file', async (_event, path: string): Promise<string> => {
|
|
125
|
+
return fs.readFile(path, 'utf-8');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
ipcMain.handle('write-file', async (_event, path: string, content: string): Promise<void> => {
|
|
129
|
+
await fs.writeFile(path, content, 'utf-8');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
ipcMain.handle('get-app-info', async (): Promise<AppInfo> => {
|
|
133
|
+
return {
|
|
134
|
+
version: app.getVersion(),
|
|
135
|
+
name: app.getName(),
|
|
136
|
+
paths: {
|
|
137
|
+
userData: app.getPath('userData'),
|
|
138
|
+
temp: app.getPath('temp')
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 4. Renderer에서 IPC 사용
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// renderer/hooks/useElectron.ts
|
|
148
|
+
|
|
149
|
+
// ✅ Custom Hook
|
|
150
|
+
function useFileReader() {
|
|
151
|
+
const [content, setContent] = useState<string | null>(null);
|
|
152
|
+
const [loading, setLoading] = useState(false);
|
|
153
|
+
const [error, setError] = useState<string | null>(null);
|
|
154
|
+
|
|
155
|
+
const readFile = useCallback(async (path: string) => {
|
|
156
|
+
setLoading(true);
|
|
157
|
+
setError(null);
|
|
158
|
+
try {
|
|
159
|
+
const result = await window.electronAPI.readFile(path);
|
|
160
|
+
setContent(result);
|
|
161
|
+
return result;
|
|
162
|
+
} catch (e) {
|
|
163
|
+
const msg = e instanceof Error ? e.message : 'Unknown error';
|
|
164
|
+
setError(msg);
|
|
165
|
+
throw e;
|
|
166
|
+
} finally {
|
|
167
|
+
setLoading(false);
|
|
168
|
+
}
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
171
|
+
return { content, loading, error, readFile };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ✅ 이벤트 구독 Hook
|
|
175
|
+
function useFileWatcher(onChanged: (path: string) => void) {
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
const unsubscribe = window.electronAPI.onFileChanged(onChanged);
|
|
178
|
+
return unsubscribe;
|
|
179
|
+
}, [onChanged]);
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 5. 창 관리
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// main.ts
|
|
187
|
+
import { BrowserWindow, screen } from 'electron';
|
|
188
|
+
|
|
189
|
+
// ✅ 창 상태 저장/복원
|
|
190
|
+
interface WindowState {
|
|
191
|
+
x?: number;
|
|
192
|
+
y?: number;
|
|
193
|
+
width: number;
|
|
194
|
+
height: number;
|
|
195
|
+
isMaximized: boolean;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function createWindowWithState(): BrowserWindow {
|
|
199
|
+
const state = loadWindowState();
|
|
200
|
+
|
|
201
|
+
const win = new BrowserWindow({
|
|
202
|
+
x: state.x,
|
|
203
|
+
y: state.y,
|
|
204
|
+
width: state.width,
|
|
205
|
+
height: state.height,
|
|
206
|
+
webPreferences: {
|
|
207
|
+
preload: path.join(__dirname, 'preload.js'),
|
|
208
|
+
contextIsolation: true,
|
|
209
|
+
nodeIntegration: false
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
if (state.isMaximized) {
|
|
214
|
+
win.maximize();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 상태 변경 시 저장
|
|
218
|
+
win.on('close', () => {
|
|
219
|
+
saveWindowState({
|
|
220
|
+
...win.getBounds(),
|
|
221
|
+
isMaximized: win.isMaximized()
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
return win;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ✅ 다중 창 관리
|
|
229
|
+
const windows = new Map<string, BrowserWindow>();
|
|
230
|
+
|
|
231
|
+
function getOrCreateWindow(id: string): BrowserWindow {
|
|
232
|
+
const existing = windows.get(id);
|
|
233
|
+
if (existing && !existing.isDestroyed()) {
|
|
234
|
+
existing.focus();
|
|
235
|
+
return existing;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const win = new BrowserWindow({ /* ... */ });
|
|
239
|
+
windows.set(id, win);
|
|
240
|
+
win.on('closed', () => windows.delete(id));
|
|
241
|
+
return win;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### 6. 메뉴 구성
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { Menu, MenuItemConstructorOptions } from 'electron';
|
|
249
|
+
|
|
250
|
+
// ✅ 플랫폼별 메뉴
|
|
251
|
+
function createMenu(): Menu {
|
|
252
|
+
const isMac = process.platform === 'darwin';
|
|
253
|
+
|
|
254
|
+
const template: MenuItemConstructorOptions[] = [
|
|
255
|
+
...(isMac ? [{
|
|
256
|
+
label: app.name,
|
|
257
|
+
submenu: [
|
|
258
|
+
{ role: 'about' as const },
|
|
259
|
+
{ type: 'separator' as const },
|
|
260
|
+
{ role: 'quit' as const }
|
|
261
|
+
]
|
|
262
|
+
}] : []),
|
|
263
|
+
{
|
|
264
|
+
label: 'File',
|
|
265
|
+
submenu: [
|
|
266
|
+
{
|
|
267
|
+
label: 'Open',
|
|
268
|
+
accelerator: 'CmdOrCtrl+O',
|
|
269
|
+
click: () => handleOpen()
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
label: 'Save',
|
|
273
|
+
accelerator: 'CmdOrCtrl+S',
|
|
274
|
+
click: () => handleSave()
|
|
275
|
+
},
|
|
276
|
+
{ type: 'separator' },
|
|
277
|
+
isMac ? { role: 'close' } : { role: 'quit' }
|
|
278
|
+
]
|
|
279
|
+
}
|
|
280
|
+
];
|
|
281
|
+
|
|
282
|
+
return Menu.buildFromTemplate(template);
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### 7. 자동 업데이트
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
import { autoUpdater } from 'electron-updater';
|
|
290
|
+
|
|
291
|
+
// ✅ 자동 업데이트 설정
|
|
292
|
+
function setupAutoUpdater(): void {
|
|
293
|
+
autoUpdater.autoDownload = false;
|
|
294
|
+
autoUpdater.autoInstallOnAppQuit = true;
|
|
295
|
+
|
|
296
|
+
autoUpdater.on('update-available', (info) => {
|
|
297
|
+
// 사용자에게 알림
|
|
298
|
+
dialog.showMessageBox({
|
|
299
|
+
type: 'info',
|
|
300
|
+
title: 'Update Available',
|
|
301
|
+
message: `Version ${info.version} is available.`,
|
|
302
|
+
buttons: ['Download', 'Later']
|
|
303
|
+
}).then(({ response }) => {
|
|
304
|
+
if (response === 0) {
|
|
305
|
+
autoUpdater.downloadUpdate();
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
autoUpdater.on('update-downloaded', () => {
|
|
311
|
+
dialog.showMessageBox({
|
|
312
|
+
type: 'info',
|
|
313
|
+
title: 'Update Ready',
|
|
314
|
+
message: 'Restart to install update?',
|
|
315
|
+
buttons: ['Restart', 'Later']
|
|
316
|
+
}).then(({ response }) => {
|
|
317
|
+
if (response === 0) {
|
|
318
|
+
autoUpdater.quitAndInstall();
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// 앱 시작 시 업데이트 확인
|
|
324
|
+
autoUpdater.checkForUpdates();
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### 8. 보안 체크리스트
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// ✅ 보안 설정 검증
|
|
332
|
+
function validateSecuritySettings(win: BrowserWindow): void {
|
|
333
|
+
const webPrefs = win.webContents.getWebPreferences();
|
|
334
|
+
|
|
335
|
+
if (webPrefs.nodeIntegration) {
|
|
336
|
+
console.error('SECURITY: nodeIntegration should be false');
|
|
337
|
+
}
|
|
338
|
+
if (!webPrefs.contextIsolation) {
|
|
339
|
+
console.error('SECURITY: contextIsolation should be true');
|
|
340
|
+
}
|
|
341
|
+
if (!webPrefs.sandbox) {
|
|
342
|
+
console.warn('SECURITY: sandbox is recommended');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ✅ 외부 링크 처리
|
|
347
|
+
win.webContents.setWindowOpenHandler(({ url }) => {
|
|
348
|
+
// 외부 URL은 시스템 브라우저에서 열기
|
|
349
|
+
if (url.startsWith('https://')) {
|
|
350
|
+
shell.openExternal(url);
|
|
351
|
+
}
|
|
352
|
+
return { action: 'deny' };
|
|
353
|
+
});
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## 폴더 구조 권장
|
|
357
|
+
|
|
358
|
+
```
|
|
359
|
+
my-electron-app/
|
|
360
|
+
├── src/
|
|
361
|
+
│ ├── main/ # Main Process
|
|
362
|
+
│ │ ├── main.ts
|
|
363
|
+
│ │ ├── ipc-handlers.ts
|
|
364
|
+
│ │ └── menu.ts
|
|
365
|
+
│ ├── preload/ # Preload Scripts
|
|
366
|
+
│ │ └── preload.ts
|
|
367
|
+
│ ├── renderer/ # Renderer (React/Vue)
|
|
368
|
+
│ │ ├── components/
|
|
369
|
+
│ │ ├── hooks/
|
|
370
|
+
│ │ └── App.tsx
|
|
371
|
+
│ └── shared/ # 공유 타입
|
|
372
|
+
│ └── ipc-types.ts
|
|
373
|
+
├── electron-builder.yml
|
|
374
|
+
└── package.json
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## 빌드 설정 (electron-builder)
|
|
378
|
+
|
|
379
|
+
```yaml
|
|
380
|
+
# electron-builder.yml
|
|
381
|
+
appId: com.example.myapp
|
|
382
|
+
productName: MyApp
|
|
383
|
+
directories:
|
|
384
|
+
output: dist
|
|
385
|
+
files:
|
|
386
|
+
- "build/**/*"
|
|
387
|
+
- "node_modules/**/*"
|
|
388
|
+
mac:
|
|
389
|
+
target: [dmg, zip]
|
|
390
|
+
category: public.app-category.developer-tools
|
|
391
|
+
win:
|
|
392
|
+
target: [nsis, portable]
|
|
393
|
+
linux:
|
|
394
|
+
target: [AppImage, deb]
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## 체크리스트
|
|
398
|
+
|
|
399
|
+
- [ ] `contextIsolation: true` 설정
|
|
400
|
+
- [ ] `nodeIntegration: false` 설정
|
|
401
|
+
- [ ] Preload script로만 API 노출
|
|
402
|
+
- [ ] IPC 채널 타입 정의
|
|
403
|
+
- [ ] 외부 링크 처리 (setWindowOpenHandler)
|
|
404
|
+
- [ ] 창 상태 저장/복원
|
|
405
|
+
- [ ] 자동 업데이트 설정
|
|
406
|
+
- [ ] 플랫폼별 메뉴 구성
|
|
407
|
+
- [ ] CSP 헤더 설정
|