Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@su-record/vibe

Package Overview
Dependencies
Maintainers
1
Versions
352
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@su-record/vibe - npm Package Compare versions

Comparing version
2.9.38
to
2.9.40
+157
hooks/scripts/__tests__/curation-index.test.js
/**
* curation-index.js — Phase 3 인덱스 로더 테스트
*
* session-start.js 가 다음 세션에 prepend 할 1줄 요약을 만드는 모듈.
* 본격 YAML parser 의존 없이 우리가 작성한 frontmatter 형식만 처리한다.
*/
import { describe, it, expect, beforeEach } from 'vitest';
import fs from 'fs';
import os from 'os';
import path from 'path';
import { loadCurationIndex, _internal } from '../lib/curation-index.js';
const { parseFrontmatter } = _internal;
function makeTmpProject() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'vibe-curation-index-'));
}
function writeRecipe(projectDir, filename, frontmatter, body = 'body text') {
const dir = path.join(projectDir, '.vibe', 'recipes');
fs.mkdirSync(dir, { recursive: true });
const fm = Object.entries(frontmatter)
.map(([k, v]) => typeof v === 'string' ? `${k}: "${v}"` : `${k}: ${v}`)
.join('\n');
fs.writeFileSync(path.join(dir, filename), `---\n${fm}\n---\n\n${body}\n`);
}
function writeAntiPattern(projectDir, filename, frontmatter, body = 'body') {
const dir = path.join(projectDir, '.vibe', 'anti-patterns');
fs.mkdirSync(dir, { recursive: true });
const fm = Object.entries(frontmatter)
.map(([k, v]) => typeof v === 'string' ? `${k}: "${v}"` : `${k}: ${v}`)
.join('\n');
fs.writeFileSync(path.join(dir, filename), `---\n${fm}\n---\n\n${body}\n`);
}
describe('parseFrontmatter', () => {
it('단순 key-value 파싱', () => {
const input = `---\nslug: foo\ntype: recipe\n---\nbody`;
expect(parseFrontmatter(input)).toEqual({ slug: 'foo', type: 'recipe' });
});
it('따옴표로 감싼 값', () => {
const input = `---\nrecipe: "use claude --model"\n---\n`;
expect(parseFrontmatter(input).recipe).toBe('use claude --model');
});
it('이스케이프된 따옴표', () => {
const input = `---\nmsg: "say \\"hi\\""\n---\n`;
expect(parseFrontmatter(input).msg).toBe('say "hi"');
});
it('frontmatter 가 없으면 null', () => {
expect(parseFrontmatter('no frontmatter here')).toBeNull();
});
it('알 수 없는 라인 무시', () => {
const input = `---\nslug: foo\n# comment line\nrandom garbage\ntype: recipe\n---\n`;
expect(parseFrontmatter(input)).toEqual({ slug: 'foo', type: 'recipe' });
});
});
describe('loadCurationIndex', () => {
let projectDir;
beforeEach(() => {
projectDir = makeTmpProject();
});
it('빈 .vibe/ 면 빈 인덱스', () => {
const idx = loadCurationIndex(projectDir);
expect(idx.recipes).toEqual([]);
expect(idx.antiPatterns).toEqual([]);
});
it('recipe 1개 로드', () => {
writeRecipe(projectDir, 'a.md', {
slug: 'login__20260507-100000',
type: 'recipe',
'symptom-context': 'login flow',
recipe: 'pin model and retry transient',
created: '2026-05-07',
});
const idx = loadCurationIndex(projectDir);
expect(idx.recipes).toHaveLength(1);
expect(idx.recipes[0]).toEqual({
slug: 'login__20260507-100000',
summary: 'pin model and retry transient',
});
});
it('recipe 정렬: created 내림차순', () => {
writeRecipe(projectDir, 'old.md', { slug: 'old', recipe: 'old', created: '2026-01-01' });
writeRecipe(projectDir, 'mid.md', { slug: 'mid', recipe: 'mid', created: '2026-03-01' });
writeRecipe(projectDir, 'new.md', { slug: 'new', recipe: 'new', created: '2026-05-01' });
const idx = loadCurationIndex(projectDir);
expect(idx.recipes.map((r) => r.slug)).toEqual(['new', 'mid', 'old']);
});
it('limit 적용', () => {
for (let i = 0; i < 10; i++) {
writeRecipe(projectDir, `r${i}.md`, {
slug: `r${i}`,
recipe: `recipe ${i}`,
created: `2026-05-${String(i + 1).padStart(2, '0')}`,
});
}
const idx = loadCurationIndex(projectDir, { recipeLimit: 3 });
expect(idx.recipes).toHaveLength(3);
expect(idx.recipes[0].slug).toBe('r9');
});
it('anti-pattern 로드 — tag + suggested-stop', () => {
writeAntiPattern(projectDir, 'a.md', {
slug: 'nullability__src__20260507',
type: 'anti-pattern',
'root-cause-tag': 'nullability',
'trigger-signature': '(file=src/x.ts, category=nullability)',
'suggested-stop': '같은 위치 null 처리 반복 — 타입 가드 필요',
created: '2026-05-07',
});
const idx = loadCurationIndex(projectDir);
expect(idx.antiPatterns).toHaveLength(1);
expect(idx.antiPatterns[0].tag).toBe('nullability');
expect(idx.antiPatterns[0].summary).toBe('같은 위치 null 처리 반복 — 타입 가드 필요');
});
it('frontmatter 손상 파일은 스킵', () => {
writeRecipe(projectDir, 'good.md', { slug: 'good', recipe: 'works', created: '2026-05-07' });
fs.writeFileSync(path.join(projectDir, '.vibe', 'recipes', 'broken.md'), 'no frontmatter');
fs.writeFileSync(path.join(projectDir, '.vibe', 'recipes', 'no-slug.md'), '---\ntype: recipe\n---\n');
const idx = loadCurationIndex(projectDir);
expect(idx.recipes).toHaveLength(1);
expect(idx.recipes[0].slug).toBe('good');
});
it('README.md, _cluster*.md 등 메타 파일 제외', () => {
writeRecipe(projectDir, 'r.md', { slug: 'r', recipe: 'good', created: '2026-05-07' });
fs.writeFileSync(path.join(projectDir, '.vibe', 'recipes', 'README.md'), '---\nslug: should-skip\n---\n');
fs.writeFileSync(path.join(projectDir, '.vibe', 'recipes', '_cluster-x.md'), '---\nslug: should-skip\n---\n');
const idx = loadCurationIndex(projectDir);
expect(idx.recipes.map((r) => r.slug)).toEqual(['r']);
});
it('recipe + anti-pattern 동시 로드 (독립)', () => {
writeRecipe(projectDir, 'r.md', { slug: 'r', recipe: 'do this', created: '2026-05-07' });
writeAntiPattern(projectDir, 'a.md', {
slug: 'a',
'root-cause-tag': 'auth',
'suggested-stop': 'check tokens',
created: '2026-05-07',
});
const idx = loadCurationIndex(projectDir);
expect(idx.recipes).toHaveLength(1);
expect(idx.antiPatterns).toHaveLength(1);
});
});
/**
* recipe-extractor.js — Phase 3 테스트
*
* 검증 범위:
* - 휴리스틱 게이트: total_tools≥8 AND fails≥3 AND last=success
* - VIBE_RECIPE_LLM=mock 으로 LLM 우회 (외부 의존 0)
* - frontmatter schema 충족
* - silent fail 정책 (jsonl 없음 / 게이트 실패 / 디렉토리 부재)
*
* 외부 의존:
* - 실제 claude CLI 는 호출하지 않음 (mock 모드 사용)
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { spawnSync } from 'child_process';
import fs from 'fs';
import os from 'os';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SCRIPT = path.resolve(__dirname, '..', 'recipe-extractor.js');
function makeTmpProject() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'vibe-recipe-extractor-'));
}
function writeJsonl(projectDir, records) {
const dir = path.join(projectDir, '.vibe', 'metrics');
fs.mkdirSync(dir, { recursive: true });
const lines = records.map((r) => JSON.stringify(r)).join('\n') + '\n';
fs.writeFileSync(path.join(dir, 'current-run.jsonl'), lines);
}
function writeRunMeta(projectDir, meta) {
const dir = path.join(projectDir, '.vibe', 'metrics');
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, 'current-run.json'), JSON.stringify(meta));
}
function runExtractor(projectDir, { mock = true } = {}) {
const result = spawnSync('node', [SCRIPT], {
encoding: 'utf-8',
timeout: 5000,
env: {
...process.env,
CLAUDE_PROJECT_DIR: projectDir,
...(mock ? { VIBE_RECIPE_LLM: 'mock' } : {}),
VIBE_HOOK_DEPTH: '0',
},
});
return result;
}
function listRecipes(projectDir) {
const dir = path.join(projectDir, '.vibe', 'recipes');
if (!fs.existsSync(dir)) return [];
return fs.readdirSync(dir).filter((f) => f.endsWith('.md'));
}
function makeRecord({ tool = 'Bash', ok = true, file = null, category = null }) {
return {
ts: new Date().toISOString(),
tool,
ok,
target_file: file,
error_category: ok ? null : category,
};
}
describe('recipe-extractor (Phase 3)', () => {
let projectDir;
beforeEach(() => {
projectDir = makeTmpProject();
});
// ───────── 게이트 통과 조건 ─────────
describe('휴리스틱 게이트', () => {
it('총 ≥8 + 실패 ≥3 + 마지막 성공 → recipe 생성', () => {
const records = [
makeRecord({ ok: false, category: 'integration' }),
makeRecord({ ok: false, category: 'integration' }),
makeRecord({ ok: false, category: 'auth' }),
makeRecord({ ok: true }),
makeRecord({ ok: true }),
makeRecord({ ok: true, tool: 'Edit', file: 'src/foo.ts' }),
makeRecord({ ok: true, tool: 'Read', file: 'src/foo.ts' }),
makeRecord({ ok: true, tool: 'Bash' }), // 마지막=성공
];
writeJsonl(projectDir, records);
writeRunMeta(projectDir, { feature: 'auth-fix', startedAt: '2026-05-07T00:00:00.000Z', steps: 8 });
const r = runExtractor(projectDir);
expect(r.status).toBe(0);
expect(listRecipes(projectDir)).toHaveLength(1);
});
it('툴콜 부족 (<8) → 생성 안 함', () => {
const records = [
makeRecord({ ok: false, category: 'integration' }),
makeRecord({ ok: false, category: 'integration' }),
makeRecord({ ok: false, category: 'integration' }),
makeRecord({ ok: true }),
];
writeJsonl(projectDir, records);
writeRunMeta(projectDir, { feature: 'x' });
runExtractor(projectDir);
expect(listRecipes(projectDir)).toHaveLength(0);
});
it('실패 부족 (<3) → 생성 안 함', () => {
const records = Array.from({ length: 10 }, () => makeRecord({ ok: true }));
records[0] = makeRecord({ ok: false, category: 'other' });
records[1] = makeRecord({ ok: false, category: 'other' });
writeJsonl(projectDir, records);
writeRunMeta(projectDir, { feature: 'x' });
runExtractor(projectDir);
expect(listRecipes(projectDir)).toHaveLength(0);
});
it('마지막 호출이 실패 → 생성 안 함 (task 미완료)', () => {
const records = [
...Array.from({ length: 5 }, () => makeRecord({ ok: true })),
makeRecord({ ok: false, category: 'integration' }),
makeRecord({ ok: false, category: 'integration' }),
makeRecord({ ok: false, category: 'integration' }),
];
writeJsonl(projectDir, records);
writeRunMeta(projectDir, { feature: 'x' });
runExtractor(projectDir);
expect(listRecipes(projectDir)).toHaveLength(0);
});
});
// ───────── frontmatter 검증 ─────────
describe('frontmatter schema', () => {
function makeQualifyingRun() {
const records = [
makeRecord({ tool: 'Bash', ok: false, category: 'integration' }),
makeRecord({ tool: 'Bash', ok: false, category: 'integration' }),
makeRecord({ tool: 'Edit', file: 'src/foo.ts', ok: false, category: 'type-narrow' }),
makeRecord({ tool: 'Edit', file: 'src/foo.ts', ok: true }),
makeRecord({ tool: 'Read', file: 'src/foo.ts', ok: true }),
makeRecord({ tool: 'Bash', ok: true }),
makeRecord({ tool: 'Bash', ok: true }),
makeRecord({ tool: 'Bash', ok: true }),
];
writeJsonl(projectDir, records);
writeRunMeta(projectDir, { feature: 'login flow', startedAt: '2026-05-07T10:00:00.000Z', steps: 8 });
}
it('생성된 recipe 의 frontmatter 가 schema 충족', () => {
makeQualifyingRun();
runExtractor(projectDir);
const [filename] = listRecipes(projectDir);
const content = fs.readFileSync(path.join(projectDir, '.vibe', 'recipes', filename), 'utf-8');
expect(content).toMatch(/^---\n/);
expect(content).toMatch(/^slug: login-flow__\d{8}-\d{6}$/m);
expect(content).toMatch(/^type: recipe$/m);
expect(content).toMatch(/^symptom-context: "login flow"$/m);
expect(content).toMatch(/^recipe: ".+"$/m);
expect(content).toMatch(/^tools-touched: \[/m);
expect(content).toMatch(/^retry-count-saved: 3$/m);
expect(content).toMatch(/^created: \d{4}-\d{2}-\d{2}$/m);
expect(content).toMatch(/^source-run: "2026-05-07T10:00:00\.000Z"$/m);
expect(content).toMatch(/^confidence: low$/m);
});
it('파일명이 timestamp slug 포함', () => {
makeQualifyingRun();
runExtractor(projectDir);
const [filename] = listRecipes(projectDir);
expect(filename).toMatch(/^login-flow__\d{8}-\d{6}\.md$/);
});
it('feature 누락 시 anon slug', () => {
const records = Array.from({ length: 8 }, (_, i) => makeRecord({
ok: i < 3 ? false : true,
category: i < 3 ? 'integration' : null,
}));
writeJsonl(projectDir, records);
writeRunMeta(projectDir, { feature: null });
runExtractor(projectDir);
const [filename] = listRecipes(projectDir);
expect(filename).toMatch(/^anon__/);
});
});
// ───────── 안전성 ─────────
describe('silent fail 정책', () => {
it('jsonl 없음 → exit 0, 파일 생성 안 함', () => {
const r = runExtractor(projectDir);
expect(r.status).toBe(0);
expect(listRecipes(projectDir)).toHaveLength(0);
});
it('빈 jsonl → exit 0, 생성 안 함', () => {
writeJsonl(projectDir, []);
const r = runExtractor(projectDir);
expect(r.status).toBe(0);
expect(listRecipes(projectDir)).toHaveLength(0);
});
it('VIBE_HOOK_DEPTH≥1 → 재귀 가드, 생성 안 함', () => {
const records = [
...Array.from({ length: 5 }, () => makeRecord({ ok: false, category: 'integration' })),
makeRecord({ ok: true }), makeRecord({ ok: true }), makeRecord({ ok: true }),
];
writeJsonl(projectDir, records);
writeRunMeta(projectDir, { feature: 'x' });
const r = spawnSync('node', [SCRIPT], {
encoding: 'utf-8',
timeout: 5000,
env: {
...process.env,
CLAUDE_PROJECT_DIR: projectDir,
VIBE_RECIPE_LLM: 'mock',
VIBE_HOOK_DEPTH: '1',
},
});
expect(r.status).toBe(0);
expect(listRecipes(projectDir)).toHaveLength(0);
});
it('손상된 jsonl 라인은 무시', () => {
const dir = path.join(projectDir, '.vibe', 'metrics');
fs.mkdirSync(dir, { recursive: true });
// 일부 라인 손상 + 8개 정상
const good = Array.from({ length: 8 }, (_, i) => JSON.stringify(makeRecord({
ok: i < 3 ? false : true,
category: i < 3 ? 'integration' : null,
})));
const lines = ['{ broken', ...good].join('\n') + '\n';
fs.writeFileSync(path.join(dir, 'current-run.jsonl'), lines);
writeRunMeta(projectDir, { feature: 'x' });
const r = runExtractor(projectDir);
expect(r.status).toBe(0);
expect(listRecipes(projectDir)).toHaveLength(1);
});
});
});
/**
* step-counter.js — Phase 1 + Phase 2 테스트
*
* 검증 범위:
* - 책임 1) current-run.json steps 증가 (회귀 테스트)
* - 책임 2) current-run.jsonl append (Phase 1)
* - 책임 3) error_category 분류기 + 3-fail detector → anti-pattern md (Phase 2)
* - 책임 간 독립성, hot-path 안정성
*
* 패턴: keyword-detector.test.js 와 동일한 execFileSync 방식.
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { spawnSync } from 'child_process';
import fs from 'fs';
import os from 'os';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SCRIPT = path.resolve(__dirname, '..', 'step-counter.js');
/**
* 격리된 임시 프로젝트에서 step-counter 를 1회 실행.
* stdin 으로 PostToolUse payload 전달.
*/
function runCounter({ payload, projectDir }) {
const result = spawnSync('node', [SCRIPT], {
input: payload ? JSON.stringify(payload) : '',
encoding: 'utf-8',
timeout: 5000,
env: {
...process.env,
CLAUDE_PROJECT_DIR: projectDir,
// VIBE_HOOK_DEPTH 미설정 — 재귀 가드 영향 없음
},
});
return result;
}
function makeTmpProject() {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'vibe-step-counter-'));
// .vibe/metrics/ 는 step-counter 가 직접 생성하도록 미리 만들지 않음
return dir;
}
function readJson(p) {
return JSON.parse(fs.readFileSync(p, 'utf-8'));
}
function readJsonl(p) {
return fs.readFileSync(p, 'utf-8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
}
describe('step-counter PostToolUse hook', () => {
let projectDir;
let runJson;
let runJsonl;
beforeEach(() => {
projectDir = makeTmpProject();
runJson = path.join(projectDir, '.vibe', 'metrics', 'current-run.json');
runJsonl = path.join(projectDir, '.vibe', 'metrics', 'current-run.jsonl');
});
// ───────── 책임 1 회귀 ─────────
describe('책임 1: current-run.json 카운터', () => {
it('첫 호출 시 steps=1, startedAt 채움', () => {
const r = runCounter({
payload: { tool_name: 'Bash', tool_input: { command: 'ls' }, tool_response: { is_error: false } },
projectDir,
});
expect(r.status).toBe(0);
const data = readJson(runJson);
expect(data.steps).toBe(1);
expect(data.startedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
});
it('연속 호출 시 steps 누적', () => {
for (let i = 0; i < 3; i++) {
runCounter({
payload: { tool_name: 'Bash', tool_input: { command: 'echo hi' }, tool_response: {} },
projectDir,
});
}
const data = readJson(runJson);
expect(data.steps).toBe(3);
});
it('손상된 JSON 이 있어도 새로 시작', () => {
fs.mkdirSync(path.join(projectDir, '.vibe', 'metrics'), { recursive: true });
fs.writeFileSync(runJson, '{ broken json');
const r = runCounter({
payload: { tool_name: 'Bash', tool_input: {}, tool_response: {} },
projectDir,
});
expect(r.status).toBe(0);
const data = readJson(runJson);
expect(data.steps).toBe(1);
});
});
// ───────── 책임 2 신규 ─────────
describe('책임 2: current-run.jsonl 로깅', () => {
it('툴콜 1회 = jsonl 1라인', () => {
runCounter({
payload: { tool_name: 'Edit', tool_input: { file_path: 'src/foo.ts' }, tool_response: {} },
projectDir,
});
const lines = readJsonl(runJsonl);
expect(lines).toHaveLength(1);
expect(lines[0].tool).toBe('Edit');
expect(lines[0].ok).toBe(true);
expect(lines[0].target_file).toBe('src/foo.ts');
expect(lines[0].error_category).toBeNull();
expect(lines[0].ts).toMatch(/^\d{4}-\d{2}-\d{2}T/);
});
it('tool_response.is_error=true 면 ok=false', () => {
runCounter({
payload: { tool_name: 'Bash', tool_input: { command: 'false' }, tool_response: { is_error: true } },
projectDir,
});
const [line] = readJsonl(runJsonl);
expect(line.ok).toBe(false);
});
it('절대 경로 file_path 를 프로젝트 상대 경로로 정규화', () => {
const abs = path.join(projectDir, 'src', 'bar.ts');
runCounter({
payload: { tool_name: 'Write', tool_input: { file_path: abs }, tool_response: {} },
projectDir,
});
const [line] = readJsonl(runJsonl);
expect(line.target_file).toBe('src/bar.ts');
});
it('file_path 없는 툴콜은 target_file=null', () => {
runCounter({
payload: { tool_name: 'Bash', tool_input: { command: 'pwd' }, tool_response: {} },
projectDir,
});
const [line] = readJsonl(runJsonl);
expect(line.target_file).toBeNull();
});
it('tool_name 없으면 jsonl 라인 안 씀 (steps 는 증가)', () => {
runCounter({ payload: {}, projectDir });
expect(fs.existsSync(runJsonl)).toBe(false);
expect(readJson(runJson).steps).toBe(1);
});
it('연속 호출 시 jsonl 누적', () => {
runCounter({
payload: { tool_name: 'Read', tool_input: { file_path: 'a.ts' }, tool_response: {} },
projectDir,
});
runCounter({
payload: { tool_name: 'Edit', tool_input: { file_path: 'a.ts' }, tool_response: {} },
projectDir,
});
const lines = readJsonl(runJsonl);
expect(lines).toHaveLength(2);
expect(lines.map((l) => l.tool)).toEqual(['Read', 'Edit']);
});
});
// ───────── 독립성 ─────────
describe('두 책임의 독립성', () => {
it('payload 가 비어 stdin 무효여도 카운터는 증가', () => {
const r = runCounter({ payload: null, projectDir });
expect(r.status).toBe(0);
expect(readJson(runJson).steps).toBe(1);
});
});
// ───────── Phase 2: error_category 분류 ─────────
describe('책임 3a: error_category 분류기', () => {
it('TypeError of undefined → nullability', () => {
runCounter({
payload: {
tool_name: 'Bash',
tool_input: { command: 'node x.js' },
tool_response: { is_error: true, error: "TypeError: Cannot read properties of undefined (reading 'foo')" },
},
projectDir,
});
const [line] = readJsonl(runJsonl);
expect(line.error_category).toBe('nullability');
});
it('TS2345 → type-narrow', () => {
runCounter({
payload: {
tool_name: 'Bash',
tool_input: { command: 'tsc' },
tool_response: { is_error: true, error: "src/x.ts(3,4): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'." },
},
projectDir,
});
const [line] = readJsonl(runJsonl);
expect(line.error_category).toBe('type-narrow');
});
it('ECONNREFUSED → integration', () => {
runCounter({
payload: {
tool_name: 'Bash',
tool_input: { command: 'curl' },
tool_response: { is_error: true, error: 'connect ECONNREFUSED 127.0.0.1:5432' },
},
projectDir,
});
const [line] = readJsonl(runJsonl);
expect(line.error_category).toBe('integration');
});
it('알 수 없는 에러 → other', () => {
runCounter({
payload: {
tool_name: 'Bash',
tool_input: { command: 'x' },
tool_response: { is_error: true, error: 'some unrecognized failure mode 12345' },
},
projectDir,
});
const [line] = readJsonl(runJsonl);
expect(line.error_category).toBe('other');
});
it('성공 툴콜은 error_category=null', () => {
runCounter({
payload: { tool_name: 'Bash', tool_input: { command: 'ls' }, tool_response: { is_error: false } },
projectDir,
});
const [line] = readJsonl(runJsonl);
expect(line.ok).toBe(true);
expect(line.error_category).toBeNull();
});
});
// ───────── Phase 2: 3-fail detector ─────────
describe('책임 3b: 3-fail detector → anti-pattern md', () => {
function failBash(command, errorText, projectDir) {
runCounter({
payload: {
tool_name: 'Bash',
tool_input: { command },
tool_response: { is_error: true, error: errorText },
},
projectDir,
});
}
function listAntiPatterns(projectDir) {
const dir = path.join(projectDir, '.vibe', 'anti-patterns');
if (!fs.existsSync(dir)) return [];
return fs.readdirSync(dir).filter((f) => f.endsWith('.md'));
}
it('같은 (file, category) 3회 누적 시 anti-pattern md 생성', () => {
const err = "TypeError: Cannot read properties of undefined (reading 'x')";
// file_path 없는 Bash 실패 3회 → target_file=null, category=nullability
for (let i = 0; i < 3; i++) failBash(`run-${i}`, err, projectDir);
const files = listAntiPatterns(projectDir);
expect(files).toHaveLength(1);
expect(files[0]).toMatch(/^nullability__global__\d{8}\.md$/);
});
it('2회만 누적되면 생성 안 함', () => {
const err = 'connect ECONNREFUSED foo';
for (let i = 0; i < 2; i++) failBash(`x-${i}`, err, projectDir);
expect(listAntiPatterns(projectDir)).toHaveLength(0);
});
it('3회지만 카테고리 다르면 생성 안 함', () => {
failBash('a', "Cannot read properties of undefined", projectDir); // nullability
failBash('b', "ECONNREFUSED", projectDir); // integration
failBash('c', "TS2345 not assignable", projectDir); // type-narrow
expect(listAntiPatterns(projectDir)).toHaveLength(0);
});
it('동일 (file, category) 4회 — md 1개만 (dedup)', () => {
const err = "TypeError: Cannot read properties of undefined";
for (let i = 0; i < 4; i++) failBash(`r-${i}`, err, projectDir);
expect(listAntiPatterns(projectDir)).toHaveLength(1);
});
it('frontmatter 가 schema 충족', () => {
const err = "TypeError: Cannot read properties of null";
for (let i = 0; i < 3; i++) failBash(`r-${i}`, err, projectDir);
const [filename] = listAntiPatterns(projectDir);
const content = fs.readFileSync(path.join(projectDir, '.vibe', 'anti-patterns', filename), 'utf-8');
expect(content).toMatch(/^---\n/);
expect(content).toMatch(/^slug: nullability__global__\d{8}$/m);
expect(content).toMatch(/^type: anti-pattern$/m);
expect(content).toMatch(/^root-cause-tag: nullability$/m);
expect(content).toMatch(/^trigger-signature: "/m);
expect(content).toMatch(/^fail-count: 3$/m);
expect(content).toMatch(/^suggested-stop: "/m);
expect(content).toMatch(/^created: \d{4}-\d{2}-\d{2}$/m);
});
it('파일 경로 있는 실패는 file slug 사용', () => {
const filePath = 'src/cli/foo.ts';
const err = "TypeError: Cannot read properties of undefined";
for (let i = 0; i < 3; i++) {
runCounter({
payload: {
tool_name: 'Edit',
tool_input: { file_path: filePath },
tool_response: { is_error: true, error: err },
},
projectDir,
});
}
const [filename] = listAntiPatterns(projectDir);
expect(filename).toMatch(/^nullability__src-cli-foo-ts__\d{8}\.md$/);
});
it('윈도우 외 실패는 카운트 안 됨', () => {
const err = "TypeError: Cannot read properties of undefined";
// 같은 카테고리 실패 2회
failBash('a', err, projectDir);
failBash('b', err, projectDir);
// 성공 툴콜로 윈도우 채움 (10줄 이상)
for (let i = 0; i < 10; i++) {
runCounter({
payload: { tool_name: 'Read', tool_input: { file_path: `f${i}.ts` }, tool_response: {} },
projectDir,
});
}
// 마지막 실패 1회 — 윈도우(10줄) 안에는 이 실패 + 직전 성공만 있으므로 트립 안 함
failBash('c', err, projectDir);
expect(listAntiPatterns(projectDir)).toHaveLength(0);
});
});
// ───────── 차단 금지 ─────────
describe('hot path 안정성', () => {
it('항상 exit 0', () => {
const r = runCounter({
payload: { tool_name: 'Bash', tool_input: { command: 'x' }, tool_response: {} },
projectDir,
});
expect(r.status).toBe(0);
});
it('잘못된 stdin JSON 도 차단 안 함', () => {
const r = spawnSync('node', [SCRIPT], {
input: 'not json at all',
encoding: 'utf-8',
timeout: 5000,
env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir },
});
expect(r.status).toBe(0);
});
});
});
/**
* Phase 3 — Curation index loader
*
* `.vibe/recipes/*.md` 와 `.vibe/anti-patterns/*.md` 의 frontmatter 만 parse 해
* 1줄 요약 인덱스를 만든다. 본문은 읽지 않음 (세션 컨텍스트 절약).
*
* SPEC 결정:
* - INDEX.jsonl 미사용. 디렉토리 스캔 + frontmatter parse 가 충분히 빠르다 (<100 파일).
* - 최근 N=5 상한 (created 내림차순).
*
* 의도적 제한:
* - 본격 YAML parser 의존성 추가 거부. 우리가 *직접 작성*한 frontmatter 만
* 읽으므로 문법이 정해져 있다. 라인별 정규식이면 충분.
*/
import fs from 'fs';
import path from 'path';
import { projectVibeRoot } from '../utils.js';
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---/;
// "key: value" 또는 'key: "quoted value"' (이스케이프 \\" 포함)
const FIELD_RE = /^([a-z][a-z0-9_-]*):\s*(?:"((?:[^"\\]|\\.)*)"|(.+?))\s*$/;
function parseFrontmatter(content) {
const m = FRONTMATTER_RE.exec(content);
if (!m) return null;
const fields = {};
for (const line of m[1].split('\n')) {
const fm = FIELD_RE.exec(line);
if (!fm) continue;
const key = fm[1];
const value = fm[2] !== undefined ? fm[2].replace(/\\"/g, '"') : fm[3];
fields[key] = value;
}
return fields;
}
function readHead(filePath, bytes = 2048) {
const fd = fs.openSync(filePath, 'r');
try {
const buf = Buffer.alloc(bytes);
const n = fs.readSync(fd, buf, 0, bytes, 0);
return buf.toString('utf-8', 0, n);
} finally {
fs.closeSync(fd);
}
}
function listMd(dir) {
if (!fs.existsSync(dir)) return [];
return fs.readdirSync(dir)
.filter((f) => f.endsWith('.md') && !f.startsWith('_') && f !== 'README.md')
.map((f) => path.join(dir, f));
}
function safeParse(filePath) {
try {
const head = readHead(filePath);
const fields = parseFrontmatter(head);
if (!fields || !fields.slug) return null;
return fields;
} catch {
return null;
}
}
function compareCreatedDesc(a, b) {
// created 가 ISO 면 문자열 비교로 시간 정렬 가능, 아니면 mtime fallback
return (b.created || '').localeCompare(a.created || '');
}
/**
* 프로젝트의 recipes + anti-patterns 인덱스를 로드.
* @returns { recipes: [{slug, summary}], antiPatterns: [{tag, summary}] }
*/
export function loadCurationIndex(projectDir, opts = {}) {
const { recipeLimit = 5, antiPatternLimit = 5 } = opts;
const root = projectVibeRoot(projectDir);
const recipes = listMd(path.join(root, 'recipes'))
.map(safeParse).filter(Boolean)
.sort(compareCreatedDesc)
.slice(0, recipeLimit)
.map((f) => ({
slug: f.slug,
summary: f.recipe || f['symptom-context'] || '(no summary)',
}));
const antiPatterns = listMd(path.join(root, 'anti-patterns'))
.map(safeParse).filter(Boolean)
.sort(compareCreatedDesc)
.slice(0, antiPatternLimit)
.map((f) => ({
tag: f['root-cause-tag'] || 'other',
summary: f['suggested-stop'] || f['trigger-signature'] || '(no summary)',
}));
return { recipes, antiPatterns };
}
/** Test-only: frontmatter parser 노출 */
export const _internal = { parseFrontmatter };
#!/usr/bin/env node
/**
* Phase 3 — Recipe extractor (post-task curation)
*
* `/vibe.verify` 마지막 단계에서 호출됨. PostToolUse hot-path 가 아니라
* 명시적 1회 호출이므로 LLM 호출 허용.
*
* 동작:
* 1) `.vibe/metrics/current-run.jsonl` 읽기 (Phase 1 의 산출)
* 2) 휴리스틱 게이트: total_tools ≥ 8 AND fail_count ≥ 3 (= 어렵게 푼 task)
* 3) Haiku 로 3줄 요약 (When/Recipe/Anti-tip)
* 4) `.vibe/recipes/<slug>.md` 작성 (frontmatter 강제)
*
* 안전성:
* - 모든 실패 silent — recipe 가 없는 게 잘못된 recipe 보다 낫다.
* - LLM 호출 재귀 가드: VIBE_HOOK_DEPTH (다른 hook 와 동일 패턴).
* - 테스트 모드: VIBE_RECIPE_LLM=mock 이면 LLM 스킵, 결정적 stub 사용.
*
* 사용:
* node hooks/scripts/recipe-extractor.js
*/
import fs from 'fs';
import path from 'path';
import { spawnSync } from 'child_process';
import { PROJECT_DIR, projectVibePath, projectVibeRoot } from './utils.js';
const METRICS_DIR = projectVibePath(PROJECT_DIR, 'metrics');
const CURRENT_RUN_JSONL = path.join(METRICS_DIR, 'current-run.jsonl');
const CURRENT_RUN_JSON = path.join(METRICS_DIR, 'current-run.json');
// 휴리스틱 게이트 — SPEC 의 "Risk #1: LLM 호출 비용" 대응
const MIN_TOOLS = 8;
const MIN_FAILS = 3;
const HAIKU_MODEL = 'claude-haiku-4-5-20251001';
// ─────────────────────────────────────────────────────
// 재귀 가드
// ─────────────────────────────────────────────────────
function isRecursive() {
const depth = parseInt(process.env.VIBE_HOOK_DEPTH || '0', 10);
return depth >= 1;
}
// ─────────────────────────────────────────────────────
// jsonl 로드 + 게이트 평가
// ─────────────────────────────────────────────────────
function loadJsonl() {
if (!fs.existsSync(CURRENT_RUN_JSONL)) return [];
return fs.readFileSync(CURRENT_RUN_JSONL, 'utf-8')
.split('\n').filter(Boolean)
.map((l) => { try { return JSON.parse(l); } catch { return null; } })
.filter(Boolean);
}
function loadCurrentRunMeta() {
try { return JSON.parse(fs.readFileSync(CURRENT_RUN_JSON, 'utf-8')); }
catch { return { feature: null, startedAt: null, steps: 0 }; }
}
function evaluateGate(records) {
const total = records.length;
const fails = records.filter((r) => r.ok === false).length;
const lastIsSuccess = records.length > 0 && records[records.length - 1].ok === true;
const passes = total >= MIN_TOOLS && fails >= MIN_FAILS && lastIsSuccess;
return { passes, total, fails };
}
// ─────────────────────────────────────────────────────
// LLM 프롬프트 빌드
// ─────────────────────────────────────────────────────
function buildPrompt(records, meta) {
const tools = [...new Set(records.map((r) => r.tool))].join(', ');
const failsByCategory = {};
for (const r of records) {
if (r.ok === false && r.error_category) {
failsByCategory[r.error_category] = (failsByCategory[r.error_category] || 0) + 1;
}
}
const failSummary = Object.entries(failsByCategory)
.map(([cat, n]) => `${cat}×${n}`).join(', ') || 'none';
// 최근 12개 호출만 — 토큰 절약
const tail = records.slice(-12).map((r, i) => {
const mark = r.ok ? '✓' : '✗';
const cat = r.error_category ? ` [${r.error_category}]` : '';
const file = r.target_file ? ` ${r.target_file}` : '';
return `${i + 1}. ${mark} ${r.tool}${file}${cat}`;
}).join('\n');
return `You are summarizing a successfully completed task into a 3-line reusable recipe.
Task feature: ${meta.feature || 'unknown'}
Tool calls: ${records.length} total, ${failSummary}
Tools used: ${tools}
Last 12 calls (chronological):
${tail}
Output EXACTLY 3 lines of plain text, no markdown, no preamble:
LINE 1 — When to use this recipe (1 sentence, the situation/context)
LINE 2 — The working approach (1-2 sentences, what actually worked)
LINE 3 — Anti-tip: one thing to avoid (1 sentence)
Be specific — name the tools, the file types, the error categories. Do not output anything else.`;
}
// ─────────────────────────────────────────────────────
// claude CLI 호출 — --model 우선, fallback 가능
// ─────────────────────────────────────────────────────
let _modelFlagSupported = null;
function detectModelFlag() {
if (_modelFlagSupported !== null) return _modelFlagSupported;
try {
const help = spawnSync('claude', ['--help'], { encoding: 'utf-8', timeout: 3000 });
const out = (help.stdout || '') + (help.stderr || '');
_modelFlagSupported = /--model\b/.test(out);
} catch {
_modelFlagSupported = false;
}
return _modelFlagSupported;
}
function callClaude(prompt) {
if (process.env.VIBE_RECIPE_LLM === 'mock') {
// 테스트용 결정적 stub
return {
ok: true,
text: 'When auth + integration retries pile up.\nUse claude CLI with explicit model + retry on transient failures.\nAvoid swallowing stderr; surface error_category for downstream classification.',
};
}
const args = ['--print', '--dangerously-skip-permissions'];
if (detectModelFlag()) {
args.unshift('--model', HAIKU_MODEL);
}
const env = { ...process.env, VIBE_HOOK_DEPTH: '1' };
try {
const r = spawnSync('claude', args, {
input: prompt,
encoding: 'utf-8',
timeout: 60_000,
env,
});
if (r.status !== 0) return { ok: false, text: '' };
const text = (r.stdout || '').trim();
if (!text) return { ok: false, text: '' };
return { ok: true, text };
} catch {
return { ok: false, text: '' };
}
}
// ─────────────────────────────────────────────────────
// Recipe md 작성
// ─────────────────────────────────────────────────────
function timestampSlug() {
const d = new Date();
const pad = (n) => String(n).padStart(2, '0');
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
}
function safeFeature(name) {
if (!name) return 'anon';
return String(name).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 40) || 'anon';
}
function writeRecipe({ records, meta, summary }) {
const vibeRoot = projectVibeRoot(PROJECT_DIR);
const dir = path.join(vibeRoot, 'recipes');
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
const slug = `${safeFeature(meta.feature)}__${timestampSlug()}`;
const file = path.join(dir, `${slug}.md`);
if (fs.existsSync(file)) return null; // 동시 호출 dedup
const today = new Date().toISOString().slice(0, 10);
const tools = [...new Set(records.map((r) => r.tool))];
const failCount = records.filter((r) => r.ok === false).length;
// summary 가 3줄 보장 안 되면 채워서 안전화
const lines = summary.split('\n').map((l) => l.trim()).filter(Boolean);
while (lines.length < 3) lines.push('—');
const [whenLine, recipeLine, antiTip] = lines.slice(0, 3);
const body =
`---
slug: ${slug}
type: recipe
symptom-context: "${(meta.feature || 'unknown').replace(/"/g, '\\"')}"
recipe: "${recipeLine.replace(/"/g, '\\"')}"
tools-touched: [${tools.map((t) => JSON.stringify(t)).join(', ')}]
retry-count-saved: ${failCount}
created: ${today}
source-run: "${meta.startedAt || 'unknown'}"
confidence: low
---
# Recipe: ${meta.feature || 'unknown'}
## When to use
${whenLine}
## What worked
${recipeLine}
## Avoid
${antiTip}
## Stats
- Total tool calls: ${records.length}
- Failures before success: ${failCount}
- Tools touched: ${tools.join(', ')}
- Source run started: ${meta.startedAt || 'unknown'}
> Auto-generated by \`recipe-extractor.js\` from \`.vibe/metrics/current-run.jsonl\`. \
Confidence \`low\` — single observation. Increment to \`medium\`/\`high\` when the same recipe is seen multiple times across runs.
`;
fs.writeFileSync(file, body);
return file;
}
// ─────────────────────────────────────────────────────
// 메인
// ─────────────────────────────────────────────────────
function main() {
if (isRecursive()) return; // claude CLI 가 다시 우리 hook 을 트리거하는 것 방지
let records;
try { records = loadJsonl(); } catch { return; }
if (records.length === 0) return;
const gate = evaluateGate(records);
if (!gate.passes) return;
const meta = loadCurrentRunMeta();
const prompt = buildPrompt(records, meta);
const llm = callClaude(prompt);
if (!llm.ok) return;
try { writeRecipe({ records, meta, summary: llm.text }); }
catch { /* silent */ }
}
try { main(); } catch { /* never throw */ }
process.exit(0);
+1
-1

@@ -105,3 +105,3 @@ # VIBE

**Include**: `.vibe/{plans,specs,features,todos,research,regressions,contracts,config.json,constitution.md}`, `CLAUDE.md`
**Include**: `.vibe/{plans,specs,features,todos,research,regressions,contracts,recipes,anti-patterns,config.json,constitution.md}`, `CLAUDE.md`
**Vibe-global (not project-local)**: `~/.vibe/test-reports/` — `/vibe.test` artifacts live with the vibe install, not with the project

@@ -108,0 +108,0 @@ **Exclude**: `~/.claude/{rules,commands,agents,skills}/`, `.claude/settings.local.json`, `.coco/settings.local.json`, `.vibe/{memories,checkpoints,metrics}/`

@@ -202,2 +202,8 @@ ---

}catch{}"
# Phase 3 — Recipe extraction (post-task curation, best-effort silent)
# 휴리스틱 게이트 (total≥8 AND fails≥3) 통과 시만 LLM 호출.
# .vibe/recipes/<slug>.md 자동 생성 → 다음 세션의 session-start 가 prepend.
HOOKS_DIR="${VIBE_PATH:-$(npm root -g 2>/dev/null)/@su-record/vibe}/hooks/scripts"
[ -f "$HOOKS_DIR/recipe-extractor.js" ] && node "$HOOKS_DIR/recipe-extractor.js" 2>/dev/null || true
```

@@ -204,0 +210,0 @@

@@ -278,3 +278,3 @@ /**

// .vibe/ 표준 하위 디렉토리 (SSOT — Claude/coco 공용)
for (const sub of ['specs', 'features', 'plans', 'todos', 'memories', 'metrics']) {
for (const sub of ['specs', 'features', 'plans', 'todos', 'memories', 'metrics', 'recipes', 'anti-patterns']) {
ensureDir(path.join(coreDir, sub));

@@ -284,3 +284,3 @@ }

if (!fs.existsSync(vibeReadme)) {
fs.writeFileSync(vibeReadme, '# .vibe/\n\nVibe SSOT — Claude Code / coco CLI 공용 프로젝트 에셋.\n\n- `config.json` — 프로젝트 스택/capabilities\n- `constitution.md` — 하드 룰\n- `specs/`, `plans/`, `features/`, `todos/` — 워크플로우 문서\n- `memories/` — 프로젝트 메모리 DB\n- `metrics/` — 세션/스텝 카운터\n- `scope.json` — SPEC 기반 자동 스코프 (auto=true)\n');
fs.writeFileSync(vibeReadme, '# .vibe/\n\nVibe SSOT — Claude Code / coco CLI 공용 프로젝트 에셋.\n\n- `config.json` — 프로젝트 스택/capabilities\n- `constitution.md` — 하드 룰\n- `specs/`, `plans/`, `features/`, `todos/` — 워크플로우 문서\n- `memories/` — 프로젝트 메모리 DB\n- `metrics/` — 세션/스텝 카운터\n- `recipes/` — 성공한 task 의 압축 경로 (post-task curation, frontmatter 강제)\n- `anti-patterns/` — 같은 카테고리 3회 실패 시 자동 기록 (frontmatter 강제)\n- `scope.json` — SPEC 기반 자동 스코프 (auto=true)\n');
}

@@ -287,0 +287,0 @@ });

@@ -1,1 +0,1 @@

{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClF,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,EACtB,2BAA2B,EAC3B,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AACpC,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC1F,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpD;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CACtC,iBAA2B,EAAE,EAC7B,UAAsB,EAAE,MAAM,EAAE,KAAK,EAAE;IAEvC,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;QAClE,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QAC9D,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;QAEtD,iEAAiE;QACjE,MAAM,kBAAkB,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;QACnF,MAAM,mBAAmB,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;QAChE,MAAM,YAAY,GAAG,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,mBAAmB,CAAC;QAElG,kCAAkC;QAClC,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;QACrE,IAAI,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAChC,mBAAmB,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;QACrD,CAAC;QAED,iDAAiD;QACjD,MAAM,sBAAsB,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC;QACpF,mBAAmB,CAAC,sBAAsB,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;QAE1E,mCAAmC;QACnC,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;QACrE,oBAAoB,CAAC,eAAe,CAAC,CAAC;IAExC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,wCAAwC;QACxC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,uCAAwC,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAChF,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,WAAmB,EACnB,UAAoB,EACpB,eAAyB,EAAE,EAC3B,aAAqB,SAAS;IAE9B,MAAM,WAAW,GAAG,kBAAkB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IACjE,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAErC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC9D,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC1D,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACtD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO;IAEzC,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;IACpE,kBAAkB,CAAC,YAAY,EAAE,cAAc,EAAE,WAAW,CAAC,CAAC;IAC9D,GAAG,CAAC,iCAAiC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,WAAmB,EACnB,UAAoB,EACpB,aAAqB,SAAS;IAE9B,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAClF,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAChF,MAAM,kBAAkB,GAAG,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,eAAe,CAAC;IAChG,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC;QAAE,OAAO;IAE/C,yBAAyB;IACzB,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;IACxC,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,IAAI;YAAE,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,aAAa,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO;IAErC,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;IACvE,SAAS,CAAC,iBAAiB,CAAC,CAAC;IAE7B,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;QAChD,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAC3B,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,mCAAmC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnE,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,OAAO,CACd,CAAiC,EACjC,IAAY,EACZ,EAAc;IAEd,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAChB,IAAI,CAAC;QACH,EAAE,EAAE,CAAC;IACP,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,eAAe,GAAG,EAAE,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CACxB,WAAoB,EACpB,SAA6C,IAAI;IAEjD,IAAI,CAAC;QACH,MAAM,WAAW,GAA0D;YACzE,EAAE,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE;YAC5C,IAAI,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE;YACrC,KAAK,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE;YACxC,MAAM,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE;SAC5C,CAAC;QACF,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;QAErE,IAAI,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QAChC,IAAI,YAAY,GAAG,KAAK,CAAC;QAEzB,IAAI,WAAW,EAAE,CAAC;YAChB,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,CAAC,CAAC;YAEpD,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC/B,GAAG,CAAC,4BAA4B,WAAW,GAAG,CAAC,CAAC;gBAChD,OAAO;YACT,CAAC;YAED,GAAG,CAAC,wBAAwB,WAAW,KAAK,CAAC,CAAC;YAC9C,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/C,YAAY,GAAG,IAAI,CAAC;QACtB,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAChD,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,GAAG,CAAC,0BAA0B,CAAC,CAAC;YAChC,OAAO;QACT,CAAC;QAED,qGAAqG;QACrG,SAAS,CAAC,OAAO,CAAC,CAAC;QACnB,MAAM,YAAY,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;QACxD,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,GAAG,CAAC,gCAAgC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC;QACD,GAAG,CAAC,sBAAsB,YAAY,KAAK,UAAU,eAAe,CAAC,CAAC;QACtE,GAAG,CAAC,6BAA6B,CAAC,CAAC;QAEnC,2DAA2D;QAC3D,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QACnC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;QAEtB,CAAC,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACnC,OAAO,CAAC,CAAC,EAAE,kBAAkB,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;QAC9E,OAAO,CAAC,CAAC,EAAE,qBAAqB,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;QAElF,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;QACnC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;QACxF,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QAEnC,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,GAAG,CAAC,0BAA0B,CAAC,CAAC;YAChC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;gBAC1B,GAAG,CAAC,WAAW,EAAE,CAAC,IAAI,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YAChE,CAAC,CAAC,CAAC;YACH,IAAI,YAAY,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtC,GAAG,CAAC,eAAe,YAAY,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC5D,CAAC;YACD,IAAI,YAAY,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC5C,GAAG,CAAC,kBAAkB,YAAY,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrE,CAAC;QACH,CAAC;QAED,mCAAmC;QACnC,IAAI,YAAY,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,sBAAsB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnG,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,WAAW,CAAC;gBACnC,OAAO,EAAE,kEAAkE;gBAC3E,OAAO,EAAE;oBACP,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,2BAA2B,EAAE;oBACvE,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;wBAClC,KAAK,EAAE,CAAC,CAAC,KAAK;wBACd,KAAK,EAAE,CAAC,CAAC,KAAK;wBACd,IAAI,EAAE,CAAC,CAAC,IAAI;qBACb,CAAC,CAAC;iBACJ;gBACD,QAAQ,EAAE,KAAK;aAChB,CAAC,CAAC;YACH,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACrD,MAAM,QAAQ,GAAI,QAAqB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC;gBACtE,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACxB,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC;QACH,CAAC;QAED,kDAAkD;QAClD,IAAI,YAAiD,CAAC;QACtD,IAAI,YAAY,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACpE,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC;gBAC9B,OAAO,EAAE,4BAA4B;gBACrC,WAAW,EAAE,oBAAoB;gBACjC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;oBACd,IAAI,CAAC,CAAC;wBAAE,OAAO,UAAU,CAAC;oBAC1B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;wBAAE,OAAO,uBAAuB,CAAC;oBACxD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;wBAAE,OAAO,qBAAqB,CAAC;oBACpD,OAAO,SAAS,CAAC;gBACnB,CAAC;aACF,CAAC,CAAC;YACH,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC5B,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC;oBAC7B,OAAO,EAAE,kBAAkB;oBAC3B,YAAY,EAAE,OAAO;oBACrB,WAAW,EAAE,OAAO;iBACrB,CAAC,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC;oBAC1B,OAAO,EAAE,cAAc;oBACvB,YAAY,EAAE,QAAQ;oBACtB,WAAW,EAAE,QAAQ;iBACtB,CAAC,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC;oBAC5B,OAAO,EAAE,qBAAqB;oBAC9B,YAAY,EAAE,IAAI;oBAClB,WAAW,EAAE,IAAI;oBACjB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;wBACd,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;wBACpB,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;4BAAE,OAAO,2BAA2B,CAAC;wBAC1D,OAAO,SAAS,CAAC;oBACnB,CAAC;iBACF,CAAC,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,OAAO,CAAC;oBAC/B,OAAO,EAAE,gCAAgC;oBACzC,YAAY,EAAE,KAAK;iBACpB,CAAC,CAAC;gBAEH,YAAY,GAAG;oBACb,OAAO,EAAE,IAAI;oBACb,UAAU,EAAE,UAAoB;oBAChC,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAmB,IAAI,OAAO,CAAC;oBAC7E,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAgB,IAAI,QAAQ,CAAC;oBACtE,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;oBAC5D,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ;oBACjD,IAAI,EAAE,gBAAgB,EAAE;iBACzB,CAAC;YACJ,CAAC;QACH,CAAC;QAED,2DAA2D;QAC3D,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QACvC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;QACvB,EAAE,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAEtC,oEAAoE;QACpE,OAAO,CAAC,EAAE,EAAE,2CAA2C,EAAE,GAAG,EAAE;YAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;YAE9C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC5B,SAAS,CAAC,OAAO,CAAC,CAAC;gBACnB,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,EAC/B,qRAAqR,CACtR,CAAC;YACJ,CAAC;YAED,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3B,SAAS,CAAC,MAAM,CAAC,CAAC;gBAClB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;gBAC1C,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;gBACxC,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,EAC9B,4KAA4K,CAC7K,CAAC;YACJ,CAAC;YAED,4CAA4C;YAC5C,KAAK,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,CAAC;gBACjF,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;YACrC,CAAC;YACD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YACnD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC/B,EAAE,CAAC,aAAa,CACd,UAAU,EACV,gSAAgS,CACjS,CAAC;YACJ,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,EAAE,EAAE,0BAA0B,EAAE,GAAG,EAAE,CAAC,kBAAkB,CAAC,OAAO,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC,CAAC;QACzG,OAAO,CAAC,EAAE,EAAE,sBAAsB,EAAE,GAAG,EAAE;YACvC,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;YACvE,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;gBACrD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;gBAChE,MAAM,CAAC,MAAM,GAAG,YAAY,CAAC;gBAC7B,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAChE,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,EAAE,EAAE,eAAe,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,CAAC,CAAC,CAAC;QAChF,OAAO,CAAC,EAAE,EAAE,sCAAsC,EAAE,GAAG,EAAE,CAAC,4BAA4B,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;QAEjH,EAAE,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QAElC,2DAA2D;QAC3D,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACtC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;QACvB,EAAE,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAErC,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QAErD,sEAAsE;QACtE,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YACxB,OAAO,CAAC,EAAE,EAAE,0BAA0B,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;QAC9F,CAAC;QACD,OAAO,CAAC,EAAE,EAAE,+BAA+B,EAAE,GAAG,EAAE,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAC,CAAC;QACzF,OAAO,CAAC,EAAE,EAAE,yBAAyB,EAAE,GAAG,EAAE,CAAC,kBAAkB,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;QAC1F,OAAO,CAAC,EAAE,EAAE,2BAA2B,EAAE,GAAG,EAAE,CAAC,oBAAoB,CAAC,WAAW,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC;QAE1G,OAAO,CAAC,EAAE,EAAE,yBAAyB,EAAE,GAAG,EAAE;YAC1C,kBAAkB,CAAC,WAAW,EAAE,UAAU,EAAE,YAAY,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QACrF,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,EAAE,EAAE,4BAA4B,EAAE,GAAG,EAAE;YAC7C,qBAAqB,CAAC,WAAW,EAAE,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;QAEH,IAAI,YAAY,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzC,GAAG,CAAC,yBAAyB,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzE,CAAC;QAED,OAAO,CAAC,EAAE,EAAE,iCAAiC,EAAE,GAAG,EAAE;YAClD,MAAM,eAAe,GAAG,WAAW,CAAC,SAAS,CAAC,WAAW,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;YACzF,IAAI,eAAe,CAAC,eAAe,EAAE,CAAC;gBACpC,GAAG,CAAC,sCAAsC,CAAC,CAAC;YAC9C,CAAC;YACD,IAAI,eAAe,CAAC,qBAAqB,EAAE,CAAC;gBAC1C,GAAG,CAAC,iCAAiC,CAAC,CAAC;YACzC,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,4FAA4F;QAC5F,oEAAoE;QACpE,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;QACnC,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;QACrC,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;QAEvC,wCAAwC;QACxC,OAAO,CAAC,EAAE,EAAE,iCAAiC,EAAE,GAAG,EAAE;YAClD,sBAAsB,EAAE,CAAC;YACzB,IAAI,UAAU,CAAC,SAAS;gBAAE,sBAAsB,EAAE,CAAC;YACnD,IAAI,WAAW,CAAC,SAAS;gBAAE,2BAA2B,EAAE,CAAC;YACzD,IAAI,YAAY,CAAC,SAAS;gBAAE,sBAAsB,EAAE,CAAC;YACrD,MAAM,SAAS,GAAG,CAAC,QAAQ;gBACzB,UAAU,CAAC,SAAS,IAAI,MAAM;gBAC9B,WAAW,CAAC,SAAS,IAAI,OAAO;gBAChC,YAAY,CAAC,SAAS,IAAI,QAAQ;aACnC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7B,GAAG,CAAC,+BAA+B,SAAS,IAAI,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,uCAAuC;QACvC,OAAO,CAAC,EAAE,EAAE,+BAA+B,EAAE,GAAG,EAAE;YAChD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBACpB,uBAAuB,CAAC,WAAW,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;gBACnE,GAAG,CAAC,6BAA6B,CAAC,CAAC;gBACnC,kDAAkD;gBAClD,IAAI,UAAU,CAAC,SAAS,IAAI,WAAW,CAAC,SAAS,EAAE,CAAC;oBAClD,uBAAuB,CAAC,WAAW,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;oBACnE,GAAG,CAAC,sDAAsD,CAAC,CAAC;gBAC9D,CAAC;YACH,CAAC;iBAAM,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;gBACnD,uBAAuB,CAAC,WAAW,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;gBACnE,GAAG,CAAC,8BAA8B,MAAM,KAAK,CAAC,CAAC;YACjD,CAAC;iBAAM,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAC/B,uBAAuB,CAAC,WAAW,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;gBACnE,GAAG,CAAC,6BAA6B,CAAC,CAAC;YACrC,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QAEjC,0EAA0E;QAC1E,sDAAsD;QACtD,IAAI,mBAAmB,CAAC,WAAW,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;gBAC9D,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;gBAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC;gBAC5F,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;oBAC/B,QAAQ,CAAC,SAAS,WAAW,MAAM,WAAW,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC1F,CAAC;YACH,CAAC;YAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;QAC/B,CAAC;QAED,SAAS;QACT,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;QAErC,GAAG,CAAC,wBAAwB,WAAW,CAAC,OAAO;EACjD,eAAe,EAAE;;;QAGX,YAAY,CAAC,CAAC,CAAC,MAAM,WAAW,MAAM,CAAC,CAAC,CAAC,EAAE;CAClD,CAAC,CAAC;IAED,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"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"}

@@ -88,2 +88,21 @@ /**

// Phase 3 — Recipes + anti-patterns 인덱스 (post-task curation)
// 본문이 아닌 1줄 요약만. session-context 가 비대해지지 않도록 N=5 상한.
try {
const { loadCurationIndex } = await import('./lib/curation-index.js');
const index = loadCurationIndex(PROJECT_DIR, { recipeLimit: 5, antiPatternLimit: 5 });
if (index.recipes.length > 0) {
console.log('\n[Recipes — succeeded patterns]');
for (const r of index.recipes) {
console.log(` • ${r.slug} — ${r.summary}`);
}
}
if (index.antiPatterns.length > 0) {
console.log('\n[Anti-patterns — known pitfalls]');
for (const a of index.antiPatterns) {
console.log(` ⚠ ${a.tag}: ${a.summary}`);
}
}
} catch { /* curation is best-effort */ }
// Version check

@@ -90,0 +109,0 @@ if (latestVersion) {

#!/usr/bin/env node
/**
* PostToolUse Hook - 툴콜 스텝 카운터
* PostToolUse Hook — 툴콜 스텝 카운터 + 패턴 로거 + 3-fail 감지기
*
* 목적: vibe 멀티-오케스트레이션의 "목표 달성까지 몇 스텝" 측정.
* 모든 성공 툴콜을 1 스텝으로 집계하여 `/vibe.verify` 리포트에 노출.
* 책임 1) 모든 성공 툴콜을 1 스텝으로 집계 → `current-run.json`
* ↳ /vibe.verify 가 history.jsonl에 append 후 출력
* 책임 2) 각 툴콜 한 줄을 `current-run.jsonl` 에 append (post-task-curation SPEC)
* ↳ Phase 3 의 recipe extractor 가 소비
* 책임 3) (Phase 2) 실패 메시지 분류 + 같은 (file, category) 3회 반복 감지 →
* `.vibe/anti-patterns/<slug>.md` 로 자동 저장.
*
* 저장 위치: .claude/vibe/metrics/current-run.json
* - /vibe.run 시작 시 overwrite (feature, startedAt, steps=0)
* - 이후 툴콜마다 steps += 1
* - /vibe.verify가 읽어서 history.jsonl에 append 후 출력
*
* 실패 시 조용히 통과 (차단 금지).
* 세 책임은 독립 — 어느 한쪽 실패가 다른 쪽을 막지 않는다.
* PostToolUse 는 hot path 이므로 LLM 호출/외부 spawn 금지, fs 만 사용.
*/

@@ -20,24 +20,233 @@ import fs from 'fs';

const METRICS_DIR = projectVibePath(PROJECT_DIR, 'metrics');
const CURRENT_RUN = path.join(METRICS_DIR, 'current-run.json');
const ANTI_PATTERNS_DIR = projectVibePath(PROJECT_DIR, 'anti-patterns');
const CURRENT_RUN_JSON = path.join(METRICS_DIR, 'current-run.json');
const CURRENT_RUN_JSONL = path.join(METRICS_DIR, 'current-run.jsonl');
const MAX_JSONL_LINES = 5000;
const FAIL_WINDOW = 10;
const FAIL_THRESHOLD = 3;
try {
if (!fs.existsSync(METRICS_DIR)) {
fs.mkdirSync(METRICS_DIR, { recursive: true });
// ─────────────────────────────────────────────────────
// stdin / env 에서 PostToolUse payload 추출
// ─────────────────────────────────────────────────────
function readStdinSync() {
try {
if (process.stdin.isTTY) return null;
const buf = Buffer.alloc(65536);
const bytesRead = fs.readSync(0, buf, 0, buf.length, null);
if (bytesRead > 0) return JSON.parse(buf.toString('utf-8', 0, bytesRead));
} catch { /* ignore */ }
return null;
}
function parseToolInput(raw) {
if (!raw) return {};
if (typeof raw === 'string') {
try { return JSON.parse(raw); } catch { return {}; }
}
return raw;
}
function extractTargetFile(toolInput) {
const fp = toolInput.file_path || toolInput.notebook_path || toolInput.path || null;
if (!fp) return null;
try {
const abs = path.isAbsolute(fp) ? fp : path.resolve(PROJECT_DIR, fp);
const rel = path.relative(path.resolve(PROJECT_DIR), abs).replace(/\\/g, '/');
return rel || path.basename(fp);
} catch {
return fp;
}
}
function isResponseError(toolResponse) {
if (!toolResponse) return false;
if (typeof toolResponse === 'object') {
if (toolResponse.is_error === true) return true;
if (toolResponse.error) return true;
if (Array.isArray(toolResponse.errors) && toolResponse.errors.length > 0) return true;
}
return false;
}
// ─────────────────────────────────────────────────────
// 책임 3a: error_category 분류 (regex 만 — vibe-regress tag enum 일부)
// ─────────────────────────────────────────────────────
const ERROR_CATEGORIES = [
{ tag: 'nullability', re: /Cannot read propert(?:y|ies) of (?:undefined|null)|TypeError[^\n]*(?:undefined|null)/i },
{ tag: 'type-narrow', re: /\bTS2345\b|not assignable to (?:parameter|type)/i },
{ tag: 'compilation', re: /\bTS\d{4}\b|cannot find name|build failed|compilation (?:error|failed)/i },
{ tag: 'syntax', re: /SyntaxError|Unexpected token/i },
{ tag: 'integration', re: /ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET|connect failed/i },
{ tag: 'auth', re: /\b(?:401|403)\b|unauthori[sz]ed|forbidden|invalid (?:token|credentials)/i },
{ tag: 'permission', re: /EACCES|permission denied/i },
{ tag: 'not-found', re: /ENOENT|no such file|not found/i },
];
function classifyError(toolResponse) {
if (!toolResponse) return null;
let text;
if (typeof toolResponse === 'string') text = toolResponse;
else if (toolResponse.error) text = String(toolResponse.error);
else if (Array.isArray(toolResponse.errors)) text = JSON.stringify(toolResponse.errors);
else if (toolResponse.content) text = JSON.stringify(toolResponse.content);
else text = JSON.stringify(toolResponse);
for (const { tag, re } of ERROR_CATEGORIES) {
if (re.test(text)) return tag;
}
return 'other';
}
// ─────────────────────────────────────────────────────
// 책임 1: current-run.json 카운터
// ─────────────────────────────────────────────────────
function bumpCounter() {
let data = { feature: null, startedAt: null, steps: 0 };
if (fs.existsSync(CURRENT_RUN)) {
if (fs.existsSync(CURRENT_RUN_JSON)) {
try {
data = JSON.parse(fs.readFileSync(CURRENT_RUN, 'utf-8'));
} catch {
// 손상 파일 무시, 새로 시작
data = JSON.parse(fs.readFileSync(CURRENT_RUN_JSON, 'utf-8'));
} catch { /* 손상 → 새로 시작 */ }
}
if (!data.startedAt) data.startedAt = new Date().toISOString();
data.steps = (data.steps || 0) + 1;
fs.writeFileSync(CURRENT_RUN_JSON, JSON.stringify(data, null, 2));
}
// ─────────────────────────────────────────────────────
// 책임 2: current-run.jsonl append + error_category 채움
// ─────────────────────────────────────────────────────
function appendJsonl(stdinPayload) {
const toolName = stdinPayload?.tool_name || process.argv[2] || '';
if (!toolName) return null;
const toolInput = parseToolInput(stdinPayload?.tool_input ?? process.env.TOOL_INPUT);
const ok = !isResponseError(stdinPayload?.tool_response);
const errorCategory = ok ? null : classifyError(stdinPayload?.tool_response);
const record = {
ts: new Date().toISOString(),
tool: toolName,
ok,
target_file: extractTargetFile(toolInput),
error_category: errorCategory,
};
fs.appendFileSync(CURRENT_RUN_JSONL, JSON.stringify(record) + '\n');
// 회전: 라인 수가 상한 초과 시 마지막 절반만 남김.
try {
const stat = fs.statSync(CURRENT_RUN_JSONL);
if (stat.size > 2 * 1024 * 1024) {
const lines = fs.readFileSync(CURRENT_RUN_JSONL, 'utf-8').split('\n').filter(Boolean);
if (lines.length > MAX_JSONL_LINES) {
const keep = lines.slice(-Math.floor(MAX_JSONL_LINES / 2));
fs.writeFileSync(CURRENT_RUN_JSONL, keep.join('\n') + '\n');
}
}
} catch { /* 회전 실패는 무시 */ }
return record;
}
// ─────────────────────────────────────────────────────
// 책임 3b: 3-fail detector → anti-pattern md
// ─────────────────────────────────────────────────────
const SUGGESTED_STOPS = {
nullability: '같은 위치 null/undefined 처리 반복 — 타입 가드 또는 옵셔널 체이닝 필요',
'type-narrow': '같은 위치 타입 좁히기 반복 — 타입 정의 점검 필요',
compilation: '컴파일 실패 반복 — 타입/임포트 정의 확인 필요',
syntax: '구문 오류 반복 — 파서 호환성/버전 확인 필요',
integration: '외부 서비스 연결 실패 반복 — endpoint/포트/네트워크 점검 필요',
auth: '인증 실패 반복 — 토큰/자격증명 점검 필요',
permission: '권한 실패 반복 — 파일/디렉토리 권한 점검 필요',
'not-found': '리소스 부재 반복 — 경로/존재 확인 필요',
other: '같은 패턴 실패 반복 — 접근 방식 재검토 필요',
};
function fileSlug(targetFile) {
if (!targetFile) return 'global';
return targetFile.replace(/[\/\.]/g, '-');
}
function readTailWindow() {
if (!fs.existsSync(CURRENT_RUN_JSONL)) return [];
const raw = fs.readFileSync(CURRENT_RUN_JSONL, 'utf-8').split('\n').filter(Boolean);
return raw.slice(-FAIL_WINDOW).map((l) => {
try { return JSON.parse(l); } catch { return null; }
}).filter(Boolean);
}
function detectThreeFail() {
const window = readTailWindow();
const groups = new Map();
for (const r of window) {
if (r.ok || !r.error_category) continue;
const key = `${r.target_file ?? 'null'}::${r.error_category}`;
const cur = groups.get(key) ?? 0;
groups.set(key, cur + 1);
}
for (const [key, count] of groups) {
if (count >= FAIL_THRESHOLD) {
const sep = key.indexOf('::');
const file = key.slice(0, sep);
const cat = key.slice(sep + 2);
return { category: cat, targetFile: file === 'null' ? null : file, count };
}
}
return null;
}
if (!data.startedAt) {
data.startedAt = new Date().toISOString();
function writeAntiPattern({ category, targetFile, count }) {
if (!fs.existsSync(ANTI_PATTERNS_DIR)) {
fs.mkdirSync(ANTI_PATTERNS_DIR, { recursive: true });
}
data.steps = (data.steps || 0) + 1;
const today = new Date().toISOString().slice(0, 10);
const dateSlug = today.replace(/-/g, '');
const fSlug = fileSlug(targetFile);
const slug = `${category}__${fSlug}__${dateSlug}`;
const filePath = path.join(ANTI_PATTERNS_DIR, `${slug}.md`);
if (fs.existsSync(filePath)) return; // dedup
fs.writeFileSync(CURRENT_RUN, JSON.stringify(data, null, 2));
const trigger = `(file=${targetFile ?? 'null'}, category=${category})`;
const stop = SUGGESTED_STOPS[category] ?? SUGGESTED_STOPS.other;
const content = `---
slug: ${slug}
type: anti-pattern
root-cause-tag: ${category}
trigger-signature: "${trigger}"
fail-count: ${count}
suggested-stop: "${stop}"
created: ${today}
---
## Trigger
${trigger} 이 ${FAIL_WINDOW} 툴콜 윈도우 내 ${count} 회 발생.
## Suggested Stop
${stop}
> 자동 생성 — 같은 패턴 반복 시 다른 접근법을 고려하거나 사용자에게 조언을 구할 것.
`;
fs.writeFileSync(filePath, content);
}
// ─────────────────────────────────────────────────────
// 메인 — 세 책임을 독립적으로 try
// ─────────────────────────────────────────────────────
try {
if (!fs.existsSync(METRICS_DIR)) {
fs.mkdirSync(METRICS_DIR, { recursive: true });
}
const stdinPayload = readStdinSync();
try { bumpCounter(); } catch { /* 카운터 실패 무시 */ }
let lastRecord = null;
try { lastRecord = appendJsonl(stdinPayload); } catch { /* 로깅 실패 무시 */ }
try {
if (lastRecord && !lastRecord.ok && lastRecord.error_category) {
const trip = detectThreeFail();
if (trip) writeAntiPattern(trip);
}
} catch { /* anti-pattern 작성 실패 무시 */ }
} catch {

@@ -44,0 +253,0 @@ // Never block on counter failure

{
"name": "@su-record/vibe",
"version": "2.9.38",
"version": "2.9.40",
"description": "AI Coding Framework for Claude Code — 56 agents, 45 skills, multi-LLM orchestration",

@@ -5,0 +5,0 @@ "type": "module",