@su-record/vibe 2.9.19 → 2.9.21

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 CHANGED
@@ -66,7 +66,11 @@ No `console.log` in commits · No hardcoded strings/numbers · No commented-out
66
66
 
67
67
  ## Workflow
68
68
 
69
- `/vibe.spec` is the single entry point — orchestrates interview → plan → spec → review → `/vibe.run` → `/vibe.verify` → `/vibe.trace`. For UI types (website/webapp/mobile), `/vibe.figma` branches in parallel. Smart Resume detects existing `.claude/vibe/{interviews,plans,specs}/*.md` to skip phases.
69
+ `/vibe.spec` is the single entry point — orchestrates interview → plan → spec → review → `/vibe.run` → `/vibe.verify` → `/vibe.contract` → `/vibe.trace`. For UI types (website/webapp/mobile), `/vibe.figma` branches in parallel. Smart Resume detects existing `.claude/vibe/{interviews,plans,specs}/*.md` to skip phases.
70
+
71
+ **Quality-loop commands** (bug → prevention):
72
+ - `/vibe.regress` — 회귀 테스트 자동 진화. `/vibe.verify` 실패 시 자동 register, `generate`로 예방 테스트 생성, `cluster`로 반복 패턴 승격.
73
+ - `/vibe.contract` — API 계약 드리프트 감지. SPEC에서 추출한 계약과 구현 비교, P1 drift는 `/vibe.regress`로 자동 전파.
70
74
 
71
75
  | Task Size | Approach |
72
76
  |---|---|
@@ -94,7 +98,7 @@ No `console.log` in commits · No hardcoded strings/numbers · No commented-out
94
98
 
95
99
  ## Git
96
100
 
97
- **Include**: `.claude/vibe/{plans,specs,features,todos}/`, `.claude/vibe/config.json`, `CLAUDE.md`
101
+ **Include**: `.claude/vibe/{plans,specs,features,todos,research,regressions,contracts}/`, `.claude/vibe/config.json`, `CLAUDE.md`
98
102
  **Exclude**: `~/.claude/{rules,commands,agents,skills}/`, `.claude/settings.local.json`
99
103
 
100
104
  <!-- VIBE:END -->
@@ -0,0 +1,105 @@
1
+ ---
2
+ description: API contract drift detection — extract contracts from SPEC, compare to implementation, fail on drift
3
+ argument-hint: "extract | check | diff [feature-name]"
4
+ ---
5
+
6
+ # /vibe.contract
7
+
8
+ **API Contract Drift Detection** — SPEC에 적힌 API 계약과 실제 구현이 어긋나면 즉시 잡는다.
9
+
10
+ > SPEC은 진실의 원천이다. 구현이 SPEC을 소리 없이 떠나면 테스트는 통과해도 계약은 깨진다.
11
+
12
+ ## Usage
13
+
14
+ ```
15
+ /vibe.contract extract <feature> # SPEC에서 API 계약 추출 → .claude/vibe/contracts/<feature>.md
16
+ /vibe.contract check <feature> # 계약 vs 구현 비교, 드리프트 리포트
17
+ /vibe.contract diff <feature> # 마지막 check 이후 변경된 필드만 요약
18
+ ```
19
+
20
+ ## What counts as an "API contract"
21
+
22
+ 계약 = 외부(클라이언트, 다른 서비스)가 의존하는 **모든 인터페이스 형태**:
23
+
24
+ - HTTP endpoint: method + path + request schema + response schema + status codes
25
+ - GraphQL: query/mutation name + args + return shape
26
+ - 이벤트/메시지: topic + payload schema
27
+ - 내보내진 TypeScript 함수 시그니처 (public API로 명시된 경우)
28
+
29
+ ## Process
30
+
31
+ Load skill `vibe-contract` with subcommand: `$ARGUMENTS`
32
+
33
+ **핵심 단계**:
34
+
35
+ 1. **extract**: SPEC(.claude/vibe/specs/\<feature\>.md)의 "API" / "Endpoints" / "Interface" 섹션을 파싱, 구조화된 계약 레코드로 저장
36
+ 2. **check**: 실제 구현(코드)에서 동일 엔드포인트를 찾아 시그니처/스키마 비교 — 드리프트 발견 시 P1 리포트
37
+ 3. **diff**: 이전 check 스냅샷과 비교, **변경된 필드만** 노출 (노이즈 최소화)
38
+
39
+ ## Drift severity
40
+
41
+ | Drift type | Severity | 예시 |
42
+ |---|---|---|
43
+ | Missing endpoint | P1 | SPEC에는 `GET /users/:id` 있는데 구현 없음 |
44
+ | Missing required field in response | P1 | SPEC response에 `email` 있는데 구현에서 빠짐 |
45
+ | Type change (breaking) | P1 | `userId: number` → `userId: string` |
46
+ | Added required request field | P1 | 기존 클라이언트 호환성 깨짐 |
47
+ | Added optional field | P3 | 확장은 허용 |
48
+ | Status code added | P2 | 클라이언트가 처리해야 할 새 케이스 |
49
+ | Status code removed | P1 | 예상 응답이 사라짐 |
50
+
51
+ **P1 드리프트 발견 시**: `/vibe.verify` 통과 여부와 무관하게 실패로 간주. 테스트는 통과해도 계약은 깨질 수 있기 때문.
52
+
53
+ ## Storage Format
54
+
55
+ ```
56
+ .claude/vibe/contracts/
57
+ <feature>.md # 추출된 계약 (SSOT)
58
+ <feature>.snapshot.md # 마지막 check 시점의 구현 스냅샷 (diff 비교용)
59
+ ```
60
+
61
+ ### Contract schema (frontmatter)
62
+
63
+ ```yaml
64
+ ---
65
+ feature: string
66
+ extracted-from: path/to/spec.md
67
+ extracted-at: ISO timestamp
68
+ endpoints:
69
+ - method: GET | POST | PUT | DELETE | PATCH
70
+ path: /users/:id
71
+ request:
72
+ params: { id: string }
73
+ body: null
74
+ response:
75
+ 200: { id: string, email: string, ... }
76
+ 404: { error: string }
77
+ required: [id, email]
78
+ - ...
79
+ ---
80
+ ```
81
+
82
+ ## Integration with /vibe.verify
83
+
84
+ `/vibe.verify <feature>` 흐름 끝에 자동 체인:
85
+
86
+ ```
87
+ scenarios pass → /vibe.contract check <feature>
88
+ ├─ no drift → ✅ complete
89
+ └─ drift found → ❌ report + auto-register to /vibe.regress (tag: integration)
90
+ ```
91
+
92
+ ## Integration with /vibe.spec
93
+
94
+ `/vibe.spec` 작성 완료 직후 자동으로 `/vibe.contract extract` 호출하여 계약을 미리 뽑아둠. 이후 `/vibe.run` 구현 시 이 계약이 참조점.
95
+
96
+ ## Done Criteria
97
+
98
+ - [ ] `extract`는 SPEC에 API 섹션이 없으면 **에러 없이 스킵** (모든 feature가 API를 갖진 않음)
99
+ - [ ] `check`는 드리프트 없으면 조용히 통과, 있으면 severity별 분류 출력
100
+ - [ ] P1 드리프트는 반드시 `/vibe.regress register --from-contract` 자동 호출
101
+ - [ ] `diff`는 이전 스냅샷이 없으면 "첫 실행"이라 안내
102
+
103
+ ---
104
+
105
+ ARGUMENTS: $ARGUMENTS
@@ -0,0 +1,73 @@
1
+ ---
2
+ description: Regression test auto-evolution — register bugs, generate preventive tests, cluster patterns
3
+ argument-hint: "register | generate | list | import | cluster [args]"
4
+ ---
5
+
6
+ # /vibe.regress
7
+
8
+ **Regression Auto-Evolution** — 같은 버그를 두 번 잡지 않기 위한 도구.
9
+
10
+ > 버그는 기록되고, 예방 테스트는 자동 생성되고, 반복 패턴은 공통 테스트로 승격된다.
11
+
12
+ ## Usage
13
+
14
+ ```
15
+ /vibe.regress register "<symptom>" # 수동 등록 (최소, 대부분 자동)
16
+ /vibe.regress generate <slug> # bug → vitest 파일 생성
17
+ /vibe.regress list # 미해결 목록
18
+ /vibe.regress import # git log의 fix: 커밋 역추적
19
+ /vibe.regress cluster # 3+ 유사 버그 → 공통 테스트 제안
20
+ ```
21
+
22
+ ## Auto-integration
23
+
24
+ - `/vibe.verify` 실패 → 자동으로 `register` 호출 (수동 개입 없음)
25
+ - `/vibe.run "<feature>"` 시작 → 해당 feature의 미해결 회귀 항목 경고
26
+
27
+ ## Process
28
+
29
+ Load skill `vibe-regress` with subcommand: `$ARGUMENTS`
30
+
31
+ `vibe-regress` 스킬이 등록·생성·클러스터링을 수행.
32
+
33
+ **핵심 단계** (상세는 `skills/vibe-regress/SKILL.md` 참조):
34
+
35
+ 1. 서브커맨드 파싱
36
+ 2. `.claude/vibe/regressions/<slug>.md` 읽기/쓰기 (frontmatter 스키마 준수)
37
+ 3. `generate` 시 프로젝트 테스트 스택 감지 → 적합한 템플릿 선택 (vitest/jest)
38
+ 4. `cluster` 시 frontmatter의 `root-cause-tag`로 그룹핑 → 3개 이상이면 공통 테스트 제안
39
+ 5. `import` 시 `git log --grep='^fix:'` 파싱 → 중복 스킵, 신규만 등록
40
+
41
+ ## Output
42
+
43
+ - `.claude/vibe/regressions/<slug>.md` — 버그 레코드 (frontmatter + 증상/재현/근본원인)
44
+ - 프로젝트 test dir — 생성된 vitest 파일 (`*.regression.test.ts` 네이밍)
45
+ - `list` 서브커맨드는 터미널 표
46
+
47
+ ## Storage Format
48
+
49
+ ```markdown
50
+ ---
51
+ slug: login-jwt-expiry-off-by-one
52
+ symptom: "JWT 만료 시간이 1초 일찍 끊김"
53
+ root-cause-tag: timezone
54
+ fix-commit: abc1234
55
+ test-path: src/auth/__tests__/login.regression.test.ts
56
+ status: open | test-generated | resolved
57
+ registered: 2026-04-14
58
+ feature: login
59
+ ---
60
+
61
+ ## Reproduction
62
+ 1. ...
63
+
64
+ ## Root cause
65
+ ...
66
+
67
+ ## Fix
68
+ ...
69
+ ```
70
+
71
+ ---
72
+
73
+ ARGUMENTS: $ARGUMENTS
@@ -46,6 +46,21 @@ Execute **Scenario-Driven Implementation** with automatic quality verification.
46
46
 
47
47
  > Automate **Scenario = Implementation = Verification** so even non-developers can trust quality
48
48
 
49
+ ### Pre-Run Regression Check (MANDATORY, before implementation starts)
50
+
51
+ 시작 직후 필수 실행:
52
+
53
+ ```
54
+ Load skill `vibe-regress` with: list --feature "{feature-name}"
55
+ ```
56
+
57
+ - 미해결 회귀 항목 있으면:
58
+ - interactive 모드: 사용자에게 "먼저 회귀 테스트 생성 후 진행?" 묻기
59
+ - ultrawork 모드: 자동 `/vibe.regress generate <slug>`로 예방 테스트 생성 후 진행
60
+ - 미해결 없으면 조용히 통과
61
+
62
+ 또한 `.claude/vibe/contracts/{feature-name}.md`이 있으면 로드 — 구현 시 계약 준수 기준으로 사용.
63
+
49
64
  ### Core Flow
50
65
 
51
66
  ```
@@ -372,13 +372,25 @@ Load skill `vibe-spec-review` with feature: {feature-name}
372
372
  5. Review Debate Team (2+ P1/P2 이슈 시)
373
373
  6. 사용자 최종 체크포인트
374
374
 
375
+ ### Phase 4.5: Contract Extract (자동, API 있는 feature만)
376
+
377
+ ```
378
+ Load skill `vibe-contract` with: extract "{feature-name}"
379
+ ```
380
+
381
+ SPEC에 `## API` / `## Endpoints` / `## Interface` 섹션이 있으면 계약을 `.claude/vibe/contracts/{feature-name}.md`로 추출. 섹션이 없으면 에러 없이 스킵 (모든 feature가 API를 갖지 않음).
382
+
383
+ 이 계약은 Phase 5a 구현 시 참조되고, `/vibe.verify` 종료 시 drift 검사에 사용됨.
384
+
375
385
  ### Phase 5a: Logic Track
376
386
 
377
387
  ```
378
388
  /vibe.run "{feature-name}"
379
389
  ```
380
390
 
381
- SPEC → 코드 구현.
391
+ SPEC → 코드 구현. 시작 시 자동 체크:
392
+ - `/vibe.regress list --feature {feature-name}` — 미해결 회귀 항목 있으면 경고
393
+ - `.claude/vibe/contracts/{feature-name}.md` — 있으면 로드하여 구현 가이드
382
394
 
383
395
  ### Phase 5b: UI Track (type ∈ {website, webapp, mobile}일 때만)
384
396
 
@@ -221,6 +221,24 @@ For each UI scenario in Feature file:
221
221
  └─────────────────────────────────────────────────────────────────┘
222
222
  ```
223
223
 
224
+ ### Failure Auto-Register (MANDATORY on any scenario failure)
225
+
226
+ Before printing the failure report, **auto-register each failed scenario as a regression bug** so the same failure cannot silently slip through next time:
227
+
228
+ ```
229
+ For each failed scenario:
230
+ Load skill `vibe-regress` with:
231
+ subcommand: register --from-verify
232
+ feature: {feature-name}
233
+ scenario: {scenario-name}
234
+ error: {error-summary}
235
+ location: {file:line}
236
+ ```
237
+
238
+ - `--from-verify` 모드는 사용자 확인 없이 등록 (verify 실패 컨텍스트에서 마찰 최소화)
239
+ - 등록된 bug의 slug를 Failure Report의 "Fix" 섹션에 링크로 노출
240
+ - 이후 `/vibe.regress generate <slug>`로 예방 테스트 생성 가능
241
+
224
242
  ### Failure Report
225
243
 
226
244
  ```
@@ -384,6 +402,19 @@ node -e "import('{{VIBE_PATH_URL}}/node_modules/@su-record/vibe/dist/tools/index
384
402
  **Codex P2 발견 시:**
385
403
  - TODO 파일에 기록 후 완료 처리
386
404
 
405
+ ## Post-Verify Contract Check (자동, contract 파일 있을 때만)
406
+
407
+ 모든 시나리오 통과 후 자동 호출:
408
+
409
+ ```
410
+ Load skill `vibe-contract` with: check "{feature-name}"
411
+ ```
412
+
413
+ - `.claude/vibe/contracts/{feature-name}.md`이 없으면 스킵
414
+ - drift 없음 → verify 통과 유지
415
+ - **P1 drift** → verify 실패로 강등 + `/vibe.regress register --from-contract` 자동 호출
416
+ - P2/P3 drift → 경고만, 통과 유지
417
+
387
418
  ## Next Step
388
419
 
389
420
  On verification pass:
@@ -280,8 +280,89 @@ describe('pre-tool-guard', () => {
280
280
  tool_name: 'Bash',
281
281
  tool_input: { command: 'DROP TABLE users' },
282
282
  });
283
- // tool_input is stringified — pattern matches against the JSON string
284
283
  expect(result.exitCode).toBe(2);
284
+ expect(result.stdout).toContain('Database drop detected');
285
+ });
286
+ });
287
+
288
+ // ══════════════════════════════════════════════════
289
+ // Regression: file content false positives (issue: machine-key.ts blocked)
290
+ // .claude/vibe/regressions/pre-tool-guard-content-false-positive.md
291
+ //
292
+ // 이전 구현은 tool_input 전체를 JSON.stringify해서 패턴 매칭했기 때문에
293
+ // 파일 내용에 '/etc/', '.env', 'secret' 같은 리터럴이 있으면 차단됐음.
294
+ // write/edit 패턴은 file_path만 봐야 한다.
295
+ // ══════════════════════════════════════════════════
296
+ describe('regression: write/edit content must not trigger path patterns', () => {
297
+ it('should ALLOW writing safe path even when content contains "/etc/" literal', () => {
298
+ const result = runGuardWithStdin({
299
+ tool_name: 'Write',
300
+ tool_input: {
301
+ file_path: 'src/machine-key.ts',
302
+ content: "for (const path of ['/etc/machine-id', '/var/lib/dbus/machine-id']) {}",
303
+ },
304
+ });
305
+ expect(result.exitCode).toBe(0);
306
+ expect(result.stdout).not.toContain('Writing to system directory');
307
+ });
308
+
309
+ it('should ALLOW writing safe path even when content contains "/usr/" literal', () => {
310
+ const result = runGuardWithStdin({
311
+ tool_name: 'Write',
312
+ tool_input: {
313
+ file_path: 'src/cli-detect.ts',
314
+ content: "const IOREG = '/usr/sbin/ioreg';",
315
+ },
316
+ });
317
+ expect(result.exitCode).toBe(0);
318
+ expect(result.stdout).not.toContain('Writing to system directory');
319
+ });
320
+
321
+ it('should ALLOW writing safe path even when content mentions ".env" / "secret"', () => {
322
+ const result = runGuardWithStdin({
323
+ tool_name: 'Write',
324
+ tool_input: {
325
+ file_path: 'src/config.ts',
326
+ content: "// loads from .env, never log secret values",
327
+ },
328
+ });
329
+ expect(result.exitCode).toBe(0);
330
+ expect(result.stdout).not.toContain('Writing to sensitive file');
331
+ });
332
+
333
+ it('should ALLOW editing safe path even when new_string contains ".env" literal', () => {
334
+ const result = runGuardWithStdin({
335
+ tool_name: 'Edit',
336
+ tool_input: {
337
+ file_path: 'src/index.ts',
338
+ old_string: 'const x = 1',
339
+ new_string: '// reads .env at startup',
340
+ },
341
+ });
342
+ expect(result.exitCode).toBe(0);
343
+ expect(result.stdout).not.toContain('Editing sensitive file');
344
+ });
345
+
346
+ it('should still BLOCK Write when file_path itself targets /etc/', () => {
347
+ const result = runGuardWithStdin({
348
+ tool_name: 'Write',
349
+ tool_input: { file_path: '/etc/passwd', content: 'root:x:0:0' },
350
+ });
351
+ expect(result.exitCode).toBe(2);
352
+ expect(result.stdout).toContain('Writing to system directory');
353
+ });
354
+
355
+ it('should still WARN Edit when file_path itself is a credentials file', () => {
356
+ const result = runGuardWithStdin({
357
+ tool_name: 'Edit',
358
+ tool_input: {
359
+ file_path: 'config/credentials.json',
360
+ old_string: 'a',
361
+ new_string: 'b',
362
+ },
363
+ });
364
+ expect(result.exitCode).toBe(0);
365
+ expect(result.stdout).toContain('Editing sensitive file');
285
366
  });
286
367
  });
287
368
  });
@@ -7,26 +7,35 @@
7
7
  import { VIBE_PATH, PROJECT_DIR } from './utils.js';
8
8
 
9
9
  // 위험한 명령어 패턴
10
+ //
11
+ // 각 엔트리의 `target`은 매칭 대상 필드:
12
+ // - 'command' → tool_input.command (Bash)
13
+ // - 'file_path' → tool_input.file_path (Write, Edit)
14
+ // - 'raw' → 전체 문자열 (스키마 모를 때 폴백)
15
+ //
16
+ // 예전 버전은 항상 stringify된 tool_input 전체에 매칭하여 file content가
17
+ // 패턴(예: /etc/, .env)과 일치하면 false positive로 차단되는 버그가 있었음.
18
+ // 회귀: .claude/vibe/regressions/pre-tool-guard-content-false-positive.md
10
19
  const DANGEROUS_PATTERNS = {
11
20
  bash: [
12
- { pattern: /rm\s+-rf?\s+[\/~]/, severity: 'critical', message: 'Deleting root or home directory' },
13
- { pattern: /rm\s+-rf?\s+\*/, severity: 'high', message: 'Wildcard deletion detected' },
14
- { pattern: /git\s+push\s+.*--force/, severity: 'high', message: 'Force push detected' },
15
- { pattern: /git\s+reset\s+--hard/, severity: 'medium', message: 'Hard reset will discard changes' },
16
- { pattern: /drop\s+(table|database)/i, severity: 'critical', message: 'Database drop detected' },
17
- { pattern: /truncate\s+table/i, severity: 'high', message: 'Table truncate detected' },
18
- { pattern: /:(){ :|:& };:/, severity: 'critical', message: 'Fork bomb detected' },
19
- { pattern: /mkfs|fdisk|dd\s+if=/, severity: 'critical', message: 'Disk operation detected' },
20
- { pattern: /chmod\s+-R\s+777/, severity: 'medium', message: 'Insecure permission change' },
21
- { pattern: /curl.*\|\s*(ba)?sh/, severity: 'high', message: 'Piping curl to shell' },
21
+ { pattern: /rm\s+-rf?\s+[\/~]/, target: 'command', severity: 'critical', message: 'Deleting root or home directory' },
22
+ { pattern: /rm\s+-rf?\s+\*/, target: 'command', severity: 'high', message: 'Wildcard deletion detected' },
23
+ { pattern: /git\s+push\s+.*--force/, target: 'command', severity: 'high', message: 'Force push detected' },
24
+ { pattern: /git\s+reset\s+--hard/, target: 'command', severity: 'medium', message: 'Hard reset will discard changes' },
25
+ { pattern: /drop\s+(table|database)/i, target: 'command', severity: 'critical', message: 'Database drop detected' },
26
+ { pattern: /truncate\s+table/i, target: 'command', severity: 'high', message: 'Table truncate detected' },
27
+ { pattern: /:(){ :|:& };:/, target: 'command', severity: 'critical', message: 'Fork bomb detected' },
28
+ { pattern: /mkfs|fdisk|dd\s+if=/, target: 'command', severity: 'critical', message: 'Disk operation detected' },
29
+ { pattern: /chmod\s+-R\s+777/, target: 'command', severity: 'medium', message: 'Insecure permission change' },
30
+ { pattern: /curl.*\|\s*(ba)?sh/, target: 'command', severity: 'high', message: 'Piping curl to shell' },
22
31
  ],
23
32
  edit: [
24
- { pattern: /\.env|credentials|secret|password|api[_-]?key/i, severity: 'medium', message: 'Editing sensitive file' },
25
- { pattern: /package-lock\.json|yarn\.lock|pnpm-lock/, severity: 'low', message: 'Editing lock file directly' },
33
+ { pattern: /\.env|credentials|secret|password|api[_-]?key/i, target: 'file_path', severity: 'medium', message: 'Editing sensitive file' },
34
+ { pattern: /package-lock\.json|yarn\.lock|pnpm-lock/, target: 'file_path', severity: 'low', message: 'Editing lock file directly' },
26
35
  ],
27
36
  write: [
28
- { pattern: /\.env|credentials|secret/i, severity: 'medium', message: 'Writing to sensitive file' },
29
- { pattern: /\/etc\/|\/usr\/|C:\\Windows/i, severity: 'critical', message: 'Writing to system directory' },
37
+ { pattern: /\.env|credentials|secret/i, target: 'file_path', severity: 'medium', message: 'Writing to sensitive file' },
38
+ { pattern: /\/etc\/|\/usr\/|C:\\Windows/i, target: 'file_path', severity: 'critical', message: 'Writing to system directory' },
30
39
  ],
31
40
  };
32
41
 
@@ -82,10 +91,39 @@ const SAFE_ALTERNATIVES = {
82
91
  'chmod 777': 'Use specific permissions (e.g., chmod 755 for directories)',
83
92
  };
84
93
 
94
+ /**
95
+ * 패턴 매칭 대상 추출
96
+ *
97
+ * tool_input이 객체이면 target 필드만 뽑아 반환. 객체가 아니거나(레거시 argv 모드)
98
+ * target='raw'이면 입력 전체를 그대로 반환.
99
+ *
100
+ * 핵심: write/edit 패턴은 file_path만 봐야 한다. content에 우연히 들어간
101
+ * 리터럴(예: 코드 안의 '/etc/machine-id' 문자열)이 차단을 일으키지 않도록.
102
+ */
103
+ function extractTarget(rawInput, target) {
104
+ if (target === 'raw') return typeof rawInput === 'string' ? rawInput : JSON.stringify(rawInput);
105
+
106
+ // 객체로 파싱 시도
107
+ let obj = rawInput;
108
+ if (typeof rawInput === 'string') {
109
+ try {
110
+ obj = JSON.parse(rawInput);
111
+ } catch {
112
+ // JSON 아님 → 레거시 argv 모드. argv[3]은 보통 file_path 또는 command 단일 문자열.
113
+ // 안전하게 그 문자열을 target 값으로 간주.
114
+ return rawInput;
115
+ }
116
+ }
117
+
118
+ if (typeof obj !== 'object' || obj === null) return '';
119
+ const value = obj[target];
120
+ return typeof value === 'string' ? value : '';
121
+ }
122
+
85
123
  /**
86
124
  * 명령어 검증
87
125
  */
88
- function validateCommand(toolName, input) {
126
+ function validateCommand(toolName, rawInput) {
89
127
  const results = {
90
128
  allowed: true,
91
129
  severity: 'none',
@@ -95,8 +133,11 @@ function validateCommand(toolName, input) {
95
133
 
96
134
  const patterns = DANGEROUS_PATTERNS[toolName.toLowerCase()] || [];
97
135
 
98
- for (const { pattern, severity, message } of patterns) {
99
- if (pattern.test(input)) {
136
+ for (const { pattern, target, severity, message } of patterns) {
137
+ const haystack = extractTarget(rawInput, target || 'raw');
138
+ if (!haystack) continue;
139
+
140
+ if (pattern.test(haystack)) {
100
141
  results.warnings.push(`[${severity.toUpperCase()}] ${message}`);
101
142
 
102
143
  // 심각도에 따른 처리
@@ -109,9 +150,9 @@ function validateCommand(toolName, input) {
109
150
  results.severity = severity;
110
151
  }
111
152
 
112
- // 대안 제안
153
+ // 대안 제안 — 매칭된 target 필드에서만 검색
113
154
  for (const [dangerous, safe] of Object.entries(SAFE_ALTERNATIVES)) {
114
- if (input.includes(dangerous)) {
155
+ if (haystack.includes(dangerous)) {
115
156
  results.suggestions.push(safe);
116
157
  }
117
158
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@su-record/vibe",
3
- "version": "2.9.19",
3
+ "version": "2.9.21",
4
4
  "description": "AI Coding Framework for Claude Code — 56 agents, 45 skills, multi-LLM orchestration",
5
5
  "type": "module",
6
6
  "main": "dist/cli/index.js",
@@ -63,11 +63,24 @@ Parallel research complete
63
63
 
64
64
  Synthesize results
65
65
 
66
+ Persist to .claude/vibe/research/<topic-slug>/
67
+ ├── synthesis.md (conversational report)
68
+ ├── awesome-list.md (curated links/repos)
69
+ └── paper.md (structured survey for /vibe.spec Context)
70
+
66
71
  Reflect in SPEC Context section
67
72
  OR
68
73
  Reference during implementation
69
74
  ```
70
75
 
76
+ ### Output Location (SSOT)
77
+
78
+ All research artifacts live under `.claude/vibe/research/<topic-slug>/`. Reference `paper.md` explicitly when writing a SPEC (e.g. paste its path into the Context section). Re-running research on the same topic overwrites unless the slug collides across dates (then `-YYYYMMDD` suffix).
79
+
80
+ > Wire-up note: `/vibe.spec` does **not** yet auto-scan this directory — that integration is a follow-up. For now, cite the path manually.
81
+
82
+ Pass `--ephemeral` to skip persistence when exploring throwaway questions.
83
+
71
84
  ## When Research is NOT Needed
72
85
 
73
86
  - Simple CRUD operations
@@ -85,5 +98,7 @@ Reference during implementation
85
98
 
86
99
  - [ ] All 4 research agents returned results (or documented why not)
87
100
  - [ ] Results synthesized into actionable recommendations
101
+ - [ ] Three artifacts written to `.claude/vibe/research/<slug>/` (synthesis / awesome-list / paper) — unless `--ephemeral`
102
+ - [ ] Every awesome-list entry has a one-line "why"
88
103
  - [ ] Key findings reflected in SPEC Context section or implementation notes
89
104
  - [ ] Conflicting recommendations resolved with reasoning
@@ -12,10 +12,15 @@ Receives outputs from all parallel research agents and produces a unified, opini
12
12
  ## Responsibilities
13
13
  - Collect and read all findings from best-practices, framework-docs, codebase-patterns, and security-advisory agents
14
14
  - Identify agreements and conflicts across sources; resolve with explicit reasoning
15
- - Weight findings: official docs > community consensus > codebase patterns > blog posts
15
+ - Weight findings: security > official docs > codebase patterns > community consensus > blog posts
16
16
  - Produce a ranked recommendation: primary approach + acceptable alternatives
17
17
  - Flag blockers — security issues or fundamental incompatibilities that must be resolved first
18
18
  - Summarize key trade-offs so the user can make an informed decision
19
+ - Emit **three** persisted artifacts (unless ephemeral mode):
20
+ 1. `synthesis.md` — conversational report (schema below)
21
+ 2. `awesome-list.md` — curated links, repos, internal patterns, anti-patterns (from `templates/awesome-list.md`)
22
+ 3. `paper.md` — structured survey with Abstract/Background/Method/Findings/Recommendation/References (from `templates/paper.md`)
23
+ - Drop entries without a one-line "why". No bare link dumps.
19
24
 
20
25
  ## Input
21
26
  Markdown findings documents from all four research agents (best-practices, framework-docs, codebase-patterns, security-advisory).
@@ -33,6 +33,17 @@ agents: [best-practices, framework-docs, codebase-patterns, security-advisory, s
33
33
  - **Output**: Ranked recommendations ordered by confidence and relevance
34
34
  - **Parallel**: no
35
35
 
36
+ ### Phase 5: Persist as Reusable Dataset
37
+ - **Agent**: orchestrator (self)
38
+ - **Input**: Synthesis (Phase 3) + ranked output (Phase 4)
39
+ - **Output**: Three files under `.claude/vibe/research/<topic-slug>/`:
40
+ - `synthesis.md` — conversational synthesis (from `templates/synthesis.md`)
41
+ - `awesome-list.md` — curated links/repos by category (from `templates/awesome-list.md`)
42
+ - `paper.md` — structured survey for `/vibe.spec` Context injection (from `templates/paper.md`)
43
+ - **Parallel**: no — write after Phase 4 completes
44
+ - **Slug rule**: kebab-case of topic, max 50 chars, deduped with `-YYYYMMDD` suffix on collision
45
+ - **Skip condition**: user passed `--ephemeral` or running inside `ultrawork` dry-run
46
+
36
47
  ## DAG (Dependency Graph)
37
48
 
38
49
  ```mermaid
@@ -43,6 +54,7 @@ graph TD
43
54
  E[Phase 1d: security-advisory] --> C
44
55
  C --> F[Phase 3: Synthesizer]
45
56
  F --> G[Phase 4: Ranked Output]
57
+ G --> H[Phase 5: Persist dataset]
46
58
  ```
47
59
 
48
60
  ## Error Handling
@@ -55,6 +67,8 @@ graph TD
55
67
  | Phase 2 | <2 agents return results | Warn user, proceed with partial synthesis |
56
68
  | Phase 3 | Conflicting recommendations | Flag conflict explicitly, present both sides |
57
69
  | Phase 4 | 0 actionable recommendations | Fallback — return raw findings without ranking |
70
+ | Phase 5 | Write fails (permission, disk) | Warn user, return synthesis in-conversation without persisting |
71
+ | Phase 5 | Slug collision | Append `-YYYYMMDD`; if still colliding, append `-<hhmm>` |
58
72
 
59
73
  ## Scalability Modes
60
74
 
@@ -0,0 +1,32 @@
1
+ # Awesome {{RESEARCH_TOPIC}}
2
+
3
+ > Curated from parallel research on {{DATE}}. Source agents: best-practices, framework-docs, codebase-patterns, security-advisory.
4
+
5
+ ## Official
6
+
7
+ - [{{OFFICIAL_DOCS_TITLE}}]({{OFFICIAL_DOCS_URL}}) — {{OFFICIAL_DOCS_NOTE}}
8
+
9
+ ## Reference Implementations
10
+
11
+ - [{{REPO_1_NAME}}]({{REPO_1_URL}}) — {{REPO_1_WHY}}
12
+ - [{{REPO_2_NAME}}]({{REPO_2_URL}}) — {{REPO_2_WHY}}
13
+
14
+ ## Articles & Best Practices
15
+
16
+ - [{{ARTICLE_1_TITLE}}]({{ARTICLE_1_URL}}) — {{ARTICLE_1_TAKEAWAY}}
17
+
18
+ ## Security Advisories
19
+
20
+ - [{{CVE_OR_ADVISORY}}]({{ADVISORY_URL}}) — severity: {{SEVERITY}} · mitigation: {{MITIGATION}}
21
+
22
+ ## Internal Patterns (this codebase)
23
+
24
+ - `{{FILE_PATH}}:{{LINE}}` — {{PATTERN_NOTE}}
25
+
26
+ ## Anti-patterns to Avoid
27
+
28
+ - {{ANTIPATTERN_1}} — why: {{ANTIPATTERN_1_REASON}}
29
+
30
+ ---
31
+
32
+ **Curation rules**: every entry has a one-line "why" explaining when to reach for it. Entries without a reason were dropped. Prefer primary sources over aggregators.
@@ -0,0 +1,88 @@
1
+ ---
2
+ topic: {{RESEARCH_TOPIC}}
3
+ slug: {{SLUG}}
4
+ date: {{DATE}}
5
+ stack: [{{TECHNOLOGY_STACK_CSV}}]
6
+ question: {{RESEARCH_QUESTION}}
7
+ ---
8
+
9
+ # {{RESEARCH_TOPIC}}: A Structured Survey for Implementation
10
+
11
+ **Date**: {{DATE}}
12
+ **Stack**: {{TECHNOLOGY_STACK}}
13
+ **Research question**: {{RESEARCH_QUESTION}}
14
+
15
+ ---
16
+
17
+ ## Abstract
18
+
19
+ {{ONE_PARAGRAPH_ABSTRACT}}
20
+
21
+ **TL;DR recommendation**: {{RECOMMENDATION}}
22
+
23
+ ## 1. Background
24
+
25
+ {{PROBLEM_CONTEXT — why this matters now, what forced the investigation}}
26
+
27
+ ### 1.1 Prior art in this codebase
28
+
29
+ {{EXISTING_PATTERNS — what's already here, at what file paths}}
30
+
31
+ ### 1.2 Scope
32
+
33
+ - In scope: {{IN_SCOPE}}
34
+ - Out of scope: {{OUT_OF_SCOPE}}
35
+
36
+ ## 2. Method
37
+
38
+ Parallel research across four specialized agents (best-practices, framework-docs, codebase-patterns, security-advisory). Findings weighted by source priority: **security > official docs > codebase consistency > community consensus > blog posts**.
39
+
40
+ ## 3. Findings
41
+
42
+ ### 3.1 Approach A — {{APPROACH_A}}
43
+ - Pro: {{A_PRO}}
44
+ - Con: {{A_CON}}
45
+ - Evidence: {{A_EVIDENCE}}
46
+
47
+ ### 3.2 Approach B — {{APPROACH_B}}
48
+ - Pro: {{B_PRO}}
49
+ - Con: {{B_CON}}
50
+ - Evidence: {{B_EVIDENCE}}
51
+
52
+ ### 3.3 Conflicts and resolutions
53
+
54
+ | Conflict | Position A | Position B | Resolution | Rationale |
55
+ |----------|-----------|-----------|------------|-----------|
56
+ | {{C_1}} | {{A_1}} | {{B_1}} | {{R_1}} | {{WHY_1}} |
57
+
58
+ ## 4. Security considerations
59
+
60
+ | Risk | Severity | Mitigation | Blocker? |
61
+ |------|----------|------------|----------|
62
+ | {{RISK_1}} | {{SEV_1}} | {{MIT_1}} | {{YN}} |
63
+
64
+ ## 5. Recommendation
65
+
66
+ **Primary**: {{PRIMARY_APPROACH}}
67
+ **Acceptable fallback**: {{FALLBACK}}
68
+ **Reject**: {{REJECTED}} — reason: {{REJECT_REASON}}
69
+
70
+ ### 5.1 Implementation starting point
71
+
72
+ {{CONCRETE_FIRST_STEP — file to touch, function to add, snippet}}
73
+
74
+ ## 6. Open questions
75
+
76
+ - [ ] {{OPEN_Q_1}} — resolve before {{PHASE}}
77
+ - [ ] {{OPEN_Q_2}} — can proceed without
78
+
79
+ ## 7. References
80
+
81
+ | # | Source | Agent | Confidence |
82
+ |---|--------|-------|------------|
83
+ | 1 | {{SRC_1}} | {{AGENT_1}} | {{CONF_1}} |
84
+ | 2 | {{SRC_2}} | {{AGENT_2}} | {{CONF_2}} |
85
+
86
+ ---
87
+
88
+ **Reuse**: cite this file by path (`.claude/vibe/research/{{SLUG}}/paper.md`) when writing a SPEC — paste the Findings and Recommendation sections into the SPEC's Context block.
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: vibe-contract
3
+ tier: core
4
+ description: "API contract drift detection. Extracts HTTP/GraphQL/event/public-function contracts from SPEC into .claude/vibe/contracts/<feature>.md, compares to implementation, and fails loudly on breaking drift (missing endpoints, removed required fields, type changes). P1 drifts auto-register as regressions via vibe-regress. Must use this skill when user runs /vibe.contract, when /vibe.spec completes, when /vibe.verify passes scenarios, or when the user says 'contract', 'API schema', 'breaking change', 'drift', '계약', '스키마 바뀜'."
5
+ triggers: [contract, drift, "계약", "API 변경", "breaking change", "schema drift"]
6
+ priority: 70
7
+ chain-next: []
8
+ ---
9
+
10
+ # vibe.contract — API Contract Drift Detection
11
+
12
+ **Purpose**: SPEC에 적힌 외부 계약과 실제 구현이 어긋나면 즉시 잡는다. 테스트 통과 ≠ 계약 준수.
13
+
14
+ ## Why this exists
15
+
16
+ 바이브코딩의 숨은 약점: 구현이 자라면서 SPEC에 명시된 응답 shape에서 조용히 벗어난다. 시나리오 테스트는 통과해도 **외부 소비자는 깨진다**. 사람이 매번 SPEC을 비교하기엔 마찰이 크므로 기계화.
17
+
18
+ ## Storage Contract
19
+
20
+ ```
21
+ .claude/vibe/contracts/
22
+ <feature>.md # 계약 SSOT (SPEC에서 추출)
23
+ <feature>.snapshot.md # 구현 스냅샷 (마지막 check 시점)
24
+ ```
25
+
26
+ ### Contract frontmatter schema
27
+
28
+ ```yaml
29
+ ---
30
+ feature: string
31
+ extracted-from: .claude/vibe/specs/<feature>.md
32
+ extracted-at: ISO-8601
33
+ source-spec-hash: sha256 # SPEC 변경 감지용
34
+ endpoints:
35
+ - id: unique-kebab-id # 예: get-user-by-id
36
+ kind: http | graphql | event | function
37
+ # http
38
+ method: GET | POST | PUT | DELETE | PATCH
39
+ path: /users/:id
40
+ request:
41
+ params: { name: type, ... }
42
+ query: { name: type, ... }
43
+ body: { field: type, ... } | null
44
+ required: [field, ...]
45
+ response:
46
+ statusCodes:
47
+ 200: { schema }
48
+ 404: { error: string }
49
+ required-fields:
50
+ 200: [id, email]
51
+ # graphql
52
+ operation: query | mutation
53
+ name: string
54
+ args: { ... }
55
+ returns: { ... }
56
+ # event
57
+ topic: string
58
+ payload: { ... }
59
+ # function
60
+ signature: "(a: string, b: number) => Promise<User>"
61
+ module: path/to/file.ts
62
+ ---
63
+ ```
64
+
65
+ ## Subcommands
66
+
67
+ ### 1. `extract <feature>` — SPEC에서 계약 추출
68
+
69
+ **단계**:
70
+ 1. SPEC 파일 로드 (single file or split folder)
71
+ 2. 다음 섹션을 순서대로 탐색:
72
+ - `## API` / `## Endpoints` / `## Interface` / `## Contract`
73
+ - Markdown 테이블 (method/path/request/response 헤더)
74
+ - 코드 블록 안의 OpenAPI/JSON Schema 스니펫
75
+ 3. 추출 실패(해당 섹션 없음) → **에러 없이 `no-contract` 상태 기록 후 종료**. 모든 feature가 API를 갖진 않음.
76
+ 4. 추출 성공 → frontmatter 구조로 변환
77
+ 5. `source-spec-hash`: SPEC 파일 내용의 sha256 (다음 extract 시 변경 감지)
78
+ 6. `.claude/vibe/contracts/<feature>.md` 저장 (기존 파일이 있고 hash가 같으면 **no-op**)
79
+
80
+ **주의**: 추출은 LLM 파싱. 신뢰도 낮은 필드는 `# unconfirmed` 주석 달아서 사용자가 검토 가능하게.
81
+
82
+ ### 2. `check <feature>` — 계약 vs 구현 비교
83
+
84
+ **단계**:
85
+ 1. `.claude/vibe/contracts/<feature>.md` 로드. 없으면 **먼저 extract 제안**.
86
+ 2. 각 endpoint에 대해 구현 탐색:
87
+ - http: 프레임워크 감지 (Express, Fastify, Next.js API routes, Hono, ...)
88
+ - graphql: resolver 파일 찾기
89
+ - event: 프로듀서/컨슈머 코드
90
+ - function: 모듈 export
91
+ 3. 구현 시그니처/스키마를 추출 → 계약과 비교
92
+ 4. Drift 분류 (severity 표는 command file 참고)
93
+ 5. 스냅샷 저장: `.claude/vibe/contracts/<feature>.snapshot.md` (현재 구현 상태)
94
+
95
+ ### 3. `diff <feature>` — 이전 스냅샷 대비 변경만
96
+
97
+ **단계**:
98
+ 1. `.snapshot.md`가 없으면 "첫 실행" 안내 후 종료
99
+ 2. 현재 구현 재추출 vs 기존 스냅샷 비교
100
+ 3. **변경된 필드만** 출력 (ASCII diff 형식):
101
+ ```
102
+ endpoints/get-user-by-id/response/200:
103
+ - email: string
104
+ + email: string | null ← nullability 추가 (P1 breaking)
105
+ + phoneNumber: string ← 신규 필드 (P3 safe)
106
+ ```
107
+ 4. 드리프트 있으면 `/vibe.regress register --from-contract` 자동 호출
108
+
109
+ ## Drift Severity Matrix
110
+
111
+ (command file의 표와 동일 — 변경 시 양쪽 갱신)
112
+
113
+ ## Integration Points
114
+
115
+ ### From /vibe.spec
116
+
117
+ SPEC 작성 완료 직후 자동 호출:
118
+ ```
119
+ Load skill `vibe-contract` with: extract <feature>
120
+ ```
121
+ 실패해도 `/vibe.spec`은 계속 진행 (계약 추출은 옵션). 단 성공 시 `/vibe.run`이 이 계약을 참조.
122
+
123
+ ### From /vibe.verify
124
+
125
+ 모든 scenario pass 후 자동 체인:
126
+ ```
127
+ Load skill `vibe-contract` with: check <feature>
128
+ ```
129
+ - drift 없음 → verify 통과 유지
130
+ - P1 drift → verify 실패로 강등, regress 자동 등록
131
+ - P2/P3 drift → 경고만, verify 통과 유지
132
+
133
+ ### To /vibe.regress
134
+
135
+ P1 drift 발견 시:
136
+ ```
137
+ Load skill `vibe-regress` with:
138
+ subcommand: register --from-contract
139
+ feature: <feature>
140
+ symptom: "Contract drift: <endpoint-id> <drift-type>"
141
+ root-cause-tag: integration
142
+ ```
143
+
144
+ ## Framework Detection Rules
145
+
146
+ HTTP framework 감지 순서:
147
+ 1. `package.json` dependencies에서: `next` → Next.js API routes
148
+ 2. `fastify` → Fastify
149
+ 3. `express` → Express
150
+ 4. `hono` → Hono
151
+ 5. `@nestjs/core` → NestJS
152
+ 6. 감지 실패 → user에게 질문 후 manual mapping
153
+
154
+ 감지 후 각 프레임워크의 **라우트 정의 패턴**을 Grep으로 찾아 endpoint 매핑:
155
+ - Next.js: `pages/api/**` or `app/api/**/route.ts`
156
+ - Express: `app.get|post|put|delete|patch\(`
157
+ - Fastify: `fastify.get|post|...` or route configuration
158
+ - Hono: `app.get|post|...`
159
+
160
+ ## Done Criteria
161
+
162
+ - [ ] `extract`가 API 섹션 없는 SPEC에서 에러 내지 않음
163
+ - [ ] `source-spec-hash` 기반 re-extract 스킵
164
+ - [ ] `check`는 각 drift에 severity + 위치(file:line) 명시
165
+ - [ ] P1 drift는 100% `/vibe.regress` 자동 등록
166
+ - [ ] 프레임워크 감지 실패 시 silently skip 금지 — 반드시 user 질문
@@ -0,0 +1,174 @@
1
+ ---
2
+ name: vibe-regress
3
+ tier: core
4
+ description: "Regression test auto-evolution. Registers bugs (auto from /vibe.verify failures or manual), generates preventive vitest/jest files from bug records, clusters repeated patterns (3+ same root-cause-tag) into shared tests, and imports historical `fix:` commits from git log. Storage: .claude/vibe/regressions/<slug>.md. Must use this skill when user runs /vibe.regress, when /vibe.verify produces a failure, or when the user says 'don't let this happen again' / '이 버그 다시는' / 'regression test' / '회귀 테스트'."
5
+ triggers: [regress, regression, "회귀", "다시는", "반복 버그", "fix commit"]
6
+ priority: 70
7
+ chain-next: []
8
+ ---
9
+
10
+ # vibe.regress — Regression Auto-Evolution
11
+
12
+ **Purpose**: 같은 버그를 두 번 잡지 않는다. 버그를 잡을 때마다 예방 테스트가 자라난다.
13
+
14
+ ## Why this exists
15
+
16
+ 바이브코딩의 고전적 약점: LLM이 같은 클래스의 버그를 매번 새로 만든다. 회귀 테스트는 이를 기계적으로 막는 유일한 장치. 단, 사람이 매번 회귀 테스트를 쓰면 건너뛰게 됨 — 그래서 자동화.
17
+
18
+ ## Storage Contract
19
+
20
+ ```
21
+ .claude/vibe/regressions/
22
+ <bug-slug>.md # 하나의 버그 = 하나의 파일
23
+ _cluster-<tag>.md # cluster 서브커맨드가 생성한 공통 테스트 설계
24
+ ```
25
+
26
+ ### Frontmatter schema (엄격)
27
+
28
+ ```yaml
29
+ slug: string # kebab-case, 글로벌 유일
30
+ symptom: string # 1줄, 사용자 관점
31
+ root-cause-tag: enum # 아래 허용 태그만
32
+ fix-commit: string # git hash (없으면 "pending")
33
+ test-path: string # 생성된 테스트 파일 경로 (없으면 "pending")
34
+ status: open | test-generated | resolved
35
+ registered: YYYY-MM-DD
36
+ feature: string # 연관 feature 이름 (SPEC과 매칭)
37
+ ```
38
+
39
+ ### Allowed `root-cause-tag` values
40
+
41
+ 클러스터링의 기반이므로 **미리 정의된 집합만** 사용:
42
+
43
+ - `timezone` — 시간대/DST/off-by-one
44
+ - `nullability` — null/undefined/empty 처리
45
+ - `concurrency` — race condition, 동시성
46
+ - `boundary` — off-by-one, edge value
47
+ - `encoding` — charset, URL encoding, escape
48
+ - `validation` — 입력 검증 누락
49
+ - `auth` — 인증/권한 로직
50
+ - `state-sync` — 클라이언트/서버 상태 불일치
51
+ - `integration` — 외부 API 호출 실패
52
+ - `type-narrow` — TypeScript 타입 좁히기 실수
53
+ - `other` — 위에 안 맞을 때 (나중에 새 태그 추가)
54
+
55
+ **규칙**: 새 태그가 필요하면 기존 태그에 억지로 맞추지 말고 `other`로 등록한 뒤, `other`가 3개 이상 쌓이면 사용자에게 태그 추가를 제안.
56
+
57
+ ## Subcommands
58
+
59
+ ### 1. `register "<symptom>"` — 수동 등록
60
+
61
+ 대부분은 자동 호출되므로 수동 사용은 드뭅니다 (verify 실패 밖에서 발견된 버그, 또는 프로덕션 이슈용).
62
+
63
+ **단계**:
64
+ 1. `getCurrentTime`으로 오늘 날짜 확보
65
+ 2. `git log -1 --format=%H`으로 현재 커밋 해시 (fix-commit 후보)
66
+ 3. Claude가 대화로 다음을 뽑아냄:
67
+ - Reproduction steps (Given/When/Then)
68
+ - Root cause 1문단
69
+ - Fix 설명
70
+ 4. `root-cause-tag`는 허용 집합에서 **자동 추론 후 사용자에게 확인**. 애매하면 `other`.
71
+ 5. slug 생성: symptom에서 핵심어 → kebab-case, 충돌 시 `-2` 접미사
72
+ 6. `.claude/vibe/regressions/<slug>.md` 작성 (status: `open`)
73
+
74
+ ### 2. `generate <slug>` — 예방 테스트 생성
75
+
76
+ **단계**:
77
+ 1. bug 파일 읽기
78
+ 2. 테스트 스택 감지:
79
+ - `package.json`의 `devDependencies`에서 `vitest` > `jest` 순
80
+ - 둘 다 없으면 **user에게 질문 후 중단**
81
+ 3. 테스트 위치 결정:
82
+ - feature의 구현 파일 옆 `__tests__/` 또는
83
+ - 프로젝트의 기존 test dir (vitest config.test.include 확인)
84
+ 4. 파일명: `<original-file>.regression.test.ts`
85
+ 5. 내용: `templates/test-vitest.md` 또는 `templates/test-jest.md` 템플릿에서 치환
86
+ 6. bug 파일 frontmatter 업데이트: `test-path`, `status: test-generated`
87
+ 7. **생성 후 즉시 테스트 실행** — 실패해야 정상 (아직 수정 안 됨이면) 또는 통과 (수정 완료됨이면). 결과를 frontmatter에 기록.
88
+
89
+ ### 3. `list` — 미해결 목록
90
+
91
+ ```
92
+ /vibe.regress list # status != resolved 전부
93
+ /vibe.regress list --feature login # feature별
94
+ /vibe.regress list --tag timezone # tag별
95
+ ```
96
+
97
+ 터미널 표:
98
+
99
+ ```
100
+ SLUG FEATURE TAG STATUS AGE
101
+ login-jwt-expiry-off-by-one login timezone test-generated 3d
102
+ cart-stock-race-double-deduct cart concurrency open 1d
103
+ ```
104
+
105
+ ### 4. `import` — git log 역추적
106
+
107
+ **단계**:
108
+ 1. `git log --grep='^fix:' --format='%H|%s|%ci' --since=<last-import-date>`
109
+ - `last-import-date`는 `.claude/vibe/regressions/.import-cursor` 파일에서 읽음 (없으면 90일 전)
110
+ 2. 각 커밋에 대해:
111
+ - 이미 같은 `fix-commit`을 가진 bug 파일이 있으면 **스킵**
112
+ - 없으면 커밋 메시지/diff에서 symptom + root-cause-tag 추론 (LLM 호출)
113
+ - 신규 bug.md 작성 (status: `resolved` — 이미 고쳐졌으므로)
114
+ 3. 완료 후 `.import-cursor` 갱신
115
+ 4. 새로 import된 항목에 대해 사용자에게 `generate` 제안
116
+
117
+ **주의**: `fix:` 커밋이 아닌 일반 커밋은 **무시**. Conventional Commits 규약을 사용하지 않는 프로젝트는 `--grep-pattern` 옵션으로 오버라이드.
118
+
119
+ ### 5. `cluster` — 반복 패턴 승격
120
+
121
+ **단계**:
122
+ 1. 모든 bug 파일의 `root-cause-tag` 집계
123
+ 2. **같은 태그가 3개 이상**이면 cluster 후보
124
+ 3. 각 후보에 대해:
125
+ - 3개 bug의 reproduction을 LLM에게 주고 "공통 원인과 공통 테스트 케이스"를 추출
126
+ - `_cluster-<tag>.md` 파일 생성 (기존 bug 파일들의 slug를 링크)
127
+ - 공통 테스트 skeleton을 `<project-test-dir>/_cluster-<tag>.regression.test.ts`로 제안 (사용자 승인 후 생성)
128
+ 4. 클러스터 생성 시 원본 bug 파일은 **삭제하지 않음** — 이력 보존
129
+
130
+ **중요**: cluster는 자동 실행 안 됨. 사용자가 명시적으로 호출해야 함 (과도한 추상화 방지).
131
+
132
+ ## Integration with /vibe.verify
133
+
134
+ `/vibe.verify` 실패 시 verify가 다음을 호출:
135
+
136
+ ```
137
+ Load skill `vibe-regress` with: register --from-verify
138
+ <feature>: {feature-name}
139
+ <scenario>: {failed-scenario}
140
+ <error>: {error-message}
141
+ <location>: {file:line}
142
+ ```
143
+
144
+ `--from-verify` 플래그 동작:
145
+ - symptom = scenario name + error summary
146
+ - feature = 전달된 feature name
147
+ - root-cause-tag = error pattern에서 자동 추론 (애매하면 `other`)
148
+ - status = `open`
149
+ - **사용자 확인 없이 등록** (verify 실패 상황은 이미 주의 집중 중이라 마찰 최소화가 중요)
150
+
151
+ ## Integration with /vibe.run
152
+
153
+ `/vibe.run "<feature>"` 시작 시:
154
+
155
+ 1. `ls .claude/vibe/regressions/*.md`에서 `feature: <feature-name>` + `status != resolved` 필터
156
+ 2. 미해결 있으면 경고:
157
+ ```
158
+ ⚠️ Open regressions for this feature:
159
+ - login-jwt-expiry-off-by-one (timezone, 3d old)
160
+ - login-session-leak (auth, 1w old)
161
+
162
+ Fix these before adding new behavior? [y/N]
163
+ ```
164
+ 3. `y` → `/vibe.regress generate`로 체인 (미생성 항목)
165
+ 4. `N` → 계속 진행 (ultrawork 모드는 자동 `N` + TODO 기록)
166
+
167
+ ## Done Criteria
168
+
169
+ - [ ] 서브커맨드가 지정 없이 호출되면 usage 표시
170
+ - [ ] frontmatter 스키마 엄격 준수 (누락 필드 있으면 거부)
171
+ - [ ] `root-cause-tag`가 허용 집합 외 값이면 경고 + `other`로 강제
172
+ - [ ] `generate` 후 테스트를 **실제로 실행**하여 결과 검증
173
+ - [ ] `import`는 중복 스킵 (fix-commit 해시 기준)
174
+ - [ ] `cluster`는 3개 미만이면 아무것도 안 함 (false positive 방지)
@@ -0,0 +1,44 @@
1
+ ---
2
+ slug: {{SLUG}}
3
+ symptom: "{{SYMPTOM}}"
4
+ root-cause-tag: {{ROOT_CAUSE_TAG}}
5
+ fix-commit: {{FIX_COMMIT}}
6
+ test-path: {{TEST_PATH}}
7
+ status: {{STATUS}}
8
+ registered: {{DATE}}
9
+ feature: {{FEATURE}}
10
+ ---
11
+
12
+ # {{SLUG}}
13
+
14
+ ## Symptom
15
+
16
+ {{SYMPTOM_DETAIL}}
17
+
18
+ ## Reproduction
19
+
20
+ **Given**: {{GIVEN}}
21
+
22
+ **When**: {{WHEN}}
23
+
24
+ **Then** (broken behavior): {{THEN_BROKEN}}
25
+
26
+ **Expected**: {{THEN_EXPECTED}}
27
+
28
+ ## Root cause
29
+
30
+ {{ROOT_CAUSE_EXPLANATION}}
31
+
32
+ ## Fix
33
+
34
+ {{FIX_EXPLANATION}}
35
+
36
+ ## Related
37
+
38
+ - Feature: [.claude/vibe/features/{{FEATURE}}.feature](../../features/{{FEATURE}}.feature)
39
+ - Fix commit: `{{FIX_COMMIT}}`
40
+ - Regression test: `{{TEST_PATH}}`
41
+
42
+ ## Notes
43
+
44
+ {{NOTES}}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Regression test — {{SLUG}}
3
+ *
4
+ * Symptom: {{SYMPTOM}}
5
+ * Root cause tag: {{ROOT_CAUSE_TAG}}
6
+ * Fix commit: {{FIX_COMMIT}}
7
+ * Source record: .claude/vibe/regressions/{{SLUG}}.md
8
+ *
9
+ * DO NOT delete this test when the bug is fixed — it exists to prevent the
10
+ * same bug from being reintroduced. Update only if the reproduction steps
11
+ * in the source record change.
12
+ */
13
+
14
+ {{IMPORTS_FROM_SUT}}
15
+
16
+ describe('regression: {{SYMPTOM_SHORT}}', () => {
17
+ it('{{TEST_NAME}}', {{ASYNC}}() => {
18
+ // Given: {{GIVEN}}
19
+ {{GIVEN_CODE}}
20
+
21
+ // When: {{WHEN}}
22
+ {{WHEN_CODE}}
23
+
24
+ // Then: {{THEN}}
25
+ {{THEN_ASSERTIONS}}
26
+ });
27
+
28
+ {{EXTRA_IT_BLOCKS}}
29
+ });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Regression test — {{SLUG}}
3
+ *
4
+ * Symptom: {{SYMPTOM}}
5
+ * Root cause tag: {{ROOT_CAUSE_TAG}}
6
+ * Fix commit: {{FIX_COMMIT}}
7
+ * Source record: .claude/vibe/regressions/{{SLUG}}.md
8
+ *
9
+ * DO NOT delete this test when the bug is fixed — it exists to prevent the
10
+ * same bug from being reintroduced. Update only if the reproduction steps
11
+ * in the source record change.
12
+ */
13
+
14
+ import { describe, it, expect{{EXTRA_IMPORTS}} } from 'vitest';
15
+ {{IMPORTS_FROM_SUT}}
16
+
17
+ describe('regression: {{SYMPTOM_SHORT}}', () => {
18
+ it('{{TEST_NAME}}', {{ASYNC}}() => {
19
+ // Given: {{GIVEN}}
20
+ {{GIVEN_CODE}}
21
+
22
+ // When: {{WHEN}}
23
+ {{WHEN_CODE}}
24
+
25
+ // Then: {{THEN}}
26
+ {{THEN_ASSERTIONS}}
27
+ });
28
+
29
+ {{EXTRA_IT_BLOCKS}}
30
+ });
@@ -395,6 +395,30 @@ Read ~/.claude/vibe/languages/typescript-react.md
395
395
  - `config.json.references` is automatically referenced when running `/vibe.run`
396
396
  - Not copied to project (referenced from global package)
397
397
 
398
+ ### 2.9 Research Cache Check (BEFORE step 3)
399
+
400
+ Before spawning any research agents, check for a prior persisted dataset:
401
+
402
+ ```bash
403
+ # Slug = kebab-case of feature/topic, max 50 chars
404
+ ls .claude/vibe/research/<slug>/paper.md 2>/dev/null
405
+ ```
406
+
407
+ **If `paper.md` exists:**
408
+ 1. Read `.claude/vibe/research/<slug>/paper.md`
409
+ 2. Read `.claude/vibe/research/<slug>/awesome-list.md` (if present)
410
+ 3. Inject the **Findings**, **Recommendation**, and **Security considerations** sections into SPEC Context verbatim, prefixed with `> Source: .claude/vibe/research/<slug>/paper.md (cached {{FILE_MTIME}})`
411
+ 4. **Skip step 3** (parallel research) entirely — do not re-run GPT/Gemini/Claude agents
412
+ 5. Show message: `✅ Research cache hit: <slug> (saved ~30s of LLM calls)`
413
+
414
+ **Cache invalidation:**
415
+ - User passes `--refresh-research` → delete dir, run step 3 fresh
416
+ - `paper.md` mtime older than 30 days → warn user, ask to refresh or reuse
417
+ - Stack in `paper.md` header differs from current stack → auto-refresh
418
+
419
+ **If `paper.md` does NOT exist:**
420
+ Proceed to step 3. After step 3 completes, the synthesizer **must** write the 3 artifacts (see `parallel-research/orchestrator.md` Phase 5) so the next `/vibe.spec` run on this topic hits the cache.
421
+
398
422
  ### 3. Parallel Research (v2.5.0) - MANDATORY AFTER requirements confirmed
399
423
 
400
424
  **🚨🚨🚨 ABSOLUTE RULES FOR RESEARCH PHASE 🚨🚨🚨**
@@ -539,6 +563,22 @@ Task(subagent_type="ui-layout-architect",
539
563
  - Layout: {③ pattern + sections}
540
564
  ```
541
565
 
566
+ ### 3.9 Persist Research Cache (AFTER research completes, BEFORE SPEC write)
567
+
568
+ > The "no Write during research" rule (step 3) does **not** apply here — research is done, artifacts are safe to persist.
569
+
570
+ After parallel research + UI/UX intelligence complete and before writing the SPEC, save the merged research into `.claude/vibe/research/<slug>/`:
571
+
572
+ 1. Compute slug: kebab-case of feature name, max 50 chars
573
+ 2. Write **three files** using templates from `parallel-research/templates/`:
574
+ - `.claude/vibe/research/<slug>/synthesis.md` — raw merged findings (all agent outputs)
575
+ - `.claude/vibe/research/<slug>/awesome-list.md` — curated links/repos/patterns (each entry needs a one-line "why"; drop entries without it)
576
+ - `.claude/vibe/research/<slug>/paper.md` — structured survey (Abstract → Background → Method → Findings → Recommendation → Security → References)
577
+ 3. Include a frontmatter header in `paper.md` with `stack:` field so step 2.9 can detect stack drift
578
+ 4. If the directory already exists (user ran `--refresh-research`), overwrite
579
+
580
+ This makes the next `/vibe.spec` or `/vibe.research` invocation on the same topic hit the cache at step 2.9.
581
+
542
582
  ### 4. Write SPEC Document (PTCF Structure)
543
583
 
544
584
  #### 4.0 Large Scope Detection & Auto-Split (MANDATORY)