@su-record/vibe 2.8.23 → 2.8.25
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/commands/vibe.figma.md +2 -2
- package/dist/cli/commands/config.d.ts +17 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +207 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/index.d.ts +2 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +2 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/info.d.ts.map +1 -1
- package/dist/cli/commands/info.js +2 -0
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +78 -54
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/stats.d.ts +13 -0
- package/dist/cli/commands/stats.d.ts.map +1 -0
- package/dist/cli/commands/stats.js +280 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +33 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/postinstall/constants.d.ts.map +1 -1
- package/dist/cli/postinstall/constants.js +1 -0
- package/dist/cli/postinstall/constants.js.map +1 -1
- package/dist/cli/setup/GlobalInstaller.d.ts.map +1 -1
- package/dist/cli/setup/GlobalInstaller.js +7 -7
- package/dist/cli/setup/GlobalInstaller.js.map +1 -1
- package/dist/cli/setup/ProjectSetup.d.ts.map +1 -1
- package/dist/cli/setup/ProjectSetup.js +18 -12
- package/dist/cli/setup/ProjectSetup.js.map +1 -1
- package/dist/infra/lib/ContextCompressor.d.ts.map +1 -1
- package/dist/infra/lib/ContextCompressor.js +10 -4
- package/dist/infra/lib/ContextCompressor.js.map +1 -1
- package/dist/infra/lib/ProjectCache.d.ts +2 -2
- package/dist/infra/lib/ProjectCache.d.ts.map +1 -1
- package/dist/infra/lib/ProjectCache.js +4 -3
- package/dist/infra/lib/ProjectCache.js.map +1 -1
- package/dist/infra/lib/utils.d.ts +24 -0
- package/dist/infra/lib/utils.d.ts.map +1 -1
- package/dist/infra/lib/utils.js +41 -0
- package/dist/infra/lib/utils.js.map +1 -1
- package/dist/infra/orchestrator/SmartRouter.d.ts +3 -0
- package/dist/infra/orchestrator/SmartRouter.d.ts.map +1 -1
- package/dist/infra/orchestrator/SmartRouter.js +11 -1
- package/dist/infra/orchestrator/SmartRouter.js.map +1 -1
- package/dist/infra/orchestrator/SmartRouter.test.d.ts +5 -0
- package/dist/infra/orchestrator/SmartRouter.test.d.ts.map +1 -0
- package/dist/infra/orchestrator/SmartRouter.test.js +457 -0
- package/dist/infra/orchestrator/SmartRouter.test.js.map +1 -0
- package/dist/tools/convention/analyzeComplexity.d.ts.map +1 -1
- package/dist/tools/convention/analyzeComplexity.js +18 -10
- package/dist/tools/convention/analyzeComplexity.js.map +1 -1
- package/dist/tools/convention/checkCouplingCohesion.d.ts.map +1 -1
- package/dist/tools/convention/checkCouplingCohesion.js +14 -6
- package/dist/tools/convention/checkCouplingCohesion.js.map +1 -1
- package/dist/tools/semantic/analyzeDependencyGraph.d.ts.map +1 -1
- package/dist/tools/semantic/analyzeDependencyGraph.js +1 -1
- package/dist/tools/semantic/analyzeDependencyGraph.js.map +1 -1
- package/dist/tools/semantic/findReferences.d.ts.map +1 -1
- package/dist/tools/semantic/findReferences.js +13 -13
- package/dist/tools/semantic/findReferences.js.map +1 -1
- package/dist/tools/semantic/findSymbol.d.ts.map +1 -1
- package/dist/tools/semantic/findSymbol.js +12 -13
- package/dist/tools/semantic/findSymbol.js.map +1 -1
- package/dist/tools/semantic/lsp.d.ts.map +1 -1
- package/dist/tools/semantic/lsp.js +22 -14
- package/dist/tools/semantic/lsp.js.map +1 -1
- package/hooks/hooks.json +29 -0
- package/hooks/scripts/__tests__/keyword-detector.test.js +199 -0
- package/hooks/scripts/__tests__/pre-tool-guard.test.js +286 -0
- package/hooks/scripts/__tests__/sentinel-guard.test.js +210 -0
- package/hooks/scripts/auto-commit.js +65 -0
- package/hooks/scripts/auto-format.js +64 -0
- package/hooks/scripts/auto-test.js +81 -0
- package/hooks/scripts/code-check.js +139 -0
- package/hooks/scripts/command-log.js +32 -0
- package/hooks/scripts/context-save.js +60 -6
- package/hooks/scripts/hud-status.js +32 -2
- package/hooks/scripts/llm-orchestrate.js +95 -17
- package/hooks/scripts/pr-test-gate.js +52 -0
- package/package.json +1 -1
- package/skills/agents-md/rubrics/what-to-keep.md +49 -0
- package/skills/agents-md/templates/agents-md.md +36 -0
- package/skills/arch-guard/agents/detector.md +48 -0
- package/skills/arch-guard/agents/reporter.md +48 -0
- package/skills/arch-guard/agents/rule-generator.md +49 -0
- package/skills/arch-guard/agents/violation-checker.md +51 -0
- package/skills/arch-guard/frameworks/clean-architecture.md +108 -0
- package/skills/arch-guard/frameworks/solid.md +102 -0
- package/skills/arch-guard/scripts/check-boundaries.js +90 -0
- package/skills/arch-guard/templates/arch-rules.json +47 -0
- package/skills/arch-guard/templates/violation-report.md +53 -0
- package/skills/brand-assets/rubrics/asset-checklist.md +98 -0
- package/skills/brand-assets/templates/brand-guide.md +161 -0
- package/skills/capability-loop/agents/capability-designer.md +61 -0
- package/skills/capability-loop/agents/failure-analyst.md +55 -0
- package/skills/capability-loop/agents/implementer.md +50 -0
- package/skills/capability-loop/agents/tester.md +53 -0
- package/skills/capability-loop/templates/capability-spec.md +118 -0
- package/skills/capability-loop/templates/failure-analysis.md +118 -0
- package/skills/characterization-test/agents/behavior-capturer.md +50 -0
- package/skills/characterization-test/agents/coverage-checker.md +54 -0
- package/skills/characterization-test/agents/reporter.md +50 -0
- package/skills/characterization-test/agents/test-writer.md +49 -0
- package/skills/characterization-test/rubrics/coverage-criteria.md +53 -0
- package/skills/characterization-test/templates/test-template.ts +101 -0
- package/skills/claude-md-guide/rubrics/anti-patterns.md +88 -0
- package/skills/claude-md-guide/templates/claude-md.md +54 -0
- package/skills/commerce-patterns/rubrics/checkout-flow.md +48 -0
- package/skills/commerce-patterns/templates/product-schema.md +85 -0
- package/skills/commit-push-pr/agents/change-analyzer.md +55 -0
- package/skills/commit-push-pr/agents/message-writer.md +50 -0
- package/skills/commit-push-pr/agents/pr-writer.md +58 -0
- package/skills/commit-push-pr/agents/reviewer.md +52 -0
- package/skills/commit-push-pr/rubrics/commit-message.md +73 -0
- package/skills/commit-push-pr/templates/pr-body.md +63 -0
- package/skills/context7-usage/rubrics/when-to-use.md +50 -0
- package/skills/create-prd/agents/edge-case-finder.md +48 -0
- package/skills/create-prd/agents/prioritizer.md +60 -0
- package/skills/create-prd/agents/requirements-writer.md +48 -0
- package/skills/create-prd/agents/researcher.md +55 -0
- package/skills/create-prd/agents/reviewer.md +54 -0
- package/skills/create-prd/frameworks/jobs-to-be-done.md +96 -0
- package/skills/create-prd/frameworks/rice-scoring.md +97 -0
- package/skills/create-prd/orchestrator.md +70 -0
- package/skills/create-prd/rubrics/completeness.md +58 -0
- package/skills/create-prd/templates/prd.md +139 -0
- package/skills/design-audit/agents/a11y-auditor.md +43 -0
- package/skills/design-audit/agents/performance-auditor.md +46 -0
- package/skills/design-audit/agents/responsive-auditor.md +46 -0
- package/skills/design-audit/agents/scorer.md +47 -0
- package/skills/design-audit/agents/slop-detector.md +47 -0
- package/skills/design-audit/frameworks/core-web-vitals.md +107 -0
- package/skills/design-audit/frameworks/wcag-checklist.md +64 -0
- package/skills/design-audit/orchestrator.md +64 -0
- package/skills/design-audit/rubrics/ai-slop-patterns.md +83 -0
- package/skills/design-audit/rubrics/scoring.md +63 -0
- package/skills/design-audit/templates/report.md +88 -0
- package/skills/design-critique/rubrics/ux-heuristics.md +143 -0
- package/skills/design-critique/templates/critique-report.md +86 -0
- package/skills/design-distill/templates/design-system.md +132 -0
- package/skills/design-normalize/rubrics/token-naming.md +117 -0
- package/skills/design-normalize/templates/token-audit.md +89 -0
- package/skills/design-polish/rubrics/polish-checklist.md +68 -0
- package/skills/design-polish/templates/polish-report.md +64 -0
- package/skills/design-teach/rubrics/brand-personality.md +73 -0
- package/skills/design-teach/templates/design-context.json +36 -0
- package/skills/e2e-commerce/templates/test-scenarios.md +170 -0
- package/skills/event-comms/templates/email-invite.md +99 -0
- package/skills/event-comms/templates/sns-post.md +133 -0
- package/skills/event-ops/rubrics/contingency.md +85 -0
- package/skills/event-ops/templates/d-day-checklist.md +65 -0
- package/skills/event-planning/rubrics/timeline.md +70 -0
- package/skills/event-planning/templates/event-plan.md +91 -0
- package/skills/exec-plan/agents/decomposer.md +47 -0
- package/skills/exec-plan/agents/dependency-mapper.md +44 -0
- package/skills/exec-plan/agents/estimator.md +43 -0
- package/skills/exec-plan/agents/validator.md +55 -0
- package/skills/exec-plan/orchestrator.md +70 -0
- package/skills/exec-plan/rubrics/complexity-scoring.md +75 -0
- package/skills/exec-plan/templates/plan.md +147 -0
- package/skills/git-worktree/rubrics/when-to-use.md +55 -0
- package/skills/handoff/agents/context-summarizer.md +51 -0
- package/skills/handoff/agents/document-writer.md +63 -0
- package/skills/handoff/agents/state-collector.md +53 -0
- package/skills/handoff/agents/verifier.md +48 -0
- package/skills/handoff/rubrics/completeness.md +62 -0
- package/skills/handoff/templates/handoff.md +107 -0
- package/skills/parallel-research/agents/best-practices.md +43 -0
- package/skills/parallel-research/agents/codebase-patterns.md +46 -0
- package/skills/parallel-research/agents/framework-docs.md +45 -0
- package/skills/parallel-research/agents/security-advisory.md +46 -0
- package/skills/parallel-research/agents/synthesizer.md +52 -0
- package/skills/parallel-research/experts/best-practices.md +50 -0
- package/skills/parallel-research/experts/codebase-patterns.md +70 -0
- package/skills/parallel-research/experts/framework-docs.md +65 -0
- package/skills/parallel-research/experts/security-advisory.md +69 -0
- package/skills/parallel-research/orchestrator.md +65 -0
- package/skills/parallel-research/templates/synthesis.md +101 -0
- package/skills/prioritization-frameworks/rubrics/frameworks.md +79 -0
- package/skills/prioritization-frameworks/templates/scoring-matrix.md +69 -0
- package/skills/priority-todos/rubrics/prioritization.md +70 -0
- package/skills/priority-todos/templates/todo-board.md +59 -0
- package/skills/seo-checklist/frameworks/structured-data.md +153 -0
- package/skills/seo-checklist/rubrics/content-seo.md +42 -0
- package/skills/seo-checklist/rubrics/technical-seo.md +48 -0
- package/skills/techdebt/agents/analyzer.md +50 -0
- package/skills/techdebt/agents/fixer.md +41 -0
- package/skills/techdebt/agents/reviewer.md +47 -0
- package/skills/techdebt/agents/scanner.md +44 -0
- package/skills/techdebt/orchestrator.md +70 -0
- package/skills/techdebt/rubrics/severity.md +51 -0
- package/skills/techdebt/scripts/scan.js +90 -0
- package/skills/techdebt/templates/report.md +86 -0
- package/skills/tool-fallback/rubrics/fallback-chain.md +58 -0
- package/skills/typescript-advanced-types/rubrics/type-patterns.md +109 -0
- package/skills/ui-ux-pro-max/rubrics/interaction-states.md +83 -0
- package/skills/ui-ux-pro-max/rubrics/responsive-breakpoints.md +99 -0
- package/skills/user-personas/rubrics/research-methods.md +56 -0
- package/skills/user-personas/templates/persona.md +89 -0
- package/skills/vercel-react-best-practices/rubrics/performance.md +82 -0
- package/skills/vercel-react-best-practices/rubrics/server-components.md +86 -0
- package/skills/vibe-docs/SKILL.md +171 -0
- package/skills/vibe-docs/templates/architecture.md +80 -0
- package/skills/vibe-docs/templates/readme.md +84 -0
- package/skills/vibe-docs/templates/release-notes.md +74 -0
- package/skills/vibe-figma/SKILL.md +173 -54
- package/skills/vibe-figma/rubrics/extraction-checklist.md +51 -0
- package/skills/vibe-figma/templates/figma-handoff.md +96 -0
- package/skills/vibe-figma-analyze/rubrics/analysis-dimensions.md +53 -0
- package/skills/vibe-figma-codegen/rubrics/code-quality.md +54 -0
- package/skills/vibe-figma-consolidate/templates/consolidation-report.md +95 -0
- package/skills/vibe-figma-convert/SKILL.md +176 -1
- package/skills/vibe-figma-convert/rubrics/conversion-rules.md +83 -0
- package/skills/vibe-figma-convert/templates/component.md +152 -0
- package/skills/vibe-figma-extract/rubrics/image-rules.md +67 -0
- package/skills/vibe-figma-frame/rubrics/frame-selection.md +55 -0
- package/skills/vibe-figma-pipeline/rubrics/pipeline-stages.md +96 -0
- package/skills/vibe-figma-rules/rubrics/naming-conventions.md +70 -0
- package/skills/vibe-figma-style/rubrics/style-mapping.md +100 -0
- package/skills/video-production/rubrics/quality-checklist.md +58 -0
- package/skills/video-production/templates/production-plan.md +104 -0
|
@@ -24,6 +24,43 @@ const summaryMap = {
|
|
|
24
24
|
// Debounce: track last reflection level per session to avoid duplicates
|
|
25
25
|
let lastReflectionLevel = null;
|
|
26
26
|
|
|
27
|
+
// Guard against recursive reflection and rapid threshold crossings
|
|
28
|
+
let reflectionInProgress = false;
|
|
29
|
+
const DEBOUNCE_LOCKFILE = path.join(
|
|
30
|
+
process.env.HOME || process.env.USERPROFILE || '/tmp',
|
|
31
|
+
'.claude', '.vibe-hud', '.context-save-lock'
|
|
32
|
+
);
|
|
33
|
+
const DEBOUNCE_INTERVAL_MS = 30000; // 30 seconds between saves at same urgency
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if a recent save at the same urgency already happened (cross-process debounce)
|
|
37
|
+
*/
|
|
38
|
+
function isDebounceLocked(level) {
|
|
39
|
+
try {
|
|
40
|
+
const lockFile = `${DEBOUNCE_LOCKFILE}-${level}`;
|
|
41
|
+
if (fs.existsSync(lockFile)) {
|
|
42
|
+
const stat = fs.statSync(lockFile);
|
|
43
|
+
const ageMs = Date.now() - stat.mtimeMs;
|
|
44
|
+
if (ageMs < DEBOUNCE_INTERVAL_MS) return true;
|
|
45
|
+
}
|
|
46
|
+
} catch { /* ignore */ }
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Set debounce lock for a given urgency level
|
|
52
|
+
*/
|
|
53
|
+
function setDebounceLock(level) {
|
|
54
|
+
try {
|
|
55
|
+
const lockFile = `${DEBOUNCE_LOCKFILE}-${level}`;
|
|
56
|
+
const lockDir = path.dirname(lockFile);
|
|
57
|
+
if (!fs.existsSync(lockDir)) {
|
|
58
|
+
fs.mkdirSync(lockDir, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
fs.writeFileSync(lockFile, String(Date.now()));
|
|
61
|
+
} catch { /* ignore */ }
|
|
62
|
+
}
|
|
63
|
+
|
|
27
64
|
/**
|
|
28
65
|
* 프로젝트 환경에서 구조적 메타데이터를 추출.
|
|
29
66
|
* 실패 시 빈 객체 반환 (non-blocking).
|
|
@@ -102,6 +139,11 @@ function buildStructuredSummary(baseMessage, metadata) {
|
|
|
102
139
|
}
|
|
103
140
|
|
|
104
141
|
async function main() {
|
|
142
|
+
// Cross-process debounce: skip if recently saved at same urgency
|
|
143
|
+
if (isDebounceLocked(urgency)) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
105
147
|
try {
|
|
106
148
|
const metadata = extractStructuredMetadata();
|
|
107
149
|
const baseMessage = summaryMap[urgency] || summaryMap.medium;
|
|
@@ -117,6 +159,9 @@ async function main() {
|
|
|
117
159
|
const percent = urgency === 'critical' ? '95' : urgency === 'high' ? '90' : '80';
|
|
118
160
|
console.log(`[CONTEXT ${percent}%]`, result.content[0].text);
|
|
119
161
|
|
|
162
|
+
// Mark this urgency level as recently saved
|
|
163
|
+
setDebounceLock(urgency);
|
|
164
|
+
|
|
120
165
|
// Sync TokenBudgetTracker with current context usage
|
|
121
166
|
try {
|
|
122
167
|
const { TokenBudgetTracker } = await import(`${LIB_URL}TokenBudgetTracker.js`);
|
|
@@ -132,14 +177,20 @@ async function main() {
|
|
|
132
177
|
// 무시
|
|
133
178
|
}
|
|
134
179
|
|
|
180
|
+
// Guard against recursive reflection (setImmediate can re-enter)
|
|
181
|
+
if (reflectionInProgress) return;
|
|
182
|
+
|
|
135
183
|
// Minor Reflection (80% = medium): 컨텍스트 압력 시 자동 성찰
|
|
136
184
|
if (urgency === 'medium' && lastReflectionLevel !== 'medium') {
|
|
137
185
|
lastReflectionLevel = 'medium';
|
|
186
|
+
reflectionInProgress = true;
|
|
138
187
|
// Fire-and-forget: 기존 context-save에 영향 없음
|
|
139
188
|
setImmediate(() => {
|
|
140
|
-
triggerMinorReflection()
|
|
141
|
-
|
|
142
|
-
|
|
189
|
+
triggerMinorReflection()
|
|
190
|
+
.catch(e => {
|
|
191
|
+
process.stderr.write(`[REFLECTION] minor reflection failed: ${e.message}\n`);
|
|
192
|
+
})
|
|
193
|
+
.finally(() => { reflectionInProgress = false; });
|
|
143
194
|
});
|
|
144
195
|
}
|
|
145
196
|
|
|
@@ -165,10 +216,13 @@ async function main() {
|
|
|
165
216
|
// Major Reflection: 세션 종료 시 전체 성찰
|
|
166
217
|
if (lastReflectionLevel !== 'critical') {
|
|
167
218
|
lastReflectionLevel = 'critical';
|
|
219
|
+
reflectionInProgress = true;
|
|
168
220
|
setImmediate(() => {
|
|
169
|
-
triggerMajorReflection()
|
|
170
|
-
|
|
171
|
-
|
|
221
|
+
triggerMajorReflection()
|
|
222
|
+
.catch(e => {
|
|
223
|
+
process.stderr.write(`[REFLECTION] major reflection failed: ${e.message}\n`);
|
|
224
|
+
})
|
|
225
|
+
.finally(() => { reflectionInProgress = false; });
|
|
172
226
|
});
|
|
173
227
|
}
|
|
174
228
|
}
|
|
@@ -12,6 +12,11 @@ import os from 'os';
|
|
|
12
12
|
const STATE_DIR = path.join(os.homedir(), '.claude', '.vibe-hud');
|
|
13
13
|
const STATE_FILE = path.join(STATE_DIR, 'state.json');
|
|
14
14
|
|
|
15
|
+
// Write debounce configuration
|
|
16
|
+
const WRITE_DEBOUNCE_MS = 500;
|
|
17
|
+
let pendingState = null;
|
|
18
|
+
let writeTimer = null;
|
|
19
|
+
|
|
15
20
|
// 기본 상태
|
|
16
21
|
const DEFAULT_STATE = {
|
|
17
22
|
mode: 'idle', // idle | ultrawork | spec | review
|
|
@@ -65,14 +70,31 @@ function loadState() {
|
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
/**
|
|
68
|
-
*
|
|
73
|
+
* 상태를 디스크에 즉시 기록
|
|
69
74
|
*/
|
|
70
|
-
function
|
|
75
|
+
function flushState(state) {
|
|
71
76
|
ensureStateDir();
|
|
72
77
|
state.lastUpdate = new Date().toISOString();
|
|
73
78
|
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
74
79
|
}
|
|
75
80
|
|
|
81
|
+
/**
|
|
82
|
+
* 상태 저장 (debounced - 빈번한 호출을 병합)
|
|
83
|
+
* CLI 종료 전 pending write가 있으면 flush 됨
|
|
84
|
+
*/
|
|
85
|
+
function saveState(state) {
|
|
86
|
+
state.lastUpdate = new Date().toISOString();
|
|
87
|
+
pendingState = state;
|
|
88
|
+
if (writeTimer) clearTimeout(writeTimer);
|
|
89
|
+
writeTimer = setTimeout(() => {
|
|
90
|
+
if (pendingState) {
|
|
91
|
+
flushState(pendingState);
|
|
92
|
+
pendingState = null;
|
|
93
|
+
writeTimer = null;
|
|
94
|
+
}
|
|
95
|
+
}, WRITE_DEBOUNCE_MS);
|
|
96
|
+
}
|
|
97
|
+
|
|
76
98
|
/**
|
|
77
99
|
* 상태 업데이트
|
|
78
100
|
*/
|
|
@@ -286,6 +308,14 @@ Examples:
|
|
|
286
308
|
}
|
|
287
309
|
}
|
|
288
310
|
|
|
311
|
+
// Flush pending state on process exit to avoid data loss
|
|
312
|
+
process.on('exit', () => {
|
|
313
|
+
if (pendingState) {
|
|
314
|
+
flushState(pendingState);
|
|
315
|
+
pendingState = null;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
289
319
|
// 메인 실행
|
|
290
320
|
const args = process.argv.slice(2);
|
|
291
321
|
handleCommand(args);
|
|
@@ -39,10 +39,65 @@ const DEFAULT_SYSTEM_PROMPT = 'You are a helpful assistant.';
|
|
|
39
39
|
const provider = process.argv[2] || 'gemini';
|
|
40
40
|
const mode = process.argv[3] || 'orchestrate';
|
|
41
41
|
|
|
42
|
-
//
|
|
42
|
+
// WHY 3 retries: Enough to ride out brief 503/overload blips (typically 1-2
|
|
43
|
+
// consecutive), but not so many that a genuinely down provider delays the
|
|
44
|
+
// fallback chain for minutes.
|
|
43
45
|
const MAX_RETRIES = 3;
|
|
46
|
+
// WHY 2000ms initial delay: LLM rate-limit windows are typically 1-5s;
|
|
47
|
+
// starting at 2s with exponential backoff (2s, 4s, 8s) covers most reset intervals.
|
|
44
48
|
const INITIAL_DELAY_MS = 2000;
|
|
45
49
|
|
|
50
|
+
// ============================================
|
|
51
|
+
// Response Cache (TTL-based, in-memory)
|
|
52
|
+
// ============================================
|
|
53
|
+
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
54
|
+
const CACHE_MAX_ENTRIES = 50;
|
|
55
|
+
const responseCache = new Map();
|
|
56
|
+
|
|
57
|
+
function getCacheKey(providerName, prompt, sysPrompt, jsonMode) {
|
|
58
|
+
const hash = crypto.createHash('sha256')
|
|
59
|
+
.update(`${providerName}|${sysPrompt}|${prompt}|${jsonMode}`)
|
|
60
|
+
.digest('hex')
|
|
61
|
+
.slice(0, 16);
|
|
62
|
+
return hash;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getCachedResponse(key) {
|
|
66
|
+
const entry = responseCache.get(key);
|
|
67
|
+
if (!entry) return null;
|
|
68
|
+
if (Date.now() - entry.timestamp > CACHE_TTL_MS) {
|
|
69
|
+
responseCache.delete(key);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return entry.result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function setCachedResponse(key, result) {
|
|
76
|
+
// Evict oldest entries when cache is full
|
|
77
|
+
if (responseCache.size >= CACHE_MAX_ENTRIES) {
|
|
78
|
+
const oldestKey = responseCache.keys().next().value;
|
|
79
|
+
responseCache.delete(oldestKey);
|
|
80
|
+
}
|
|
81
|
+
responseCache.set(key, { result, timestamp: Date.now() });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ============================================
|
|
85
|
+
// Simple Prompt Detection (early exit)
|
|
86
|
+
// WHY skip orchestration for simple prompts: Sending greetings/acks to an
|
|
87
|
+
// external LLM wastes latency and tokens — Claude handles these natively.
|
|
88
|
+
// ============================================
|
|
89
|
+
const SIMPLE_PROMPT_MAX_LEN = 20;
|
|
90
|
+
const SIMPLE_PROMPT_PATTERNS = [
|
|
91
|
+
/^(hi|hello|hey|thanks|thank you|ok|yes|no|y|n)\.?$/i,
|
|
92
|
+
/^(help|version|status)$/i,
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
function isSimplePrompt(prompt) {
|
|
96
|
+
const trimmed = prompt.trim();
|
|
97
|
+
if (trimmed.length > SIMPLE_PROMPT_MAX_LEN) return false;
|
|
98
|
+
return SIMPLE_PROMPT_PATTERNS.some(p => p.test(trimmed));
|
|
99
|
+
}
|
|
100
|
+
|
|
46
101
|
// Errors that should skip retry and go to fallback immediately
|
|
47
102
|
const SKIP_RETRY_PATTERNS = [
|
|
48
103
|
/rate.?limit/i,
|
|
@@ -123,7 +178,8 @@ function parseAnalyzeImageArgs(args) {
|
|
|
123
178
|
// CLI Provider Functions
|
|
124
179
|
// ============================================
|
|
125
180
|
|
|
126
|
-
const CLI_TIMEOUT_MS =
|
|
181
|
+
const CLI_TIMEOUT_MS = 60000;
|
|
182
|
+
const CLI_FALLBACK_TIMEOUT_MS = 30000;
|
|
127
183
|
const IS_WINDOWS = os.platform() === 'win32';
|
|
128
184
|
|
|
129
185
|
function spawnCli(cmd, args, options) {
|
|
@@ -145,7 +201,7 @@ function buildCliPrompt(prompt, sysPrompt, jsonMode) {
|
|
|
145
201
|
}
|
|
146
202
|
|
|
147
203
|
|
|
148
|
-
function callCodexCli(prompt, sysPrompt, jsonMode, model) {
|
|
204
|
+
function callCodexCli(prompt, sysPrompt, jsonMode, model, timeoutMs) {
|
|
149
205
|
const fullPrompt = buildCliPrompt(prompt, sysPrompt, jsonMode);
|
|
150
206
|
const outputFile = path.join(os.tmpdir(), `vibe-codex-${crypto.randomUUID()}.txt`);
|
|
151
207
|
// stdin pipe로 프롬프트 전달 (shell escaping 이슈 회피)
|
|
@@ -155,11 +211,12 @@ function callCodexCli(prompt, sysPrompt, jsonMode, model) {
|
|
|
155
211
|
const vibeConfig = readVibeConfig();
|
|
156
212
|
const apiKey = vibeConfig.credentials?.gpt?.apiKey || process.env.OPENAI_API_KEY;
|
|
157
213
|
const env = apiKey ? { ...process.env, OPENAI_API_KEY: apiKey } : process.env;
|
|
214
|
+
const effectiveTimeout = timeoutMs || CLI_TIMEOUT_MS;
|
|
158
215
|
|
|
159
216
|
return new Promise((resolve, reject) => {
|
|
160
217
|
const proc = spawnCli('codex', args, {
|
|
161
218
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
162
|
-
timeout:
|
|
219
|
+
timeout: effectiveTimeout,
|
|
163
220
|
env,
|
|
164
221
|
});
|
|
165
222
|
proc.stdin.write(fullPrompt);
|
|
@@ -186,16 +243,17 @@ function callCodexCli(prompt, sysPrompt, jsonMode, model) {
|
|
|
186
243
|
});
|
|
187
244
|
}
|
|
188
245
|
|
|
189
|
-
function callGeminiCli(prompt, sysPrompt, jsonMode, model) {
|
|
246
|
+
function callGeminiCli(prompt, sysPrompt, jsonMode, model, timeoutMs) {
|
|
190
247
|
const fullPrompt = buildCliPrompt(prompt, sysPrompt, jsonMode);
|
|
191
248
|
// -p 로 headless 모드, stdin으로 프롬프트 전달 (stdin is appended to -p value)
|
|
192
249
|
const args = ['-p', '.', '-o', 'text'];
|
|
193
250
|
if (model) args.push('-m', model);
|
|
251
|
+
const effectiveTimeout = timeoutMs || CLI_TIMEOUT_MS;
|
|
194
252
|
|
|
195
253
|
return new Promise((resolve, reject) => {
|
|
196
254
|
const proc = spawnCli('gemini', args, {
|
|
197
255
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
198
|
-
timeout:
|
|
256
|
+
timeout: effectiveTimeout,
|
|
199
257
|
});
|
|
200
258
|
proc.stdin.write(fullPrompt);
|
|
201
259
|
proc.stdin.end();
|
|
@@ -219,7 +277,7 @@ function callGeminiCli(prompt, sysPrompt, jsonMode, model) {
|
|
|
219
277
|
});
|
|
220
278
|
}
|
|
221
279
|
|
|
222
|
-
async function callProvider(providerName, prompt, sysPrompt, jsonMode) {
|
|
280
|
+
async function callProvider(providerName, prompt, sysPrompt, jsonMode, timeoutMs) {
|
|
223
281
|
const vibeConfig = readVibeConfig();
|
|
224
282
|
|
|
225
283
|
if (providerName === 'gpt' || providerName === 'gpt-codex' || providerName === 'gpt-spark') {
|
|
@@ -231,23 +289,23 @@ async function callProvider(providerName, prompt, sysPrompt, jsonMode) {
|
|
|
231
289
|
} else {
|
|
232
290
|
model = vibeConfig.models?.gpt || process.env.GPT_MODEL || 'gpt-5.4';
|
|
233
291
|
}
|
|
234
|
-
return await callCodexCli(prompt, sysPrompt, jsonMode, model);
|
|
292
|
+
return await callCodexCli(prompt, sysPrompt, jsonMode, model, timeoutMs);
|
|
235
293
|
}
|
|
236
294
|
|
|
237
295
|
if (providerName === 'gemini') {
|
|
238
296
|
const model = vibeConfig.models?.gemini || process.env.GEMINI_MODEL || 'gemini-3.1-pro-preview';
|
|
239
|
-
return await callGeminiCli(prompt, sysPrompt, jsonMode, model);
|
|
297
|
+
return await callGeminiCli(prompt, sysPrompt, jsonMode, model, timeoutMs);
|
|
240
298
|
}
|
|
241
299
|
|
|
242
300
|
throw new Error(`Unknown provider: ${providerName}`);
|
|
243
301
|
}
|
|
244
302
|
|
|
245
|
-
async function callWithRetry(providerName, prompt, sysPrompt, jsonMode) {
|
|
303
|
+
async function callWithRetry(providerName, prompt, sysPrompt, jsonMode, timeoutMs) {
|
|
246
304
|
let lastError;
|
|
247
305
|
|
|
248
306
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
249
307
|
try {
|
|
250
|
-
return { success: true, result: await callProvider(providerName, prompt, sysPrompt, jsonMode) };
|
|
308
|
+
return { success: true, result: await callProvider(providerName, prompt, sysPrompt, jsonMode, timeoutMs) };
|
|
251
309
|
} catch (e) {
|
|
252
310
|
lastError = e;
|
|
253
311
|
const errorMsg = e.message || String(e);
|
|
@@ -447,26 +505,46 @@ async function main() {
|
|
|
447
505
|
const cleanPrompt = prompt.replace(prefixPatterns[provider] || /^/, '').trim();
|
|
448
506
|
const jsonMode = mode === 'orchestrate-json';
|
|
449
507
|
|
|
508
|
+
// Early exit: simple prompts don't need LLM orchestration
|
|
509
|
+
if (isSimplePrompt(cleanPrompt)) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Check cache for identical prompts
|
|
514
|
+
const cacheKey = getCacheKey(provider, cleanPrompt, systemPrompt, jsonMode);
|
|
515
|
+
const cached = getCachedResponse(cacheKey);
|
|
516
|
+
if (cached) {
|
|
517
|
+
console.log(cached);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
450
521
|
// Provider chain: primary → cross fallback
|
|
451
|
-
//
|
|
522
|
+
// WHY GPT → Gemini (not reverse): GPT is the primary code/reasoning model;
|
|
523
|
+
// Gemini serves as cross-vendor fallback so a single vendor outage never
|
|
524
|
+
// blocks the user. When Gemini is primary (e.g. web-search), GPT is fallback.
|
|
452
525
|
const providerLabels = { gpt: 'GPT', 'gpt-codex': 'GPT Codex', gemini: 'Gemini' };
|
|
453
526
|
const isGpt = provider === 'gpt' || provider === 'gpt-codex';
|
|
454
527
|
const providerChain = isGpt
|
|
455
528
|
? [provider, 'gemini']
|
|
456
529
|
: ['gemini', 'gpt'];
|
|
457
530
|
|
|
458
|
-
for (
|
|
531
|
+
for (let i = 0; i < providerChain.length; i++) {
|
|
532
|
+
const currentProvider = providerChain[i];
|
|
459
533
|
const label = providerLabels[currentProvider] || currentProvider.toUpperCase();
|
|
460
|
-
|
|
534
|
+
// Use shorter timeout for fallback providers
|
|
535
|
+
const timeoutMs = i === 0 ? CLI_TIMEOUT_MS : CLI_FALLBACK_TIMEOUT_MS;
|
|
536
|
+
const result = await callWithRetry(currentProvider, cleanPrompt, systemPrompt, jsonMode, timeoutMs);
|
|
461
537
|
|
|
462
538
|
if (result.success) {
|
|
463
|
-
|
|
539
|
+
const output = `${label} response: ${result.result}`;
|
|
540
|
+
setCachedResponse(cacheKey, output);
|
|
541
|
+
console.log(output);
|
|
464
542
|
return;
|
|
465
543
|
}
|
|
466
544
|
|
|
467
545
|
// Log failure and try fallback
|
|
468
|
-
if (
|
|
469
|
-
const nextProvider = providerChain[
|
|
546
|
+
if (i < providerChain.length - 1) {
|
|
547
|
+
const nextProvider = providerChain[i + 1];
|
|
470
548
|
const nextLabel = providerLabels[nextProvider] || nextProvider.toUpperCase();
|
|
471
549
|
console.error(`[${currentProvider.toUpperCase()}] Failed: ${result.error}. Falling back to ${nextLabel}...`);
|
|
472
550
|
} else {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PreToolUse Hook - PR 생성 전 테스트 게이트
|
|
3
|
+
*
|
|
4
|
+
* mcp__github__create_pull_request 호출 시 테스트가 통과해야만 PR 생성 허용.
|
|
5
|
+
* exit 2 = 차단, exit 0 = 통과
|
|
6
|
+
*/
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import { PROJECT_DIR } from './utils.js';
|
|
9
|
+
import { existsSync, readFileSync } from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
function detectTestCommand() {
|
|
13
|
+
const pkgPath = path.join(PROJECT_DIR, 'package.json');
|
|
14
|
+
if (existsSync(pkgPath)) {
|
|
15
|
+
try {
|
|
16
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
17
|
+
if (pkg.scripts?.test && pkg.scripts.test !== 'echo "Error: no test specified" && exit 1') {
|
|
18
|
+
return 'npm test';
|
|
19
|
+
}
|
|
20
|
+
} catch { /* ignore */ }
|
|
21
|
+
}
|
|
22
|
+
// Python
|
|
23
|
+
if (existsSync(path.join(PROJECT_DIR, 'pytest.ini')) || existsSync(path.join(PROJECT_DIR, 'pyproject.toml'))) {
|
|
24
|
+
return 'python -m pytest --tb=short -q';
|
|
25
|
+
}
|
|
26
|
+
// Go
|
|
27
|
+
if (existsSync(path.join(PROJECT_DIR, 'go.mod'))) {
|
|
28
|
+
return 'go test ./...';
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const testCmd = detectTestCommand();
|
|
35
|
+
if (!testCmd) {
|
|
36
|
+
// No test command detected — allow PR
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(`[PR-GATE] Running tests before PR creation: ${testCmd}`);
|
|
41
|
+
execSync(testCmd, {
|
|
42
|
+
cwd: PROJECT_DIR,
|
|
43
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
44
|
+
timeout: 120000,
|
|
45
|
+
});
|
|
46
|
+
console.log('[PR-GATE] Tests passed — PR creation allowed');
|
|
47
|
+
process.exit(0);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
const output = err.stdout ? err.stdout.toString().split('\n').slice(-5).join('\n') : '';
|
|
50
|
+
console.log(`[PR-GATE] Tests failed — PR creation blocked\n${output}`);
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# What to Keep vs Remove in AGENTS.md
|
|
2
|
+
|
|
3
|
+
## The One-Line Test
|
|
4
|
+
|
|
5
|
+
> "Can the agent discover this by reading the code?" → Yes = delete.
|
|
6
|
+
|
|
7
|
+
## Keep (Non-discoverable Gotchas)
|
|
8
|
+
|
|
9
|
+
| Type | Example | Why Keep |
|
|
10
|
+
|------|---------|----------|
|
|
11
|
+
| Runtime trap | "Bun runtime, not Node" | Not visible in package.json |
|
|
12
|
+
| Forbidden pattern | "Never use `require()` — ESM only" | Agent will default to CJS without this |
|
|
13
|
+
| SSOT location | "Edit only `constants.ts` for stack mapping" | Agent will create duplicates |
|
|
14
|
+
| Ordering invariant | "Build before test — always" | Violating silently breaks things |
|
|
15
|
+
| Non-standard convention | "Exports need `.js` extension" | Counterintuitive in TS projects |
|
|
16
|
+
| Tool choice | "Zod only — no joi or yup" | Agent picks any validation lib |
|
|
17
|
+
| Boundary | "Never edit `dist/` directly" | Agent may "fix" generated files |
|
|
18
|
+
| Response directive | "Respond in Korean" | Claude-specific, not in code |
|
|
19
|
+
|
|
20
|
+
## Remove (Discoverable)
|
|
21
|
+
|
|
22
|
+
| Type | Why Remove | Where to Find Instead |
|
|
23
|
+
|------|------------|----------------------|
|
|
24
|
+
| Directory structure | `ls` or Glob reveals it | The repo itself |
|
|
25
|
+
| Tech stack list | Listed in `package.json` | `package.json` |
|
|
26
|
+
| Build/test commands | Listed in `scripts` | `package.json` |
|
|
27
|
+
| Phase progress tables | Historical record, not actionable | Git history |
|
|
28
|
+
| API endpoint list | Readable from router code | Source files |
|
|
29
|
+
| Architecture diagrams | Visual aid, no mistake prevention | Readme or separate doc |
|
|
30
|
+
| Feature descriptions | Code speaks for itself | Source files |
|
|
31
|
+
| General best practices | LLM already knows them | Unnecessary |
|
|
32
|
+
|
|
33
|
+
## Anchoring Warning
|
|
34
|
+
|
|
35
|
+
Mentioning any technology name biases the agent toward it:
|
|
36
|
+
- "We use React" → unnecessary (visible in package.json) and creates anchoring
|
|
37
|
+
- "Never use jQuery, even for legacy modules" → useful (prevents a specific mistake)
|
|
38
|
+
|
|
39
|
+
Rule: Only name a technology when saying **don't use it** or when it's a **hidden runtime detail**.
|
|
40
|
+
|
|
41
|
+
## Size Target
|
|
42
|
+
|
|
43
|
+
| Outcome | Lines |
|
|
44
|
+
|---------|-------|
|
|
45
|
+
| Ideal | Under 50 |
|
|
46
|
+
| Acceptable | 50-80 |
|
|
47
|
+
| Warning | 80+ |
|
|
48
|
+
|
|
49
|
+
If over 80 lines, almost certainly contains discoverable content. Audit again.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} — {{ONE_LINER}}
|
|
2
|
+
|
|
3
|
+
{{OPTIONAL: What the project does in 1-2 sentences. Only include if purpose is unclear from code.}}
|
|
4
|
+
|
|
5
|
+
## Gotchas
|
|
6
|
+
|
|
7
|
+
- **{{TRAP_TITLE}}.** {{Specific do/don't — e.g., "Use Bun, not Node. `bun run dev`, not `node`."}}
|
|
8
|
+
- **{{TRAP_TITLE}}.** {{Specific do/don't}}
|
|
9
|
+
- **{{TRAP_TITLE}}.** {{Specific do/don't}}
|
|
10
|
+
|
|
11
|
+
## Naming
|
|
12
|
+
|
|
13
|
+
{{Only if non-standard naming patterns exist. Delete this section if standard conventions apply.}}
|
|
14
|
+
|
|
15
|
+
- {{Pattern}}: {{Example}}
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<!--
|
|
20
|
+
INSTRUCTIONS FOR FILLING THIS TEMPLATE
|
|
21
|
+
|
|
22
|
+
1. Replace {{PROJECT_NAME}} and {{ONE_LINER}} with your project.
|
|
23
|
+
2. For each Gotcha, ask: "Would an AI agent make this mistake without being told?"
|
|
24
|
+
- YES → keep it as a gotcha
|
|
25
|
+
- NO → delete it (discoverable from code)
|
|
26
|
+
3. Delete the Naming section if you use standard conventions (camelCase, PascalCase, etc.).
|
|
27
|
+
4. Target: under 50 lines total.
|
|
28
|
+
5. Delete this comment block before committing.
|
|
29
|
+
|
|
30
|
+
WHAT NOT TO ADD:
|
|
31
|
+
- Directory structure (use ls/Glob)
|
|
32
|
+
- Tech stack list (see package.json)
|
|
33
|
+
- Build/test commands (see package.json scripts)
|
|
34
|
+
- Phase progress or history
|
|
35
|
+
- General best practices the LLM already knows
|
|
36
|
+
-->
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: arch-detector
|
|
3
|
+
role: Detects the project's architecture pattern by analyzing directory structure and import graph
|
|
4
|
+
tools: [Glob, Grep, Read]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Arch Detector
|
|
8
|
+
|
|
9
|
+
## Role
|
|
10
|
+
Analyzes directory layout, existing documentation, and import relationships to classify the project into a known architecture pattern. Produces a structured architecture map that downstream agents use to generate rules and check violations.
|
|
11
|
+
|
|
12
|
+
## Responsibilities
|
|
13
|
+
- Scan top-level directory structure for known layer naming conventions
|
|
14
|
+
- Read CLAUDE.md, README, and any ADR files for explicit architecture documentation
|
|
15
|
+
- Sample import statements across files to infer actual dependency direction
|
|
16
|
+
- Classify project into one of: MVC, Clean Architecture, Hexagonal, Feature-based, Component hierarchy, or Unknown
|
|
17
|
+
- Produce a layer map with canonical names and glob patterns for each layer
|
|
18
|
+
|
|
19
|
+
## Input
|
|
20
|
+
- Project root path
|
|
21
|
+
- Optional: explicit architecture hint from user (e.g., "this is Clean Architecture")
|
|
22
|
+
|
|
23
|
+
## Output
|
|
24
|
+
Architecture map JSON:
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"pattern": "Clean Architecture",
|
|
28
|
+
"confidence": "high",
|
|
29
|
+
"layers": [
|
|
30
|
+
{ "name": "domain", "glob": "src/domain/**", "allowedDeps": [] },
|
|
31
|
+
{ "name": "application", "glob": "src/application/**", "allowedDeps": ["domain"] },
|
|
32
|
+
{ "name": "infrastructure", "glob": "src/infra/**", "allowedDeps": ["domain", "application"] },
|
|
33
|
+
{ "name": "ui", "glob": "src/components/**", "allowedDeps": ["application"] }
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Communication
|
|
39
|
+
- Reports architecture map to: `arch-rule-generator`
|
|
40
|
+
- Receives instructions from: arch-guard orchestrator (SKILL.md)
|
|
41
|
+
|
|
42
|
+
## Domain Knowledge
|
|
43
|
+
Architecture pattern signals:
|
|
44
|
+
- **MVC**: directories named `controllers/`, `models/`, `views/` or `services/`
|
|
45
|
+
- **Clean Architecture**: `domain/`, `application/` or `use-cases/`, `infrastructure/` or `infra/`
|
|
46
|
+
- **Hexagonal**: `adapters/`, `ports/`, `core/` or `domain/`
|
|
47
|
+
- **Feature-based**: top-level feature folders each containing `components/`, `hooks/`, `api/`
|
|
48
|
+
- **Component hierarchy**: `pages/`, `features/`, `shared/`, `ui/` in frontend projects
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: arch-reporter
|
|
3
|
+
role: Formats the violation scan results into an actionable report with fix suggestions
|
|
4
|
+
tools: [Read]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Arch Reporter
|
|
8
|
+
|
|
9
|
+
## Role
|
|
10
|
+
Transforms the raw violation list into a human-readable report grouped by rule and severity. For each violation, it provides a specific fix suggestion explaining which layer the import should move to or be replaced by. Produces both a console summary and a machine-readable output for CI.
|
|
11
|
+
|
|
12
|
+
## Responsibilities
|
|
13
|
+
- Group violations by rule name and severity
|
|
14
|
+
- Generate a specific fix suggestion for each violation type
|
|
15
|
+
- Calculate a health score (clean files / total scanned)
|
|
16
|
+
- Produce a markdown summary for the user
|
|
17
|
+
- Produce a JSON summary for CI badge / artifact storage
|
|
18
|
+
|
|
19
|
+
## Input
|
|
20
|
+
Violation list JSON from `arch-violation-checker` plus the rule set from `arch-rule-generator`.
|
|
21
|
+
|
|
22
|
+
## Output
|
|
23
|
+
Markdown report:
|
|
24
|
+
```markdown
|
|
25
|
+
## Architecture Boundary Report
|
|
26
|
+
|
|
27
|
+
Health: 139/142 files clean (97.9%)
|
|
28
|
+
|
|
29
|
+
### Violations (3 errors)
|
|
30
|
+
|
|
31
|
+
#### Rule: domain-no-infra — Domain must not import Infrastructure
|
|
32
|
+
- src/domain/user.ts:3 imports `../infra/db/userRepository`
|
|
33
|
+
Fix: Extract a port interface in `src/domain/ports/userRepository.ts` and inject via DI
|
|
34
|
+
|
|
35
|
+
Total: 3 violations across 1 rule. Run `npx vitest run tests/arch-guard.test.ts` to enforce in CI.
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Communication
|
|
39
|
+
- Reports formatted output to: orchestrator / user
|
|
40
|
+
- Receives instructions from: arch-guard orchestrator (SKILL.md)
|
|
41
|
+
|
|
42
|
+
## Domain Knowledge
|
|
43
|
+
Fix suggestion patterns:
|
|
44
|
+
- **Domain importing Infra**: introduce a port/interface in domain; implement in infra; inject via constructor
|
|
45
|
+
- **Feature importing Feature internals**: move shared code to `shared/` layer
|
|
46
|
+
- **Service importing Controller**: the logic belongs in a service method called by the controller
|
|
47
|
+
- **UI importing Domain directly**: route through an application-layer use case or store
|
|
48
|
+
- Suggest the minimal move — avoid recommending full refactors for single violations
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: arch-rule-generator
|
|
3
|
+
role: Generates concrete import boundary rules from the detected architecture map
|
|
4
|
+
tools: [Read]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Arch Rule Generator
|
|
8
|
+
|
|
9
|
+
## Role
|
|
10
|
+
Translates a detected architecture map into a precise, machine-checkable set of import boundary rules. Merges default rules for the detected pattern with any custom rules defined in `.claude/vibe/arch-rules.json`. Outputs a normalized rule set ready for the violation checker.
|
|
11
|
+
|
|
12
|
+
## Responsibilities
|
|
13
|
+
- Select default rule templates for the detected architecture pattern
|
|
14
|
+
- Merge with custom rules from `.claude/vibe/arch-rules.json` if present
|
|
15
|
+
- Resolve glob patterns to concrete layer names
|
|
16
|
+
- Deduplicate and normalize rule list
|
|
17
|
+
- Flag rules with low confidence (detected layer with no matching files)
|
|
18
|
+
|
|
19
|
+
## Input
|
|
20
|
+
Architecture map JSON from `arch-detector`, plus optional `.claude/vibe/arch-rules.json` for user-defined overrides.
|
|
21
|
+
|
|
22
|
+
## Output
|
|
23
|
+
Normalized rule set JSON:
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"rules": [
|
|
27
|
+
{
|
|
28
|
+
"name": "domain-no-infra",
|
|
29
|
+
"from": "src/domain/**",
|
|
30
|
+
"cannotImport": ["src/infra/**"],
|
|
31
|
+
"reason": "Domain layer must not depend on infrastructure",
|
|
32
|
+
"severity": "error"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"warnings": ["Layer 'adapters' detected but no files found — rule may be inaccurate"]
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Communication
|
|
40
|
+
- Reports rule set to: `arch-violation-checker`
|
|
41
|
+
- Receives instructions from: arch-guard orchestrator (SKILL.md)
|
|
42
|
+
|
|
43
|
+
## Domain Knowledge
|
|
44
|
+
Default rules by pattern:
|
|
45
|
+
- **Clean Architecture**: domain has no deps; application imports domain only; infra imports domain + application; ui imports application only
|
|
46
|
+
- **MVC**: models have no deps on controllers or views; services import models only
|
|
47
|
+
- **Hexagonal**: domain/core imports nothing internal; adapters import ports only
|
|
48
|
+
- **Feature-based**: features must not import each other's internals; only `shared/` is cross-feature
|
|
49
|
+
- **SOLID Dependency Inversion**: high-level modules must not import low-level modules directly
|