@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 +6 -2
- package/commands/vibe.contract.md +105 -0
- package/commands/vibe.regress.md +73 -0
- package/commands/vibe.run.md +15 -0
- package/commands/vibe.spec.md +13 -1
- package/commands/vibe.verify.md +31 -0
- package/hooks/scripts/__tests__/pre-tool-guard.test.js +82 -1
- package/hooks/scripts/pre-tool-guard.js +60 -19
- package/package.json +1 -1
- package/skills/parallel-research/SKILL.md +15 -0
- package/skills/parallel-research/agents/synthesizer.md +6 -1
- package/skills/parallel-research/orchestrator.md +14 -0
- package/skills/parallel-research/templates/awesome-list.md +32 -0
- package/skills/parallel-research/templates/paper.md +88 -0
- package/skills/vibe-contract/SKILL.md +166 -0
- package/skills/vibe-regress/SKILL.md +174 -0
- package/skills/vibe-regress/templates/bug.md +44 -0
- package/skills/vibe-regress/templates/test-jest.md +29 -0
- package/skills/vibe-regress/templates/test-vitest.md +30 -0
- package/skills/vibe-spec/SKILL.md +40 -0
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
|
package/commands/vibe.run.md
CHANGED
|
@@ -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
|
```
|
package/commands/vibe.spec.md
CHANGED
|
@@ -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
|
|
package/commands/vibe.verify.md
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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 (
|
|
155
|
+
if (haystack.includes(dangerous)) {
|
|
115
156
|
results.suggestions.push(safe);
|
|
116
157
|
}
|
|
117
158
|
}
|
package/package.json
CHANGED
|
@@ -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 >
|
|
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)
|