@su-record/vibe 2.9.38 → 2.10.0
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 +19 -6
- package/README.md +31 -24
- package/agents/{teams/figma → figma}/figma-analyst.md +2 -2
- package/agents/research/{best-practices-agent.md → best-practices.md} +1 -1
- package/agents/research/{codebase-patterns-agent.md → codebase-patterns.md} +1 -1
- package/agents/research/{framework-docs-agent.md → framework-docs.md} +1 -1
- package/agents/research/{security-advisory-agent.md → security-advisory.md} +1 -1
- package/agents/teams/research-team.md +4 -4
- package/agents/teams/review-debate-team.md +2 -2
- package/agents/teams/security-team.md +4 -4
- package/dist/cli/commands/init.js +2 -2
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/postinstall/claude-agents.d.ts +3 -1
- package/dist/cli/postinstall/claude-agents.d.ts.map +1 -1
- package/dist/cli/postinstall/claude-agents.js +47 -9
- package/dist/cli/postinstall/claude-agents.js.map +1 -1
- package/dist/cli/postinstall/constants.d.ts +5 -0
- package/dist/cli/postinstall/constants.d.ts.map +1 -1
- package/dist/cli/postinstall/constants.js +165 -23
- package/dist/cli/postinstall/constants.js.map +1 -1
- package/dist/cli/postinstall/cursor-skills.js +2 -2
- package/dist/cli/postinstall/main.d.ts.map +1 -1
- package/dist/cli/postinstall/main.js +19 -10
- package/dist/cli/postinstall/main.js.map +1 -1
- package/dist/infra/lib/OrchestrateWorkflow.js +1 -1
- package/dist/infra/lib/OrchestrateWorkflow.js.map +1 -1
- package/dist/infra/lib/telemetry/SkillTelemetry.test.js +4 -4
- package/dist/infra/lib/telemetry/SkillTelemetry.test.js.map +1 -1
- package/dist/infra/orchestrator/parallelResearch.js +4 -4
- package/dist/infra/orchestrator/parallelResearch.js.map +1 -1
- package/hooks/scripts/__tests__/curation-index.test.js +157 -0
- package/hooks/scripts/__tests__/recipe-extractor.test.js +244 -0
- package/hooks/scripts/__tests__/step-counter.test.js +358 -0
- package/hooks/scripts/clone-extract.js +712 -0
- package/hooks/scripts/clone-refine.js +510 -0
- package/hooks/scripts/clone-to-scss.js +275 -0
- package/hooks/scripts/clone-validate.js +280 -0
- package/hooks/scripts/lib/curation-index.js +101 -0
- package/hooks/scripts/recipe-extractor.js +249 -0
- package/hooks/scripts/session-start.js +19 -0
- package/hooks/scripts/step-counter.js +230 -21
- package/package.json +2 -1
- package/skills/agents-md/SKILL.md +2 -0
- package/skills/arch-guard/SKILL.md +2 -0
- package/skills/brand-assets/SKILL.md +1 -0
- package/skills/capability-loop/SKILL.md +2 -0
- package/skills/characterization-test/SKILL.md +2 -0
- package/skills/chub-usage/SKILL.md +1 -0
- package/skills/claude-md-guide/SKILL.md +2 -0
- package/skills/clone/SKILL.md +361 -0
- package/skills/commerce-patterns/SKILL.md +1 -0
- package/skills/commit-push-pr/SKILL.md +1 -0
- package/skills/context7-usage/SKILL.md +1 -0
- package/skills/{vibe-contract → contract}/SKILL.md +7 -8
- package/skills/create-prd/SKILL.md +1 -0
- package/skills/design-audit/SKILL.md +1 -0
- package/skills/design-critique/SKILL.md +1 -0
- package/skills/design-distill/SKILL.md +1 -0
- package/skills/design-normalize/SKILL.md +1 -0
- package/skills/design-polish/SKILL.md +1 -0
- package/skills/design-teach/SKILL.md +2 -0
- package/skills/devlog/SKILL.md +1 -0
- package/skills/{vibe-docs → docs}/SKILL.md +5 -5
- package/skills/e2e-commerce/SKILL.md +1 -0
- package/skills/event-comms/SKILL.md +1 -0
- package/skills/event-ops/SKILL.md +1 -0
- package/skills/event-planning/SKILL.md +1 -0
- package/skills/exec-plan/SKILL.md +2 -0
- package/skills/{vibe-figma → figma}/SKILL.md +4 -3
- package/skills/{vibe-figma-convert → figma-convert}/SKILL.md +4 -3
- package/skills/{vibe-figma-extract → figma-extract}/SKILL.md +4 -3
- package/skills/git-worktree/SKILL.md +1 -0
- package/skills/handoff/SKILL.md +2 -0
- package/skills/{vibe-interview → interview}/SKILL.md +16 -16
- package/skills/parallel-research/SKILL.md +2 -0
- package/skills/{vibe-plan → plan}/SKILL.md +9 -9
- package/skills/prioritization-frameworks/SKILL.md +1 -0
- package/skills/priority-todos/SKILL.md +2 -0
- package/skills/{vibe-regress → regress}/SKILL.md +5 -6
- package/skills/rob-pike/SKILL.md +2 -0
- package/skills/seo-checklist/SKILL.md +1 -0
- package/skills/{vibe-spec → spec}/SKILL.md +14 -14
- package/skills/{vibe-spec-review → spec-review}/SKILL.md +8 -9
- package/skills/systematic-debugging/SKILL.md +2 -0
- package/skills/techdebt/SKILL.md +2 -0
- package/skills/{vibe-test → test}/SKILL.md +12 -12
- package/skills/tool-fallback/SKILL.md +1 -0
- package/skills/typescript-advanced-types/SKILL.md +1 -0
- package/skills/ui-ux-pro-max/SKILL.md +1 -0
- package/skills/user-personas/SKILL.md +1 -0
- package/skills/vercel-react-best-practices/SKILL.md +1 -0
- package/skills/vibe/SKILL.md +266 -0
- package/{commands/vibe.analyze.md → skills/vibe.analyze/SKILL.md} +2 -0
- package/skills/vibe.clone/SKILL.md +117 -0
- package/{commands/vibe.contract.md → skills/vibe.contract/SKILL.md} +3 -1
- package/{commands/vibe.docs.md → skills/vibe.docs/SKILL.md} +3 -1
- package/{commands/vibe.event.md → skills/vibe.event/SKILL.md} +2 -0
- package/{commands/vibe.figma.md → skills/vibe.figma/SKILL.md} +25 -23
- package/{commands/vibe.harness.md → skills/vibe.harness/SKILL.md} +2 -0
- package/{commands/vibe.reason.md → skills/vibe.reason/SKILL.md} +2 -0
- package/{commands/vibe.regress.md → skills/vibe.regress/SKILL.md} +5 -3
- package/{commands/vibe.review.md → skills/vibe.review/SKILL.md} +2 -0
- package/{commands/vibe.run.md → skills/vibe.run/SKILL.md} +3 -1
- package/{commands/vibe.scaffold.md → skills/vibe.scaffold/SKILL.md} +2 -0
- package/{commands/vibe.spec.md → skills/vibe.spec/SKILL.md} +36 -34
- package/{commands/vibe.test.md → skills/vibe.test/SKILL.md} +4 -2
- package/{commands/vibe.trace.md → skills/vibe.trace/SKILL.md} +7 -0
- package/{commands/vibe.utils.md → skills/vibe.utils/SKILL.md} +2 -0
- package/{commands/vibe.verify.md → skills/vibe.verify/SKILL.md} +10 -2
- package/skills/video-production/SKILL.md +1 -0
- /package/agents/{teams/figma → figma}/figma-architect.md +0 -0
- /package/agents/{teams/figma → figma}/figma-auditor.md +0 -0
- /package/agents/{teams/figma → figma}/figma-builder.md +0 -0
- /package/skills/{vibe-docs → docs}/templates/architecture.md +0 -0
- /package/skills/{vibe-docs → docs}/templates/behavioral-principles.md +0 -0
- /package/skills/{vibe-docs → docs}/templates/readme.md +0 -0
- /package/skills/{vibe-docs → docs}/templates/release-notes.md +0 -0
- /package/skills/{vibe-figma → figma}/rubrics/extraction-checklist.md +0 -0
- /package/skills/{vibe-figma → figma}/templates/component-index.md +0 -0
- /package/skills/{vibe-figma → figma}/templates/component-spec.md +0 -0
- /package/skills/{vibe-figma → figma}/templates/figma-handoff.md +0 -0
- /package/skills/{vibe-figma → figma}/templates/remapped-tree.md +0 -0
- /package/skills/{vibe-figma-convert → figma-convert}/rubrics/conversion-rules.md +0 -0
- /package/skills/{vibe-figma-convert → figma-convert}/templates/component.md +0 -0
- /package/skills/{vibe-figma-extract → figma-extract}/rubrics/image-rules.md +0 -0
- /package/skills/{vibe-interview → interview}/checklists/api.md +0 -0
- /package/skills/{vibe-interview → interview}/checklists/feature.md +0 -0
- /package/skills/{vibe-interview → interview}/checklists/library.md +0 -0
- /package/skills/{vibe-interview → interview}/checklists/mobile.md +0 -0
- /package/skills/{vibe-interview → interview}/checklists/webapp.md +0 -0
- /package/skills/{vibe-interview → interview}/checklists/website.md +0 -0
- /package/skills/{vibe-regress → regress}/templates/bug.md +0 -0
- /package/skills/{vibe-regress → regress}/templates/test-jest.md +0 -0
- /package/skills/{vibe-regress → regress}/templates/test-vitest.md +0 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Phase 3 — Recipe extractor (post-task curation)
|
|
4
|
+
*
|
|
5
|
+
* `/vibe.verify` 마지막 단계에서 호출됨. PostToolUse hot-path 가 아니라
|
|
6
|
+
* 명시적 1회 호출이므로 LLM 호출 허용.
|
|
7
|
+
*
|
|
8
|
+
* 동작:
|
|
9
|
+
* 1) `.vibe/metrics/current-run.jsonl` 읽기 (Phase 1 의 산출)
|
|
10
|
+
* 2) 휴리스틱 게이트: total_tools ≥ 8 AND fail_count ≥ 3 (= 어렵게 푼 task)
|
|
11
|
+
* 3) Haiku 로 3줄 요약 (When/Recipe/Anti-tip)
|
|
12
|
+
* 4) `.vibe/recipes/<slug>.md` 작성 (frontmatter 강제)
|
|
13
|
+
*
|
|
14
|
+
* 안전성:
|
|
15
|
+
* - 모든 실패 silent — recipe 가 없는 게 잘못된 recipe 보다 낫다.
|
|
16
|
+
* - LLM 호출 재귀 가드: VIBE_HOOK_DEPTH (다른 hook 와 동일 패턴).
|
|
17
|
+
* - 테스트 모드: VIBE_RECIPE_LLM=mock 이면 LLM 스킵, 결정적 stub 사용.
|
|
18
|
+
*
|
|
19
|
+
* 사용:
|
|
20
|
+
* node hooks/scripts/recipe-extractor.js
|
|
21
|
+
*/
|
|
22
|
+
import fs from 'fs';
|
|
23
|
+
import path from 'path';
|
|
24
|
+
import { spawnSync } from 'child_process';
|
|
25
|
+
import { PROJECT_DIR, projectVibePath, projectVibeRoot } from './utils.js';
|
|
26
|
+
|
|
27
|
+
const METRICS_DIR = projectVibePath(PROJECT_DIR, 'metrics');
|
|
28
|
+
const CURRENT_RUN_JSONL = path.join(METRICS_DIR, 'current-run.jsonl');
|
|
29
|
+
const CURRENT_RUN_JSON = path.join(METRICS_DIR, 'current-run.json');
|
|
30
|
+
|
|
31
|
+
// 휴리스틱 게이트 — SPEC 의 "Risk #1: LLM 호출 비용" 대응
|
|
32
|
+
const MIN_TOOLS = 8;
|
|
33
|
+
const MIN_FAILS = 3;
|
|
34
|
+
|
|
35
|
+
const HAIKU_MODEL = 'claude-haiku-4-5-20251001';
|
|
36
|
+
|
|
37
|
+
// ─────────────────────────────────────────────────────
|
|
38
|
+
// 재귀 가드
|
|
39
|
+
// ─────────────────────────────────────────────────────
|
|
40
|
+
function isRecursive() {
|
|
41
|
+
const depth = parseInt(process.env.VIBE_HOOK_DEPTH || '0', 10);
|
|
42
|
+
return depth >= 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─────────────────────────────────────────────────────
|
|
46
|
+
// jsonl 로드 + 게이트 평가
|
|
47
|
+
// ─────────────────────────────────────────────────────
|
|
48
|
+
function loadJsonl() {
|
|
49
|
+
if (!fs.existsSync(CURRENT_RUN_JSONL)) return [];
|
|
50
|
+
return fs.readFileSync(CURRENT_RUN_JSONL, 'utf-8')
|
|
51
|
+
.split('\n').filter(Boolean)
|
|
52
|
+
.map((l) => { try { return JSON.parse(l); } catch { return null; } })
|
|
53
|
+
.filter(Boolean);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function loadCurrentRunMeta() {
|
|
57
|
+
try { return JSON.parse(fs.readFileSync(CURRENT_RUN_JSON, 'utf-8')); }
|
|
58
|
+
catch { return { feature: null, startedAt: null, steps: 0 }; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function evaluateGate(records) {
|
|
62
|
+
const total = records.length;
|
|
63
|
+
const fails = records.filter((r) => r.ok === false).length;
|
|
64
|
+
const lastIsSuccess = records.length > 0 && records[records.length - 1].ok === true;
|
|
65
|
+
const passes = total >= MIN_TOOLS && fails >= MIN_FAILS && lastIsSuccess;
|
|
66
|
+
return { passes, total, fails };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─────────────────────────────────────────────────────
|
|
70
|
+
// LLM 프롬프트 빌드
|
|
71
|
+
// ─────────────────────────────────────────────────────
|
|
72
|
+
function buildPrompt(records, meta) {
|
|
73
|
+
const tools = [...new Set(records.map((r) => r.tool))].join(', ');
|
|
74
|
+
const failsByCategory = {};
|
|
75
|
+
for (const r of records) {
|
|
76
|
+
if (r.ok === false && r.error_category) {
|
|
77
|
+
failsByCategory[r.error_category] = (failsByCategory[r.error_category] || 0) + 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const failSummary = Object.entries(failsByCategory)
|
|
81
|
+
.map(([cat, n]) => `${cat}×${n}`).join(', ') || 'none';
|
|
82
|
+
|
|
83
|
+
// 최근 12개 호출만 — 토큰 절약
|
|
84
|
+
const tail = records.slice(-12).map((r, i) => {
|
|
85
|
+
const mark = r.ok ? '✓' : '✗';
|
|
86
|
+
const cat = r.error_category ? ` [${r.error_category}]` : '';
|
|
87
|
+
const file = r.target_file ? ` ${r.target_file}` : '';
|
|
88
|
+
return `${i + 1}. ${mark} ${r.tool}${file}${cat}`;
|
|
89
|
+
}).join('\n');
|
|
90
|
+
|
|
91
|
+
return `You are summarizing a successfully completed task into a 3-line reusable recipe.
|
|
92
|
+
|
|
93
|
+
Task feature: ${meta.feature || 'unknown'}
|
|
94
|
+
Tool calls: ${records.length} total, ${failSummary}
|
|
95
|
+
Tools used: ${tools}
|
|
96
|
+
|
|
97
|
+
Last 12 calls (chronological):
|
|
98
|
+
${tail}
|
|
99
|
+
|
|
100
|
+
Output EXACTLY 3 lines of plain text, no markdown, no preamble:
|
|
101
|
+
LINE 1 — When to use this recipe (1 sentence, the situation/context)
|
|
102
|
+
LINE 2 — The working approach (1-2 sentences, what actually worked)
|
|
103
|
+
LINE 3 — Anti-tip: one thing to avoid (1 sentence)
|
|
104
|
+
|
|
105
|
+
Be specific — name the tools, the file types, the error categories. Do not output anything else.`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─────────────────────────────────────────────────────
|
|
109
|
+
// claude CLI 호출 — --model 우선, fallback 가능
|
|
110
|
+
// ─────────────────────────────────────────────────────
|
|
111
|
+
let _modelFlagSupported = null;
|
|
112
|
+
|
|
113
|
+
function detectModelFlag() {
|
|
114
|
+
if (_modelFlagSupported !== null) return _modelFlagSupported;
|
|
115
|
+
try {
|
|
116
|
+
const help = spawnSync('claude', ['--help'], { encoding: 'utf-8', timeout: 3000 });
|
|
117
|
+
const out = (help.stdout || '') + (help.stderr || '');
|
|
118
|
+
_modelFlagSupported = /--model\b/.test(out);
|
|
119
|
+
} catch {
|
|
120
|
+
_modelFlagSupported = false;
|
|
121
|
+
}
|
|
122
|
+
return _modelFlagSupported;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function callClaude(prompt) {
|
|
126
|
+
if (process.env.VIBE_RECIPE_LLM === 'mock') {
|
|
127
|
+
// 테스트용 결정적 stub
|
|
128
|
+
return {
|
|
129
|
+
ok: true,
|
|
130
|
+
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.',
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const args = ['--print', '--dangerously-skip-permissions'];
|
|
135
|
+
if (detectModelFlag()) {
|
|
136
|
+
args.unshift('--model', HAIKU_MODEL);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const env = { ...process.env, VIBE_HOOK_DEPTH: '1' };
|
|
140
|
+
try {
|
|
141
|
+
const r = spawnSync('claude', args, {
|
|
142
|
+
input: prompt,
|
|
143
|
+
encoding: 'utf-8',
|
|
144
|
+
timeout: 60_000,
|
|
145
|
+
env,
|
|
146
|
+
});
|
|
147
|
+
if (r.status !== 0) return { ok: false, text: '' };
|
|
148
|
+
const text = (r.stdout || '').trim();
|
|
149
|
+
if (!text) return { ok: false, text: '' };
|
|
150
|
+
return { ok: true, text };
|
|
151
|
+
} catch {
|
|
152
|
+
return { ok: false, text: '' };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ─────────────────────────────────────────────────────
|
|
157
|
+
// Recipe md 작성
|
|
158
|
+
// ─────────────────────────────────────────────────────
|
|
159
|
+
function timestampSlug() {
|
|
160
|
+
const d = new Date();
|
|
161
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
162
|
+
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function safeFeature(name) {
|
|
166
|
+
if (!name) return 'anon';
|
|
167
|
+
return String(name).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 40) || 'anon';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function writeRecipe({ records, meta, summary }) {
|
|
171
|
+
const vibeRoot = projectVibeRoot(PROJECT_DIR);
|
|
172
|
+
const dir = path.join(vibeRoot, 'recipes');
|
|
173
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
174
|
+
|
|
175
|
+
const slug = `${safeFeature(meta.feature)}__${timestampSlug()}`;
|
|
176
|
+
const file = path.join(dir, `${slug}.md`);
|
|
177
|
+
if (fs.existsSync(file)) return null; // 동시 호출 dedup
|
|
178
|
+
|
|
179
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
180
|
+
const tools = [...new Set(records.map((r) => r.tool))];
|
|
181
|
+
const failCount = records.filter((r) => r.ok === false).length;
|
|
182
|
+
|
|
183
|
+
// summary 가 3줄 보장 안 되면 채워서 안전화
|
|
184
|
+
const lines = summary.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
185
|
+
while (lines.length < 3) lines.push('—');
|
|
186
|
+
const [whenLine, recipeLine, antiTip] = lines.slice(0, 3);
|
|
187
|
+
|
|
188
|
+
const body =
|
|
189
|
+
`---
|
|
190
|
+
slug: ${slug}
|
|
191
|
+
type: recipe
|
|
192
|
+
symptom-context: "${(meta.feature || 'unknown').replace(/"/g, '\\"')}"
|
|
193
|
+
recipe: "${recipeLine.replace(/"/g, '\\"')}"
|
|
194
|
+
tools-touched: [${tools.map((t) => JSON.stringify(t)).join(', ')}]
|
|
195
|
+
retry-count-saved: ${failCount}
|
|
196
|
+
created: ${today}
|
|
197
|
+
source-run: "${meta.startedAt || 'unknown'}"
|
|
198
|
+
confidence: low
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
# Recipe: ${meta.feature || 'unknown'}
|
|
202
|
+
|
|
203
|
+
## When to use
|
|
204
|
+
${whenLine}
|
|
205
|
+
|
|
206
|
+
## What worked
|
|
207
|
+
${recipeLine}
|
|
208
|
+
|
|
209
|
+
## Avoid
|
|
210
|
+
${antiTip}
|
|
211
|
+
|
|
212
|
+
## Stats
|
|
213
|
+
- Total tool calls: ${records.length}
|
|
214
|
+
- Failures before success: ${failCount}
|
|
215
|
+
- Tools touched: ${tools.join(', ')}
|
|
216
|
+
- Source run started: ${meta.startedAt || 'unknown'}
|
|
217
|
+
|
|
218
|
+
> Auto-generated by \`recipe-extractor.js\` from \`.vibe/metrics/current-run.jsonl\`. \
|
|
219
|
+
Confidence \`low\` — single observation. Increment to \`medium\`/\`high\` when the same recipe is seen multiple times across runs.
|
|
220
|
+
`;
|
|
221
|
+
|
|
222
|
+
fs.writeFileSync(file, body);
|
|
223
|
+
return file;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ─────────────────────────────────────────────────────
|
|
227
|
+
// 메인
|
|
228
|
+
// ─────────────────────────────────────────────────────
|
|
229
|
+
function main() {
|
|
230
|
+
if (isRecursive()) return; // claude CLI 가 다시 우리 hook 을 트리거하는 것 방지
|
|
231
|
+
|
|
232
|
+
let records;
|
|
233
|
+
try { records = loadJsonl(); } catch { return; }
|
|
234
|
+
if (records.length === 0) return;
|
|
235
|
+
|
|
236
|
+
const gate = evaluateGate(records);
|
|
237
|
+
if (!gate.passes) return;
|
|
238
|
+
|
|
239
|
+
const meta = loadCurrentRunMeta();
|
|
240
|
+
const prompt = buildPrompt(records, meta);
|
|
241
|
+
const llm = callClaude(prompt);
|
|
242
|
+
if (!llm.ok) return;
|
|
243
|
+
|
|
244
|
+
try { writeRecipe({ records, meta, summary: llm.text }); }
|
|
245
|
+
catch { /* silent */ }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try { main(); } catch { /* never throw */ }
|
|
249
|
+
process.exit(0);
|
|
@@ -85,6 +85,25 @@ async function main() {
|
|
|
85
85
|
console.log('\n[Recent Memories]');
|
|
86
86
|
console.log(memories.content[0].text);
|
|
87
87
|
|
|
88
|
+
// Phase 3 — Recipes + anti-patterns 인덱스 (post-task curation)
|
|
89
|
+
// 본문이 아닌 1줄 요약만. session-context 가 비대해지지 않도록 N=5 상한.
|
|
90
|
+
try {
|
|
91
|
+
const { loadCurationIndex } = await import('./lib/curation-index.js');
|
|
92
|
+
const index = loadCurationIndex(PROJECT_DIR, { recipeLimit: 5, antiPatternLimit: 5 });
|
|
93
|
+
if (index.recipes.length > 0) {
|
|
94
|
+
console.log('\n[Recipes — succeeded patterns]');
|
|
95
|
+
for (const r of index.recipes) {
|
|
96
|
+
console.log(` • ${r.slug} — ${r.summary}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (index.antiPatterns.length > 0) {
|
|
100
|
+
console.log('\n[Anti-patterns — known pitfalls]');
|
|
101
|
+
for (const a of index.antiPatterns) {
|
|
102
|
+
console.log(` ⚠ ${a.tag}: ${a.summary}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch { /* curation is best-effort */ }
|
|
106
|
+
|
|
88
107
|
// Version check
|
|
89
108
|
if (latestVersion) {
|
|
90
109
|
const currentVersion = getCurrentVersion();
|
|
@@ -1,44 +1,253 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* PostToolUse Hook
|
|
3
|
+
* PostToolUse Hook — 툴콜 스텝 카운터 + 패턴 로거 + 3-fail 감지기
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* 책임 1) 모든 성공 툴콜을 1 스텝으로 집계 → `current-run.json`
|
|
6
|
+
* ↳ /vibe.verify 가 history.jsonl에 append 후 출력
|
|
7
|
+
* 책임 2) 각 툴콜 한 줄을 `current-run.jsonl` 에 append (post-task-curation SPEC)
|
|
8
|
+
* ↳ Phase 3 의 recipe extractor 가 소비
|
|
9
|
+
* 책임 3) (Phase 2) 실패 메시지 분류 + 같은 (file, category) 3회 반복 감지 →
|
|
10
|
+
* `.vibe/anti-patterns/<slug>.md` 로 자동 저장.
|
|
7
11
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* - 이후 툴콜마다 steps += 1
|
|
11
|
-
* - /vibe.verify가 읽어서 history.jsonl에 append 후 출력
|
|
12
|
-
*
|
|
13
|
-
* 실패 시 조용히 통과 (차단 금지).
|
|
12
|
+
* 세 책임은 독립 — 어느 한쪽 실패가 다른 쪽을 막지 않는다.
|
|
13
|
+
* PostToolUse 는 hot path 이므로 LLM 호출/외부 spawn 금지, fs 만 사용.
|
|
14
14
|
*/
|
|
15
15
|
import fs from 'fs';
|
|
16
16
|
import path from 'path';
|
|
17
17
|
import { PROJECT_DIR, projectVibePath } from './utils.js';
|
|
18
18
|
|
|
19
19
|
const METRICS_DIR = projectVibePath(PROJECT_DIR, 'metrics');
|
|
20
|
-
const
|
|
20
|
+
const ANTI_PATTERNS_DIR = projectVibePath(PROJECT_DIR, 'anti-patterns');
|
|
21
|
+
const CURRENT_RUN_JSON = path.join(METRICS_DIR, 'current-run.json');
|
|
22
|
+
const CURRENT_RUN_JSONL = path.join(METRICS_DIR, 'current-run.jsonl');
|
|
23
|
+
const MAX_JSONL_LINES = 5000;
|
|
24
|
+
const FAIL_WINDOW = 10;
|
|
25
|
+
const FAIL_THRESHOLD = 3;
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
// ─────────────────────────────────────────────────────
|
|
28
|
+
// stdin / env 에서 PostToolUse payload 추출
|
|
29
|
+
// ─────────────────────────────────────────────────────
|
|
30
|
+
function readStdinSync() {
|
|
31
|
+
try {
|
|
32
|
+
if (process.stdin.isTTY) return null;
|
|
33
|
+
const buf = Buffer.alloc(65536);
|
|
34
|
+
const bytesRead = fs.readSync(0, buf, 0, buf.length, null);
|
|
35
|
+
if (bytesRead > 0) return JSON.parse(buf.toString('utf-8', 0, bytesRead));
|
|
36
|
+
} catch { /* ignore */ }
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseToolInput(raw) {
|
|
41
|
+
if (!raw) return {};
|
|
42
|
+
if (typeof raw === 'string') {
|
|
43
|
+
try { return JSON.parse(raw); } catch { return {}; }
|
|
25
44
|
}
|
|
45
|
+
return raw;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function extractTargetFile(toolInput) {
|
|
49
|
+
const fp = toolInput.file_path || toolInput.notebook_path || toolInput.path || null;
|
|
50
|
+
if (!fp) return null;
|
|
51
|
+
try {
|
|
52
|
+
const abs = path.isAbsolute(fp) ? fp : path.resolve(PROJECT_DIR, fp);
|
|
53
|
+
const rel = path.relative(path.resolve(PROJECT_DIR), abs).replace(/\\/g, '/');
|
|
54
|
+
return rel || path.basename(fp);
|
|
55
|
+
} catch {
|
|
56
|
+
return fp;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isResponseError(toolResponse) {
|
|
61
|
+
if (!toolResponse) return false;
|
|
62
|
+
if (typeof toolResponse === 'object') {
|
|
63
|
+
if (toolResponse.is_error === true) return true;
|
|
64
|
+
if (toolResponse.error) return true;
|
|
65
|
+
if (Array.isArray(toolResponse.errors) && toolResponse.errors.length > 0) return true;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
26
69
|
|
|
70
|
+
// ─────────────────────────────────────────────────────
|
|
71
|
+
// 책임 3a: error_category 분류 (regex 만 — regress tag enum 일부)
|
|
72
|
+
// ─────────────────────────────────────────────────────
|
|
73
|
+
const ERROR_CATEGORIES = [
|
|
74
|
+
{ tag: 'nullability', re: /Cannot read propert(?:y|ies) of (?:undefined|null)|TypeError[^\n]*(?:undefined|null)/i },
|
|
75
|
+
{ tag: 'type-narrow', re: /\bTS2345\b|not assignable to (?:parameter|type)/i },
|
|
76
|
+
{ tag: 'compilation', re: /\bTS\d{4}\b|cannot find name|build failed|compilation (?:error|failed)/i },
|
|
77
|
+
{ tag: 'syntax', re: /SyntaxError|Unexpected token/i },
|
|
78
|
+
{ tag: 'integration', re: /ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET|connect failed/i },
|
|
79
|
+
{ tag: 'auth', re: /\b(?:401|403)\b|unauthori[sz]ed|forbidden|invalid (?:token|credentials)/i },
|
|
80
|
+
{ tag: 'permission', re: /EACCES|permission denied/i },
|
|
81
|
+
{ tag: 'not-found', re: /ENOENT|no such file|not found/i },
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
function classifyError(toolResponse) {
|
|
85
|
+
if (!toolResponse) return null;
|
|
86
|
+
let text;
|
|
87
|
+
if (typeof toolResponse === 'string') text = toolResponse;
|
|
88
|
+
else if (toolResponse.error) text = String(toolResponse.error);
|
|
89
|
+
else if (Array.isArray(toolResponse.errors)) text = JSON.stringify(toolResponse.errors);
|
|
90
|
+
else if (toolResponse.content) text = JSON.stringify(toolResponse.content);
|
|
91
|
+
else text = JSON.stringify(toolResponse);
|
|
92
|
+
|
|
93
|
+
for (const { tag, re } of ERROR_CATEGORIES) {
|
|
94
|
+
if (re.test(text)) return tag;
|
|
95
|
+
}
|
|
96
|
+
return 'other';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─────────────────────────────────────────────────────
|
|
100
|
+
// 책임 1: current-run.json 카운터
|
|
101
|
+
// ─────────────────────────────────────────────────────
|
|
102
|
+
function bumpCounter() {
|
|
27
103
|
let data = { feature: null, startedAt: null, steps: 0 };
|
|
28
|
-
if (fs.existsSync(
|
|
104
|
+
if (fs.existsSync(CURRENT_RUN_JSON)) {
|
|
29
105
|
try {
|
|
30
|
-
data = JSON.parse(fs.readFileSync(
|
|
31
|
-
} catch {
|
|
32
|
-
|
|
106
|
+
data = JSON.parse(fs.readFileSync(CURRENT_RUN_JSON, 'utf-8'));
|
|
107
|
+
} catch { /* 손상 → 새로 시작 */ }
|
|
108
|
+
}
|
|
109
|
+
if (!data.startedAt) data.startedAt = new Date().toISOString();
|
|
110
|
+
data.steps = (data.steps || 0) + 1;
|
|
111
|
+
fs.writeFileSync(CURRENT_RUN_JSON, JSON.stringify(data, null, 2));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─────────────────────────────────────────────────────
|
|
115
|
+
// 책임 2: current-run.jsonl append + error_category 채움
|
|
116
|
+
// ─────────────────────────────────────────────────────
|
|
117
|
+
function appendJsonl(stdinPayload) {
|
|
118
|
+
const toolName = stdinPayload?.tool_name || process.argv[2] || '';
|
|
119
|
+
if (!toolName) return null;
|
|
120
|
+
|
|
121
|
+
const toolInput = parseToolInput(stdinPayload?.tool_input ?? process.env.TOOL_INPUT);
|
|
122
|
+
const ok = !isResponseError(stdinPayload?.tool_response);
|
|
123
|
+
const errorCategory = ok ? null : classifyError(stdinPayload?.tool_response);
|
|
124
|
+
|
|
125
|
+
const record = {
|
|
126
|
+
ts: new Date().toISOString(),
|
|
127
|
+
tool: toolName,
|
|
128
|
+
ok,
|
|
129
|
+
target_file: extractTargetFile(toolInput),
|
|
130
|
+
error_category: errorCategory,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
fs.appendFileSync(CURRENT_RUN_JSONL, JSON.stringify(record) + '\n');
|
|
134
|
+
|
|
135
|
+
// 회전: 라인 수가 상한 초과 시 마지막 절반만 남김.
|
|
136
|
+
try {
|
|
137
|
+
const stat = fs.statSync(CURRENT_RUN_JSONL);
|
|
138
|
+
if (stat.size > 2 * 1024 * 1024) {
|
|
139
|
+
const lines = fs.readFileSync(CURRENT_RUN_JSONL, 'utf-8').split('\n').filter(Boolean);
|
|
140
|
+
if (lines.length > MAX_JSONL_LINES) {
|
|
141
|
+
const keep = lines.slice(-Math.floor(MAX_JSONL_LINES / 2));
|
|
142
|
+
fs.writeFileSync(CURRENT_RUN_JSONL, keep.join('\n') + '\n');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch { /* 회전 실패는 무시 */ }
|
|
146
|
+
|
|
147
|
+
return record;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ─────────────────────────────────────────────────────
|
|
151
|
+
// 책임 3b: 3-fail detector → anti-pattern md
|
|
152
|
+
// ─────────────────────────────────────────────────────
|
|
153
|
+
const SUGGESTED_STOPS = {
|
|
154
|
+
nullability: '같은 위치 null/undefined 처리 반복 — 타입 가드 또는 옵셔널 체이닝 필요',
|
|
155
|
+
'type-narrow': '같은 위치 타입 좁히기 반복 — 타입 정의 점검 필요',
|
|
156
|
+
compilation: '컴파일 실패 반복 — 타입/임포트 정의 확인 필요',
|
|
157
|
+
syntax: '구문 오류 반복 — 파서 호환성/버전 확인 필요',
|
|
158
|
+
integration: '외부 서비스 연결 실패 반복 — endpoint/포트/네트워크 점검 필요',
|
|
159
|
+
auth: '인증 실패 반복 — 토큰/자격증명 점검 필요',
|
|
160
|
+
permission: '권한 실패 반복 — 파일/디렉토리 권한 점검 필요',
|
|
161
|
+
'not-found': '리소스 부재 반복 — 경로/존재 확인 필요',
|
|
162
|
+
other: '같은 패턴 실패 반복 — 접근 방식 재검토 필요',
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
function fileSlug(targetFile) {
|
|
166
|
+
if (!targetFile) return 'global';
|
|
167
|
+
return targetFile.replace(/[\/\.]/g, '-');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function readTailWindow() {
|
|
171
|
+
if (!fs.existsSync(CURRENT_RUN_JSONL)) return [];
|
|
172
|
+
const raw = fs.readFileSync(CURRENT_RUN_JSONL, 'utf-8').split('\n').filter(Boolean);
|
|
173
|
+
return raw.slice(-FAIL_WINDOW).map((l) => {
|
|
174
|
+
try { return JSON.parse(l); } catch { return null; }
|
|
175
|
+
}).filter(Boolean);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function detectThreeFail() {
|
|
179
|
+
const window = readTailWindow();
|
|
180
|
+
const groups = new Map();
|
|
181
|
+
for (const r of window) {
|
|
182
|
+
if (r.ok || !r.error_category) continue;
|
|
183
|
+
const key = `${r.target_file ?? 'null'}::${r.error_category}`;
|
|
184
|
+
const cur = groups.get(key) ?? 0;
|
|
185
|
+
groups.set(key, cur + 1);
|
|
186
|
+
}
|
|
187
|
+
for (const [key, count] of groups) {
|
|
188
|
+
if (count >= FAIL_THRESHOLD) {
|
|
189
|
+
const sep = key.indexOf('::');
|
|
190
|
+
const file = key.slice(0, sep);
|
|
191
|
+
const cat = key.slice(sep + 2);
|
|
192
|
+
return { category: cat, targetFile: file === 'null' ? null : file, count };
|
|
33
193
|
}
|
|
34
194
|
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
35
197
|
|
|
36
|
-
|
|
37
|
-
|
|
198
|
+
function writeAntiPattern({ category, targetFile, count }) {
|
|
199
|
+
if (!fs.existsSync(ANTI_PATTERNS_DIR)) {
|
|
200
|
+
fs.mkdirSync(ANTI_PATTERNS_DIR, { recursive: true });
|
|
38
201
|
}
|
|
39
|
-
|
|
202
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
203
|
+
const dateSlug = today.replace(/-/g, '');
|
|
204
|
+
const fSlug = fileSlug(targetFile);
|
|
205
|
+
const slug = `${category}__${fSlug}__${dateSlug}`;
|
|
206
|
+
const filePath = path.join(ANTI_PATTERNS_DIR, `${slug}.md`);
|
|
207
|
+
if (fs.existsSync(filePath)) return; // dedup
|
|
40
208
|
|
|
41
|
-
|
|
209
|
+
const trigger = `(file=${targetFile ?? 'null'}, category=${category})`;
|
|
210
|
+
const stop = SUGGESTED_STOPS[category] ?? SUGGESTED_STOPS.other;
|
|
211
|
+
const content = `---
|
|
212
|
+
slug: ${slug}
|
|
213
|
+
type: anti-pattern
|
|
214
|
+
root-cause-tag: ${category}
|
|
215
|
+
trigger-signature: "${trigger}"
|
|
216
|
+
fail-count: ${count}
|
|
217
|
+
suggested-stop: "${stop}"
|
|
218
|
+
created: ${today}
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Trigger
|
|
222
|
+
${trigger} 이 ${FAIL_WINDOW} 툴콜 윈도우 내 ${count} 회 발생.
|
|
223
|
+
|
|
224
|
+
## Suggested Stop
|
|
225
|
+
${stop}
|
|
226
|
+
|
|
227
|
+
> 자동 생성 — 같은 패턴 반복 시 다른 접근법을 고려하거나 사용자에게 조언을 구할 것.
|
|
228
|
+
`;
|
|
229
|
+
fs.writeFileSync(filePath, content);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ─────────────────────────────────────────────────────
|
|
233
|
+
// 메인 — 세 책임을 독립적으로 try
|
|
234
|
+
// ─────────────────────────────────────────────────────
|
|
235
|
+
try {
|
|
236
|
+
if (!fs.existsSync(METRICS_DIR)) {
|
|
237
|
+
fs.mkdirSync(METRICS_DIR, { recursive: true });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const stdinPayload = readStdinSync();
|
|
241
|
+
|
|
242
|
+
try { bumpCounter(); } catch { /* 카운터 실패 무시 */ }
|
|
243
|
+
let lastRecord = null;
|
|
244
|
+
try { lastRecord = appendJsonl(stdinPayload); } catch { /* 로깅 실패 무시 */ }
|
|
245
|
+
try {
|
|
246
|
+
if (lastRecord && !lastRecord.ok && lastRecord.error_category) {
|
|
247
|
+
const trip = detectThreeFail();
|
|
248
|
+
if (trip) writeAntiPattern(trip);
|
|
249
|
+
}
|
|
250
|
+
} catch { /* anti-pattern 작성 실패 무시 */ }
|
|
42
251
|
} catch {
|
|
43
252
|
// Never block on counter failure
|
|
44
253
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@su-record/vibe",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "AI Coding Framework for Claude Code — 56 agents, 45 skills, multi-LLM orchestration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli/index.js",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"dev": "tsc --watch",
|
|
28
28
|
"gen:skill-docs": "npx tsx scripts/gen-skill-docs.ts",
|
|
29
29
|
"gen:skill-docs:check": "npx tsx scripts/gen-skill-docs.ts --check",
|
|
30
|
+
"validate:skill-invocation": "npx tsx scripts/validate-skill-invocation.ts",
|
|
30
31
|
"test": "vitest run",
|
|
31
32
|
"test:watch": "vitest",
|
|
32
33
|
"prepublishOnly": "pnpm build",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agents-md
|
|
3
|
+
user-invocable: false
|
|
4
|
+
invocation: [auto, chain]
|
|
3
5
|
tier: standard
|
|
4
6
|
description: "Optimize AGENTS.md / CLAUDE.md by removing discoverable info and keeping only gotchas. Based on Addy Osmani's AGENTS.md principles. Activates on agents.md, claude.md, context file optimization."
|
|
5
7
|
triggers: [agents.md, claude.md, context file, optimize agents, optimize claude]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: arch-guard
|
|
3
|
+
user-invocable: false
|
|
4
|
+
invocation: [auto]
|
|
3
5
|
tier: core
|
|
4
6
|
description: "Generate architecture boundary tests that mechanically enforce layer constraints. Use when adding new modules, refactoring layers, or after detecting circular dependencies. Creates import-rule tests (e.g., 'UI must not import DB') that fail CI on violation. Must use this skill when user mentions layer enforcement, dependency rules, or architectural boundaries — even casually like 'make sure services don't import controllers'."
|
|
5
7
|
triggers: [arch guard, architecture test, layer test, boundary test, structural test, arch validation]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: brand-assets
|
|
3
|
+
invocation: [auto]
|
|
3
4
|
tier: standard
|
|
4
5
|
description: "Auto-generate app icons (iOS/Android/PWA), favicons, and OG images from SPEC brand information using Gemini Image API. Use when the project needs visual brand assets, when user mentions 'icon', 'favicon', 'logo', or 'brand assets', or when a SPEC defines brand colors/identity but no assets exist yet. Outputs multiple sizes and formats ready for deployment. Not for complex illustration or marketing graphics — focused on app identity assets."
|
|
5
6
|
triggers: [icon, favicon, brand, logo, app icon, branding, assets]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: capability-loop
|
|
3
|
+
user-invocable: false
|
|
4
|
+
invocation: [auto]
|
|
3
5
|
tier: standard
|
|
4
6
|
description: "When an agent fails, diagnose which capability is missing and build it into the repo. Activates after repeated agent failures, tool errors, or when a task keeps failing in the same way. Analyzes failure transcripts, identifies the missing guardrail/tool/abstraction/doc, and creates it permanently. Use this skill whenever you see 3+ similar failures, an agent hitting the same wall repeatedly, or the user asking 'why does this keep failing'."
|
|
5
7
|
triggers: [capability loop, failure loop, build capability, missing capability, agent failed, why did it fail]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: characterization-test
|
|
3
|
+
user-invocable: false
|
|
4
|
+
invocation: [auto]
|
|
3
5
|
tier: core
|
|
4
6
|
description: "Lock existing behavior with characterization tests before modifying code. Use BEFORE any refactor, rewrite, or large-scale modification of existing code — especially legacy code without tests. Captures current input/output behavior as test cases so regressions are caught immediately. Must use this skill when touching files >200 lines with no existing tests, when user says 'refactor', 'rewrite', 'modernize', or 'clean up' existing code."
|
|
5
7
|
triggers: [legacy, characterization test, lock behavior, regression prevention, before refactor, large file]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: chub-usage
|
|
3
|
+
invocation: [auto]
|
|
3
4
|
tier: optional
|
|
4
5
|
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
6
|
triggers: [chub, context hub, API docs, latest API, deprecated API, SDK documentation, api reference, 최신 문서]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: claude-md-guide
|
|
3
|
+
user-invocable: false
|
|
4
|
+
invocation: [auto]
|
|
3
5
|
tier: standard
|
|
4
6
|
description: "Guide for writing effective CLAUDE.md files from scratch. Evidence-based methodology from 40+ sources including research papers, official docs, and real-world examples. Covers 3-layer architecture, Curse of Instructions mitigation, progressive disclosure, and maintenance. Use when creating new CLAUDE.md, improving existing ones, or teaching team members how to write project instructions for AI agents."
|
|
5
7
|
triggers: [claude-md guide, write claude.md, create claude.md, claude.md 작성, 클로드 문서, project instructions, claude-md]
|