@su-record/vibe 2.9.1 → 2.9.3
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 +31 -10
- package/README.ko.md +90 -25
- package/README.md +139 -25
- package/agents/teams/debug-team.md +70 -0
- package/agents/teams/dev-team.md +88 -0
- package/agents/teams/docs-team.md +80 -0
- package/agents/teams/figma/figma-analyst.md +52 -0
- package/agents/teams/figma/figma-architect.md +112 -0
- package/agents/teams/figma/figma-auditor.md +82 -0
- package/agents/teams/figma/figma-builder.md +100 -0
- package/agents/teams/figma-team.md +85 -0
- package/agents/teams/fullstack-team.md +83 -0
- package/agents/teams/lite-team.md +69 -0
- package/agents/teams/migration-team.md +78 -0
- package/agents/teams/refactor-team.md +94 -0
- package/agents/teams/research-team.md +86 -0
- package/agents/teams/review-debate-team.md +125 -0
- package/agents/teams/security-team.md +81 -0
- package/commands/vibe.analyze.md +324 -170
- package/commands/vibe.figma.md +549 -34
- package/commands/vibe.harness.md +177 -0
- package/commands/vibe.review.md +1 -63
- package/commands/vibe.run.md +52 -403
- package/commands/vibe.scaffold.md +195 -0
- package/commands/vibe.spec.md +373 -1003
- package/commands/vibe.trace.md +17 -0
- package/commands/vibe.verify.md +19 -10
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +29 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +4 -2
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/postinstall/constants.d.ts +1 -1
- package/dist/cli/postinstall/constants.d.ts.map +1 -1
- package/dist/cli/postinstall/constants.js +6 -1
- package/dist/cli/postinstall/constants.js.map +1 -1
- package/dist/cli/setup/ProjectSetup.d.ts +12 -1
- package/dist/cli/setup/ProjectSetup.d.ts.map +1 -1
- package/dist/cli/setup/ProjectSetup.js +259 -72
- package/dist/cli/setup/ProjectSetup.js.map +1 -1
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +1 -1
- package/dist/cli/setup.js.map +1 -1
- package/hooks/scripts/figma-guard.js +220 -0
- package/hooks/scripts/figma-refine.js +315 -0
- package/hooks/scripts/figma-to-scss.js +394 -0
- package/hooks/scripts/figma-validate.js +353 -0
- package/package.json +1 -1
- package/skills/arch-guard/SKILL.md +1 -1
- package/skills/capability-loop/SKILL.md +106 -2
- package/skills/chub-usage/SKILL.md +43 -43
- package/skills/claude-md-guide/SKILL.md +175 -175
- package/skills/design-teach/SKILL.md +33 -33
- package/skills/devlog/SKILL.md +38 -38
- package/skills/event-comms/SKILL.md +23 -13
- package/skills/event-ops/SKILL.md +28 -19
- package/skills/event-planning/SKILL.md +13 -1
- package/skills/priority-todos/SKILL.md +1 -1
- package/skills/vibe.figma/SKILL.md +263 -115
- package/skills/vibe.figma/templates/component-spec.md +168 -0
- package/skills/vibe.figma.convert/SKILL.md +131 -84
- package/skills/vibe.figma.convert/rubrics/conversion-rules.md +12 -0
- package/skills/vibe.figma.extract/SKILL.md +148 -108
- package/skills/vibe.figma.extract/rubrics/image-rules.md +15 -3
- package/skills/vibe.interview/SKILL.md +358 -0
- package/skills/vibe.interview/checklists/api.md +101 -0
- package/skills/vibe.interview/checklists/feature.md +88 -0
- package/skills/vibe.interview/checklists/library.md +95 -0
- package/skills/vibe.interview/checklists/mobile.md +89 -0
- package/skills/vibe.interview/checklists/webapp.md +97 -0
- package/skills/vibe.interview/checklists/website.md +99 -0
- package/skills/vibe.plan/SKILL.md +216 -0
- package/skills/vibe.spec/SKILL.md +1155 -0
- package/{commands/vibe.spec.review.md → skills/vibe.spec.review/SKILL.md} +272 -155
- package/vibe/templates/claudemd-template.md +74 -0
- package/vibe/templates/constitution-template.md +15 -0
- package/vibe/templates/plan-template.md +194 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* figma-validate.js — SCSS vs sections.json 대조 검증
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node figma-validate.js <scss-dir> <sections.json> [--section=<name>]
|
|
8
|
+
*
|
|
9
|
+
* 검증 항목:
|
|
10
|
+
* 1. SCSS의 모든 CSS 속성이 sections.json에 근거하는가
|
|
11
|
+
* 2. sections.json의 CSS 속성이 SCSS에 누락되지 않았는가
|
|
12
|
+
* 3. 금지 패턴 감지 (커스텀 함수, aspect-ratio 등)
|
|
13
|
+
* 4. 이미지 파일명 kebab-case 확인
|
|
14
|
+
*
|
|
15
|
+
* 출력: JSON { status, errors[], summary }
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fs from 'fs';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
|
|
21
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
function camelToKebab(str) {
|
|
24
|
+
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseSCSS(scssContent) {
|
|
28
|
+
// 간이 SCSS 파서: 셀렉터 → CSS 속성 맵 추출
|
|
29
|
+
const blocks = {};
|
|
30
|
+
let currentSelector = null;
|
|
31
|
+
const lines = scssContent.split('\n');
|
|
32
|
+
|
|
33
|
+
for (const line of lines) {
|
|
34
|
+
const trimmed = line.trim();
|
|
35
|
+
|
|
36
|
+
// 빈 줄, 코멘트 스킵
|
|
37
|
+
if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('/*')) continue;
|
|
38
|
+
|
|
39
|
+
// 셀렉터 열기
|
|
40
|
+
const selectorMatch = trimmed.match(/^([.&][a-zA-Z0-9_-][a-zA-Z0-9_-]*(?:__[a-zA-Z0-9_-]+(?:-[a-zA-Z0-9_-]+)*)?)\s*\{/);
|
|
41
|
+
if (selectorMatch) {
|
|
42
|
+
currentSelector = selectorMatch[1];
|
|
43
|
+
if (!blocks[currentSelector]) blocks[currentSelector] = {};
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 블록 닫기
|
|
48
|
+
if (trimmed === '}') {
|
|
49
|
+
currentSelector = null;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// CSS 속성
|
|
54
|
+
if (currentSelector && trimmed.includes(':') && trimmed.endsWith(';')) {
|
|
55
|
+
const colonIdx = trimmed.indexOf(':');
|
|
56
|
+
const prop = trimmed.slice(0, colonIdx).trim();
|
|
57
|
+
const value = trimmed.slice(colonIdx + 1).replace(/;$/, '').trim();
|
|
58
|
+
blocks[currentSelector][prop] = value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return blocks;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── Forbidden Pattern Detection ────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
const FORBIDDEN_PATTERNS = [
|
|
68
|
+
{ pattern: /@function\s/, id: 'custom-function', msg: '@function 자체 정의 금지' },
|
|
69
|
+
{ pattern: /@mixin\s/, id: 'custom-mixin', msg: '@mixin 자체 정의 금지 (기존 @use만 허용)' },
|
|
70
|
+
{ pattern: /aspect-ratio\s*:/, id: 'aspect-ratio', msg: 'aspect-ratio는 tree.json에 없는 속성' },
|
|
71
|
+
{ pattern: /clamp\s*\(/, id: 'clamp', msg: 'clamp()는 font-size 외 사용 금지' },
|
|
72
|
+
{ pattern: /\d+vw/, id: 'vw-unit', msg: 'vw 단위 사용 금지 (스태틱 구현)' },
|
|
73
|
+
{ pattern: /@media\s/, id: 'media-query', msg: '@media 금지 (스태틱 구현)' },
|
|
74
|
+
{ pattern: /@include\s/, id: 'include', msg: '@include 사용 시 기존 프로젝트 믹스인만 허용' },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
function detectForbidden(scssContent, filePath) {
|
|
78
|
+
const errors = [];
|
|
79
|
+
const lines = scssContent.split('\n');
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < lines.length; i++) {
|
|
82
|
+
const line = lines[i];
|
|
83
|
+
for (const { pattern, id, msg } of FORBIDDEN_PATTERNS) {
|
|
84
|
+
if (pattern.test(line)) {
|
|
85
|
+
// clamp in font-size is allowed
|
|
86
|
+
if (id === 'clamp' && line.includes('font-size')) continue;
|
|
87
|
+
errors.push({
|
|
88
|
+
priority: 'P1',
|
|
89
|
+
file: filePath,
|
|
90
|
+
line: i + 1,
|
|
91
|
+
type: `forbidden-${id}`,
|
|
92
|
+
actual: line.trim(),
|
|
93
|
+
message: msg
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return errors;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ─── CSS Value Comparison ───────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
function collectCSSFromTree(node, sectionClass, parentPath, result) {
|
|
105
|
+
const css = node.css || {};
|
|
106
|
+
const children = node.children || [];
|
|
107
|
+
const name = node.name || '';
|
|
108
|
+
const type = node.type || '';
|
|
109
|
+
|
|
110
|
+
// BG 프레임 스킵
|
|
111
|
+
const nameLower = name.toLowerCase();
|
|
112
|
+
if (nameLower === 'bg' || nameLower.endsWith('-bg') || nameLower.startsWith('bg-')) return;
|
|
113
|
+
|
|
114
|
+
// 클래스명 재현 (figma-to-scss.js와 동일 로직)
|
|
115
|
+
let cls = name
|
|
116
|
+
.replace(/[^a-zA-Z0-9\s_-]/g, '')
|
|
117
|
+
.replace(/[\s_]+/g, '-')
|
|
118
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
119
|
+
.toLowerCase()
|
|
120
|
+
.replace(/-+/g, '-')
|
|
121
|
+
.replace(/^-|-$/g, '') || null;
|
|
122
|
+
|
|
123
|
+
if (!cls) {
|
|
124
|
+
const idx = parentPath ? parentPath.split('-').length : 0;
|
|
125
|
+
if (type === 'TEXT') cls = `text-${idx}`;
|
|
126
|
+
else if (type === 'VECTOR') cls = `vector-${idx}`;
|
|
127
|
+
else if (type === 'RECTANGLE') cls = `rect-${idx}`;
|
|
128
|
+
else cls = `el-${idx}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const currentPath = parentPath ? `${parentPath}-${cls}` : cls;
|
|
132
|
+
const selector = `.${sectionClass}__${currentPath}`;
|
|
133
|
+
|
|
134
|
+
// CSS 속성 저장 (kebab 변환)
|
|
135
|
+
if (Object.keys(css).length > 0) {
|
|
136
|
+
const kebabCSS = {};
|
|
137
|
+
for (const [prop, value] of Object.entries(css)) {
|
|
138
|
+
if (prop.startsWith('_')) continue;
|
|
139
|
+
kebabCSS[camelToKebab(prop)] = String(value);
|
|
140
|
+
}
|
|
141
|
+
result[selector] = kebabCSS;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 자식 재귀
|
|
145
|
+
const seenClasses = new Set();
|
|
146
|
+
for (const child of children) {
|
|
147
|
+
const childName = child.name || '';
|
|
148
|
+
const childLower = childName.toLowerCase();
|
|
149
|
+
if (childLower === 'bg' || childLower.endsWith('-bg') || childLower.startsWith('bg-')) continue;
|
|
150
|
+
|
|
151
|
+
let childCls = childName
|
|
152
|
+
.replace(/[^a-zA-Z0-9\s_-]/g, '')
|
|
153
|
+
.replace(/[\s_]+/g, '-')
|
|
154
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
155
|
+
.toLowerCase()
|
|
156
|
+
.replace(/-+/g, '-')
|
|
157
|
+
.replace(/^-|-$/g, '') || null;
|
|
158
|
+
if (!childCls) childCls = `${child.type?.toLowerCase() || 'el'}-0`;
|
|
159
|
+
|
|
160
|
+
if (seenClasses.has(childCls)) continue;
|
|
161
|
+
seenClasses.add(childCls);
|
|
162
|
+
collectCSSFromTree(child, sectionClass, currentPath, result);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function compareCSSValues(scssBlocks, treeCSS, filePath) {
|
|
167
|
+
const errors = [];
|
|
168
|
+
|
|
169
|
+
// SCSS에 있지만 tree에 없는 속성 (임의 추가)
|
|
170
|
+
for (const [selector, props] of Object.entries(scssBlocks)) {
|
|
171
|
+
if (!selector.startsWith('.')) continue;
|
|
172
|
+
const treeProps = treeCSS[selector];
|
|
173
|
+
|
|
174
|
+
for (const [prop, value] of Object.entries(props)) {
|
|
175
|
+
// background-image url, background-size 등은 이미지 처리에서 추가됨
|
|
176
|
+
if (prop === 'background-image' || prop === 'background-size' ||
|
|
177
|
+
prop === 'background-position' || prop === 'background-repeat') continue;
|
|
178
|
+
// imageRef 주석 스킵
|
|
179
|
+
if (prop.startsWith('//')) continue;
|
|
180
|
+
|
|
181
|
+
if (!treeProps || !(prop in treeProps)) {
|
|
182
|
+
errors.push({
|
|
183
|
+
priority: 'P2',
|
|
184
|
+
file: filePath,
|
|
185
|
+
type: 'extra-property',
|
|
186
|
+
selector,
|
|
187
|
+
property: prop,
|
|
188
|
+
actual: value,
|
|
189
|
+
message: `SCSS에 있지만 sections.json에 없는 속성: ${prop}: ${value}`
|
|
190
|
+
});
|
|
191
|
+
} else if (treeProps[prop] !== value) {
|
|
192
|
+
errors.push({
|
|
193
|
+
priority: 'P1',
|
|
194
|
+
file: filePath,
|
|
195
|
+
type: 'value-mismatch',
|
|
196
|
+
selector,
|
|
197
|
+
property: prop,
|
|
198
|
+
expected: treeProps[prop],
|
|
199
|
+
actual: value,
|
|
200
|
+
message: `값 불일치: ${prop} expected="${treeProps[prop]}" actual="${value}"`
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// tree에 있지만 SCSS에 없는 속성 (누락)
|
|
207
|
+
for (const [selector, props] of Object.entries(treeCSS)) {
|
|
208
|
+
const scssProps = scssBlocks[selector];
|
|
209
|
+
if (!scssProps) {
|
|
210
|
+
errors.push({
|
|
211
|
+
priority: 'P1',
|
|
212
|
+
file: filePath,
|
|
213
|
+
type: 'missing-selector',
|
|
214
|
+
selector,
|
|
215
|
+
message: `sections.json에 있지만 SCSS에 없는 셀렉터: ${selector}`
|
|
216
|
+
});
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
for (const [prop] of Object.entries(props)) {
|
|
220
|
+
if (!(prop in scssProps)) {
|
|
221
|
+
errors.push({
|
|
222
|
+
priority: 'P1',
|
|
223
|
+
file: filePath,
|
|
224
|
+
type: 'missing-property',
|
|
225
|
+
selector,
|
|
226
|
+
property: prop,
|
|
227
|
+
expected: props[prop],
|
|
228
|
+
message: `SCSS에 누락된 속성: ${selector} { ${prop}: ${props[prop]} }`
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return errors;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ─── Image Filename Check ───────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
function checkImageFilenames(scssContent, filePath) {
|
|
240
|
+
const errors = [];
|
|
241
|
+
const urlRegex = /url\(['"]?([^'")\s]+)['"]?\)/g;
|
|
242
|
+
let match;
|
|
243
|
+
|
|
244
|
+
while ((match = urlRegex.exec(scssContent)) !== null) {
|
|
245
|
+
const imgPath = match[1];
|
|
246
|
+
const filename = path.basename(imgPath, path.extname(imgPath));
|
|
247
|
+
|
|
248
|
+
// 해시 파일명 감지 (16자 이상 hex)
|
|
249
|
+
if (/^[0-9a-f]{16,}$/.test(filename)) {
|
|
250
|
+
errors.push({
|
|
251
|
+
priority: 'P1',
|
|
252
|
+
file: filePath,
|
|
253
|
+
type: 'hash-filename',
|
|
254
|
+
actual: imgPath,
|
|
255
|
+
message: `해시 파일명 금지: ${imgPath} → kebab-case로 변경 필요`
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return errors;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ─── Main ───────────────────────────────────────────────────────────
|
|
264
|
+
|
|
265
|
+
function main() {
|
|
266
|
+
const args = process.argv.slice(2);
|
|
267
|
+
if (args.length < 2) {
|
|
268
|
+
console.error('Usage: node figma-validate.js <scss-dir> <sections.json> [--section=<name>]');
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const scssDir = args[0];
|
|
273
|
+
const sectionsFile = args[1];
|
|
274
|
+
let sectionFilter = '';
|
|
275
|
+
|
|
276
|
+
for (const arg of args.slice(2)) {
|
|
277
|
+
if (arg.startsWith('--section=')) sectionFilter = arg.slice(10);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 입력 읽기
|
|
281
|
+
const data = JSON.parse(fs.readFileSync(sectionsFile, 'utf-8'));
|
|
282
|
+
let sections = data.sections || [];
|
|
283
|
+
|
|
284
|
+
if (sectionFilter) {
|
|
285
|
+
sections = sections.filter(s => s.name === sectionFilter);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const allErrors = [];
|
|
289
|
+
|
|
290
|
+
for (const section of sections) {
|
|
291
|
+
const sectionClass = section.name
|
|
292
|
+
.replace(/[^a-zA-Z0-9\s_-]/g, '')
|
|
293
|
+
.replace(/[\s_]+/g, '-')
|
|
294
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
295
|
+
.toLowerCase()
|
|
296
|
+
.replace(/-+/g, '-')
|
|
297
|
+
.replace(/^-|-$/g, '');
|
|
298
|
+
|
|
299
|
+
const scssFile = path.join(scssDir, `_${sectionClass}.scss`);
|
|
300
|
+
if (!fs.existsSync(scssFile)) {
|
|
301
|
+
allErrors.push({
|
|
302
|
+
priority: 'P1',
|
|
303
|
+
type: 'missing-file',
|
|
304
|
+
expected: scssFile,
|
|
305
|
+
message: `SCSS 파일 없음: ${scssFile}`
|
|
306
|
+
});
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const scssContent = fs.readFileSync(scssFile, 'utf-8');
|
|
311
|
+
|
|
312
|
+
// 1. 금지 패턴 검사
|
|
313
|
+
allErrors.push(...detectForbidden(scssContent, scssFile));
|
|
314
|
+
|
|
315
|
+
// 2. 이미지 파일명 검사
|
|
316
|
+
allErrors.push(...checkImageFilenames(scssContent, scssFile));
|
|
317
|
+
|
|
318
|
+
// 3. CSS 값 대조
|
|
319
|
+
const scssBlocks = parseSCSS(scssContent);
|
|
320
|
+
const treeCSS = {};
|
|
321
|
+
|
|
322
|
+
// 루트 섹션 CSS
|
|
323
|
+
const rootSelector = `.${sectionClass}`;
|
|
324
|
+
if (section.css && Object.keys(section.css).length > 0) {
|
|
325
|
+
treeCSS[rootSelector] = {};
|
|
326
|
+
for (const [prop, value] of Object.entries(section.css)) {
|
|
327
|
+
if (!prop.startsWith('_')) treeCSS[rootSelector][camelToKebab(prop)] = String(value);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 자식 CSS 재귀 수집
|
|
332
|
+
for (const child of (section.children || [])) {
|
|
333
|
+
collectCSSFromTree(child, sectionClass, '', treeCSS);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
allErrors.push(...compareCSSValues(scssBlocks, treeCSS, scssFile));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// 결과 출력
|
|
340
|
+
const p1 = allErrors.filter(e => e.priority === 'P1').length;
|
|
341
|
+
const p2 = allErrors.filter(e => e.priority === 'P2').length;
|
|
342
|
+
|
|
343
|
+
const result = {
|
|
344
|
+
status: p1 === 0 ? 'PASS' : 'FAIL',
|
|
345
|
+
errors: allErrors,
|
|
346
|
+
summary: { p1, p2, total: allErrors.length }
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
console.log(JSON.stringify(result, null, 2));
|
|
350
|
+
process.exit(p1 > 0 ? 1 : 0);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
main();
|
package/package.json
CHANGED
|
@@ -178,4 +178,4 @@ The test generator reads this file and adds custom rules to the test suite.
|
|
|
178
178
|
- `vibe init` → auto-detect and generate initial arch-guard tests
|
|
179
179
|
- `vibe update` → refresh rules if directory structure changed
|
|
180
180
|
- Pre-commit hook → run arch-guard tests before commit
|
|
181
|
-
-
|
|
181
|
+
- `vibe.review` (skill) → architecture-reviewer checks against arch-rules.json
|
|
@@ -107,6 +107,92 @@ Based on classification, build the appropriate artifact:
|
|
|
107
107
|
4. If the capability is a test, verify it FAILS without the fix
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
+
**Termination branches:**
|
|
111
|
+
- ✅ **Capability prevents the failure** → Step 5 PERSIST
|
|
112
|
+
- ❌ **Capability does NOT prevent the failure** → Step 4.5 ESCALATE (re-diagnose or ask user)
|
|
113
|
+
|
|
114
|
+
### Step 4.5: ESCALATE — When VERIFY Fails
|
|
115
|
+
|
|
116
|
+
> **Problem**: The built capability didn't actually prevent the failure. This usually means the initial diagnosis was wrong (picked `Tool` when it needed `Guardrail`), or the failure has multiple missing capabilities.
|
|
117
|
+
>
|
|
118
|
+
> **Do NOT silently proceed** — a sub-standard capability log pollutes `.claude/vibe/capabilities-log.md` and the failure will recur.
|
|
119
|
+
|
|
120
|
+
**Escalation loop:**
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
tried = [current_diagnosis.category] # e.g., ["Tool"]
|
|
124
|
+
|
|
125
|
+
while True:
|
|
126
|
+
# Re-diagnose excluding already-tried categories
|
|
127
|
+
next_diagnosis = diagnose(failure, exclude=tried)
|
|
128
|
+
|
|
129
|
+
if next_diagnosis is None:
|
|
130
|
+
# All 5 categories (Tool/Guardrail/Abstraction/Documentation/Feedback) exhausted
|
|
131
|
+
escalate_to_user(failure, tried)
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
if next_diagnosis.category in tried:
|
|
135
|
+
# Stuck: diagnose keeps returning the same category
|
|
136
|
+
escalate_to_user(failure, tried)
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
tried.append(next_diagnosis.category)
|
|
140
|
+
capability = build(next_diagnosis)
|
|
141
|
+
|
|
142
|
+
if verify(capability, failure):
|
|
143
|
+
persist(capability)
|
|
144
|
+
return # Success — go to Step 5
|
|
145
|
+
|
|
146
|
+
# Still failing — next iteration
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**User escalation prompt (interactive mode):**
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
153
|
+
⚠️ CAPABILITY LOOP STUCK
|
|
154
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
155
|
+
|
|
156
|
+
Failure: {original failure description}
|
|
157
|
+
|
|
158
|
+
Tried capabilities (all failed to prevent the failure):
|
|
159
|
+
❌ Tool: {what was built, why it didn't work}
|
|
160
|
+
❌ Guardrail: {what was built, why it didn't work}
|
|
161
|
+
❌ Documentation: {what was built, why it didn't work}
|
|
162
|
+
|
|
163
|
+
Automated diagnosis has run out of angles. This failure may require
|
|
164
|
+
human judgment (process issue, cross-category solution, or external factor).
|
|
165
|
+
|
|
166
|
+
How would you like to proceed?
|
|
167
|
+
1. Suggest a different angle (e.g., "this is a process issue", "needs Tool+Guardrail combination")
|
|
168
|
+
→ Attempt custom approach per user instruction, then enter next verify
|
|
169
|
+
2. "manual" — resolve this failure via manual intervention, end capability loop
|
|
170
|
+
(record "escalated to manual" in capabilities-log.md)
|
|
171
|
+
3. "abort" — give up, record failure only
|
|
172
|
+
(record "diagnosis exhausted" in capabilities-log.md, do not halt the rest of the workflow)
|
|
173
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**ultrawork mode exception:**
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
if ultrawork_mode:
|
|
180
|
+
# Skip user prompt: try all 5 categories in sequence, record final state
|
|
181
|
+
all_tried_exhausted = exhaust_all_categories(failure, tried)
|
|
182
|
+
record_failure_to_log(
|
|
183
|
+
status="diagnosis_exhausted",
|
|
184
|
+
tried=all_tried_exhausted,
|
|
185
|
+
failure=failure
|
|
186
|
+
)
|
|
187
|
+
return # Proceed without blocking downstream workflow
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Rollback of failed builds:**
|
|
191
|
+
|
|
192
|
+
- Each failed capability build should be rolled back before trying the next category (unless it's non-destructive documentation).
|
|
193
|
+
- For code additions (tool/guardrail/abstraction): `git checkout -- {files}` or delete created files.
|
|
194
|
+
- For docs-only additions: leave in place (low risk) but note in escalation prompt.
|
|
195
|
+
|
|
110
196
|
### Step 5: PERSIST — Record for Future Reference
|
|
111
197
|
|
|
112
198
|
Save the capability-building decision:
|
|
@@ -126,11 +212,14 @@ Update `.claude/vibe/capabilities-log.md`:
|
|
|
126
212
|
```markdown
|
|
127
213
|
## {date} — {capability-name}
|
|
128
214
|
|
|
215
|
+
**Status**: resolved | escalated_to_manual | diagnosis_exhausted
|
|
129
216
|
**Failure**: {what happened}
|
|
130
217
|
**Missing**: {what capability was absent}
|
|
131
|
-
**
|
|
218
|
+
**Tried**: {list of categories attempted, e.g., [Tool, Guardrail]}
|
|
219
|
+
**Built**: {what was created (if resolved)}
|
|
132
220
|
**Files**: {list}
|
|
133
|
-
**Prevents**: {what class of failures this prevents}
|
|
221
|
+
**Prevents**: {what class of failures this prevents (if resolved)}
|
|
222
|
+
**Notes**: {for escalated/exhausted — what the user decided or what remains unsolved}
|
|
134
223
|
```
|
|
135
224
|
|
|
136
225
|
## Decision Tree
|
|
@@ -152,6 +241,21 @@ Agent failed
|
|
|
152
241
|
│
|
|
153
242
|
└─ Did the agent not KNOW it was wrong?
|
|
154
243
|
YES → Build feedback (types/errors/tests)
|
|
244
|
+
|
|
245
|
+
↓
|
|
246
|
+
VERIFY: Does the built capability actually prevent the failure?
|
|
247
|
+
│
|
|
248
|
+
├─ YES → PERSIST (log as "resolved")
|
|
249
|
+
│
|
|
250
|
+
└─ NO → ESCALATE (Step 4.5)
|
|
251
|
+
│
|
|
252
|
+
├─ Other categories untried? → Re-diagnose with exclusion list
|
|
253
|
+
│ → BUILD next category → VERIFY
|
|
254
|
+
│
|
|
255
|
+
└─ All categories tried OR same diagnosis loops:
|
|
256
|
+
│
|
|
257
|
+
├─ Interactive: Ask user (custom angle / manual / abort)
|
|
258
|
+
└─ ultrawork: Auto-record "diagnosis_exhausted" → continue
|
|
155
259
|
```
|
|
156
260
|
|
|
157
261
|
## Anti-patterns
|
|
@@ -1,86 +1,86 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: chub-usage
|
|
3
3
|
tier: optional
|
|
4
|
-
description: "Context Hub (chub) —
|
|
4
|
+
description: "Context Hub (chub) — fetch vetted, up-to-date API documentation. Write accurate code based on the latest docs instead of training data when working with external APIs/SDKs."
|
|
5
5
|
triggers: [chub, context hub, API docs, latest API, deprecated API, SDK documentation, api reference, 최신 문서]
|
|
6
6
|
priority: 65
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Context Hub (chub) Usage
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
A skill for fetching vetted, up-to-date documentation before writing external API/SDK code.
|
|
12
|
+
Solves the knowledge cutoff problem inherent in training data.
|
|
13
13
|
|
|
14
14
|
## Why?
|
|
15
15
|
|
|
16
16
|
| Problem | Solution |
|
|
17
17
|
|---------|----------|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
18
|
+
| Relying on training data → using deprecated APIs | chub get → code based on vetted latest docs |
|
|
19
|
+
| Web search → noisy results | chub search → curated docs only |
|
|
20
|
+
| Repeating the same mistakes every session | chub annotate → accumulated learnings |
|
|
21
21
|
|
|
22
22
|
## When to Use
|
|
23
23
|
|
|
24
24
|
| Situation | Example |
|
|
25
25
|
|-----------|---------|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
26
|
+
| Writing external API code | "Integrate Stripe payments" |
|
|
27
|
+
| Checking latest SDK version | "Call the latest OpenAI model" |
|
|
28
|
+
| Need official documentation | "Set up Supabase auth" |
|
|
29
|
+
| Preventing deprecated patterns | "Firebase v10 migration" |
|
|
30
30
|
|
|
31
31
|
## Workflow
|
|
32
32
|
|
|
33
33
|
```
|
|
34
|
-
|
|
34
|
+
Request to write external API/SDK code
|
|
35
35
|
↓
|
|
36
|
-
Step 0: chub
|
|
36
|
+
Step 0: Check if chub is installed (auto-install if not)
|
|
37
37
|
↓
|
|
38
|
-
Step 1: chub search "
|
|
38
|
+
Step 1: chub search "<library name>"
|
|
39
39
|
↓
|
|
40
40
|
Step 2: chub get <id> --lang ts
|
|
41
41
|
↓
|
|
42
|
-
Step 3:
|
|
42
|
+
Step 3: Write code based on the docs
|
|
43
43
|
↓
|
|
44
|
-
Step 4:
|
|
44
|
+
Step 4: chub annotate when a gotcha is discovered
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
## Step 0 —
|
|
47
|
+
## Step 0 — Auto-install
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
**Always perform this step before running the skill.**
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
# 1.
|
|
52
|
+
# 1. Check if chub exists
|
|
53
53
|
which chub || command -v chub
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
chub
|
|
56
|
+
If chub is not found, attempt automatic installation:
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
59
|
npm install -g @aisuite/chub
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
If installation fails, fall back to running via `npx @aisuite/chub`:
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
|
-
# search
|
|
65
|
+
# search example
|
|
66
66
|
npx @aisuite/chub search "stripe"
|
|
67
|
-
# get
|
|
67
|
+
# get example
|
|
68
68
|
npx @aisuite/chub get stripe/api --lang ts
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
`npx
|
|
71
|
+
If `npx` also fails, fall back to context7 or Web Search (see Fallback Chain).
|
|
72
72
|
|
|
73
73
|
## Usage
|
|
74
74
|
|
|
75
|
-
### Step 1 —
|
|
75
|
+
### Step 1 — Search for docs
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
78
|
chub search "stripe"
|
|
79
79
|
chub search "openai"
|
|
80
|
-
chub search "" #
|
|
80
|
+
chub search "" # View full list
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
### Step 2 —
|
|
83
|
+
### Step 2 — Fetch latest docs
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
86
|
chub get stripe/api --lang ts
|
|
@@ -88,26 +88,26 @@ chub get openai/chat --lang py
|
|
|
88
88
|
chub get supabase/auth --lang js
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
### Step 3 —
|
|
91
|
+
### Step 3 — Write code based on the docs
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
Write accurate code based on the fetched documentation.
|
|
94
|
+
**Never rely on training data. Docs first, code second.**
|
|
95
95
|
|
|
96
|
-
### Step 4 —
|
|
96
|
+
### Step 4 — Record learnings
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
Gotchas, workarounds, and version issues discovered during work:
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
|
-
chub annotate stripe/api "
|
|
102
|
-
chub annotate openai/chat "streaming
|
|
103
|
-
chub annotate firebase/auth "
|
|
101
|
+
chub annotate stripe/api "pg parameter is required for Korean payments"
|
|
102
|
+
chub annotate openai/chat "tool_calls in streaming comes as delta"
|
|
103
|
+
chub annotate firebase/auth "getAuth() import path changed in v10"
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
Annotations are stored locally and automatically included the next time you run `chub get`.
|
|
107
107
|
|
|
108
108
|
## Implementation Pattern (Subagent)
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
Run via subagent to prevent context bloat:
|
|
111
111
|
|
|
112
112
|
```
|
|
113
113
|
Task tool call:
|
|
@@ -116,24 +116,24 @@ Task tool call:
|
|
|
116
116
|
- prompt: "Run `chub search <library>` then `chub get <id> --lang <lang>` to fetch latest API documentation for [topic]. Return only the relevant API usage examples, key changes from previous versions, and any annotations."
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
The subagent handles the chub calls and returns only a summary — keeping the main context clean.
|
|
120
120
|
|
|
121
121
|
## Supported APIs (1,000+)
|
|
122
122
|
|
|
123
|
-
OpenAI, Anthropic, Stripe, Firebase, Supabase, Vercel, AWS S3, Cloudflare Workers, Auth0, Clerk
|
|
123
|
+
OpenAI, Anthropic, Stripe, Firebase, Supabase, Vercel, AWS S3, Cloudflare Workers, Auth0, Clerk, and more.
|
|
124
124
|
|
|
125
125
|
```bash
|
|
126
|
-
chub search #
|
|
126
|
+
chub search # Run without arguments to view the full list
|
|
127
127
|
```
|
|
128
128
|
|
|
129
129
|
## Fallback Chain
|
|
130
130
|
|
|
131
131
|
```
|
|
132
|
-
which chub
|
|
132
|
+
which chub fails
|
|
133
133
|
↓
|
|
134
|
-
npm install -g @aisuite/chub (
|
|
134
|
+
npm install -g @aisuite/chub (auto attempt)
|
|
135
135
|
↓
|
|
136
|
-
|
|
136
|
+
On failure: npx @aisuite/chub <command> (temporary execution)
|
|
137
137
|
↓
|
|
138
|
-
npx
|
|
138
|
+
If npx also fails: context7 or Web Search fallback
|
|
139
139
|
```
|