@su-record/vibe 2.9.40 → 2.10.2
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 +28 -12
- package/README.en.md +2 -3
- package/README.md +33 -27
- 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/auth.d.ts +0 -1
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +1 -18
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/collaborator.d.ts +3 -3
- package/dist/cli/collaborator.js +4 -4
- package/dist/cli/collaborator.js.map +1 -1
- package/dist/cli/commands/info.d.ts.map +1 -1
- package/dist/cli/commands/info.js +0 -1
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/init.d.ts +3 -4
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +15 -20
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/remove.d.ts.map +1 -1
- package/dist/cli/commands/remove.js +2 -7
- package/dist/cli/commands/remove.js.map +1 -1
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +10 -10
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +1 -3
- package/dist/cli/index.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 +26 -24
- package/dist/cli/postinstall/main.js.map +1 -1
- package/dist/cli/setup/LegacyMigration.d.ts +3 -3
- package/dist/cli/setup/LegacyMigration.d.ts.map +1 -1
- package/dist/cli/setup/LegacyMigration.js +3 -5
- package/dist/cli/setup/LegacyMigration.js.map +1 -1
- package/dist/cli/setup/ProjectSetup.d.ts +18 -8
- package/dist/cli/setup/ProjectSetup.d.ts.map +1 -1
- package/dist/cli/setup/ProjectSetup.js +70 -19
- package/dist/cli/setup/ProjectSetup.js.map +1 -1
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +1 -1
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/utils/cli-detector.d.ts +0 -7
- package/dist/cli/utils/cli-detector.d.ts.map +1 -1
- package/dist/cli/utils/cli-detector.js +0 -95
- package/dist/cli/utils/cli-detector.js.map +1 -1
- package/dist/cli/utils.d.ts +1 -1
- package/dist/cli/utils.d.ts.map +1 -1
- package/dist/cli/utils.js +1 -2
- package/dist/cli/utils.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/memory/MemoryStorage.d.ts +1 -1
- package/dist/infra/lib/memory/MemoryStorage.d.ts.map +1 -1
- package/dist/infra/lib/memory/MemoryStorage.js +2 -3
- package/dist/infra/lib/memory/MemoryStorage.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__/pre-tool-guard.test.js +1 -1
- 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/codex-notify.js +49 -0
- package/hooks/scripts/command-log.js +1 -1
- package/hooks/scripts/lib/dispatcher.js +2 -3
- package/hooks/scripts/lib/scope-from-spec.js +2 -4
- package/hooks/scripts/llm-orchestrate.js +2 -7
- package/hooks/scripts/prompt-dispatcher.js +3 -3
- package/hooks/scripts/step-counter.js +1 -1
- package/hooks/scripts/utils.js +5 -10
- 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 +8 -8
- 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 +19 -19
- 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 +288 -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} +5 -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} +8 -6
- 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} +4 -2
- package/skills/video-production/SKILL.md +1 -0
- package/vibe/rules/principles/dual-harness-doctrine.md +50 -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,275 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* clone-to-scss.js — sections.json → SCSS partials + class plan
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node clone-to-scss.js <sections.json> --out=<styles-dir> [--feature=<name>] [--asset-root=<public-prefix>]
|
|
8
|
+
*
|
|
9
|
+
* Output:
|
|
10
|
+
* <styles-dir>/
|
|
11
|
+
* index.scss (master orchestrator)
|
|
12
|
+
* _tokens.scss (CSS variables from tokens)
|
|
13
|
+
* _base.scss (@font-face + body defaults from stylesheets.json hints)
|
|
14
|
+
* _shared.scss (placeholder for cross-section utilities)
|
|
15
|
+
* sections/_<name>.scss (per-section partial)
|
|
16
|
+
* <styles-dir>/class-plan.json (node.id → BEM class name; HTML scaffolder applies these)
|
|
17
|
+
*
|
|
18
|
+
* Rules:
|
|
19
|
+
* - CSS values copied verbatim from sections.json — no eyeballing
|
|
20
|
+
* - Token-referenced values stay as var(--xxx)
|
|
21
|
+
* - Selectors are class-based, BEM-flavored: .{feature}__{section}__{role}
|
|
22
|
+
* - One class per node; nested rules use SCSS &__ syntax
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import fs from 'fs';
|
|
26
|
+
import path from 'path';
|
|
27
|
+
|
|
28
|
+
// ─── CLI ────────────────────────────────────────────────────────────
|
|
29
|
+
function parseArgs(argv) {
|
|
30
|
+
const [, , inputPath, ...rest] = argv;
|
|
31
|
+
const opts = {};
|
|
32
|
+
for (const a of rest) {
|
|
33
|
+
if (a.startsWith('--out=')) opts.out = a.slice(6);
|
|
34
|
+
else if (a.startsWith('--feature=')) opts.feature = a.slice(10);
|
|
35
|
+
else if (a.startsWith('--asset-root=')) opts.assetRoot = a.slice(13);
|
|
36
|
+
}
|
|
37
|
+
return { inputPath, opts };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { inputPath, opts } = parseArgs(process.argv);
|
|
41
|
+
if (!inputPath || !opts.out) {
|
|
42
|
+
console.error('Usage: node clone-to-scss.js <sections.json> --out=<styles-dir> [--feature=<name>] [--asset-root=<public-prefix>]');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
47
|
+
function kebab(s) {
|
|
48
|
+
return String(s)
|
|
49
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
50
|
+
.replace(/[^a-zA-Z0-9]+/g, '-')
|
|
51
|
+
.replace(/^-+|-+$/g, '')
|
|
52
|
+
.toLowerCase() || 'x';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function shorten(id) {
|
|
56
|
+
// node id like "0.1.2.3" → "n-0-1-2-3"
|
|
57
|
+
return 'n-' + id.replace(/[^0-9]/g, '-').replace(/^-+|-+$/g, '').slice(0, 20);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Detect a semantic role for a node based on tag/class/text/attrs
|
|
61
|
+
function semanticRole(node) {
|
|
62
|
+
const cls = (node.classes || '').toLowerCase();
|
|
63
|
+
const tag = node.tag;
|
|
64
|
+
if (tag === 'h1' || /\bheadline\b|\bhero-title\b/.test(cls)) return 'title';
|
|
65
|
+
if (/^h[2-6]$/.test(tag)) return `heading-${tag.slice(1)}`;
|
|
66
|
+
if (tag === 'p' || /\bsubtitle\b|\bdescription\b/.test(cls)) return 'body';
|
|
67
|
+
if (tag === 'a' && (/\bbutton\b|\bbtn\b|\bcta\b/.test(cls) || (node.attrs && /button/.test(node.attrs.role || '')))) return 'button';
|
|
68
|
+
if (tag === 'button') return 'button';
|
|
69
|
+
if (tag === 'img' || (node.css && node.css['background-image'] && node.css['background-image'] !== 'none')) return 'media';
|
|
70
|
+
if (tag === 'ul' || tag === 'ol') return 'list';
|
|
71
|
+
if (tag === 'li') return 'item';
|
|
72
|
+
if (tag === 'nav' || /\bnav\b/.test(cls)) return 'nav';
|
|
73
|
+
if (tag === 'form') return 'form';
|
|
74
|
+
if (tag === 'input' || tag === 'textarea' || tag === 'select') return 'field';
|
|
75
|
+
if (tag === 'header') return 'header';
|
|
76
|
+
if (tag === 'footer') return 'footer';
|
|
77
|
+
if (tag === 'section' || tag === 'article') return 'content';
|
|
78
|
+
if (tag === 'div' && node.children && node.children.length > 1) return 'group';
|
|
79
|
+
return 'el';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ─── Class plan: assign one BEM class per visible node ──────────────
|
|
83
|
+
function buildClassPlan(sections, feature) {
|
|
84
|
+
const plan = {}; // id → class name
|
|
85
|
+
const usedNames = new Map(); // base name → count for disambiguation
|
|
86
|
+
const sectionUsedRoles = new Map();
|
|
87
|
+
|
|
88
|
+
const assign = (sectionName, node, parentRole) => {
|
|
89
|
+
if (!node || node.pseudo) return;
|
|
90
|
+
const sectionKey = kebab(sectionName);
|
|
91
|
+
let role = semanticRole(node);
|
|
92
|
+
if (parentRole === 'list' && role !== 'item') role = 'item';
|
|
93
|
+
// disambiguate within section by role
|
|
94
|
+
const k = `${sectionKey}::${role}`;
|
|
95
|
+
const idx = (sectionUsedRoles.get(k) || 0) + 1;
|
|
96
|
+
sectionUsedRoles.set(k, idx);
|
|
97
|
+
const suffix = idx === 1 ? role : `${role}-${idx}`;
|
|
98
|
+
const cls = `${feature}__${sectionKey}__${suffix}`;
|
|
99
|
+
plan[node.id] = cls;
|
|
100
|
+
for (const child of (node.children || [])) assign(sectionName, child, role);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
for (const sec of sections) {
|
|
104
|
+
// Root section class
|
|
105
|
+
const sectionKey = kebab(sec.name);
|
|
106
|
+
plan[sec.nodeRef] = `${feature}__${sectionKey}`;
|
|
107
|
+
for (const child of (sec.children || [])) assign(sec.name, child, 'section');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return plan;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ─── CSS emission ───────────────────────────────────────────────────
|
|
114
|
+
const SKIP_DEFAULT_VALUES = {
|
|
115
|
+
'pointer-events': 'auto',
|
|
116
|
+
'visibility': 'visible',
|
|
117
|
+
'opacity': '1',
|
|
118
|
+
'transform': 'none',
|
|
119
|
+
'background-repeat': 'repeat',
|
|
120
|
+
'background-attachment': 'scroll',
|
|
121
|
+
'background-blend-mode': 'normal',
|
|
122
|
+
'mix-blend-mode': 'normal',
|
|
123
|
+
'filter': 'none',
|
|
124
|
+
'backdrop-filter': 'none',
|
|
125
|
+
'overflow': 'visible',
|
|
126
|
+
'overflow-x': 'visible',
|
|
127
|
+
'overflow-y': 'visible',
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
function emitDeclarations(css, indent) {
|
|
131
|
+
const out = [];
|
|
132
|
+
for (const [prop, val] of Object.entries(css)) {
|
|
133
|
+
if (!val || prop.startsWith('--')) continue;
|
|
134
|
+
if (SKIP_DEFAULT_VALUES[prop] === val) continue;
|
|
135
|
+
out.push(`${indent}${prop}: ${val};`);
|
|
136
|
+
}
|
|
137
|
+
return out.join('\n');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function emitNodeRule(node, plan, indent) {
|
|
141
|
+
const cls = plan[node.id];
|
|
142
|
+
if (!cls) return '';
|
|
143
|
+
const lines = [];
|
|
144
|
+
lines.push(`${indent}.${cls} {`);
|
|
145
|
+
const decl = emitDeclarations(node.css || {}, indent + ' ');
|
|
146
|
+
if (decl) lines.push(decl);
|
|
147
|
+
// pseudo-elements
|
|
148
|
+
for (const child of (node.children || [])) {
|
|
149
|
+
if (child.pseudo) {
|
|
150
|
+
const kind = child.tag.replace('::', '');
|
|
151
|
+
lines.push('');
|
|
152
|
+
lines.push(`${indent} &::${kind} {`);
|
|
153
|
+
lines.push(emitDeclarations(child.css || {}, indent + ' '));
|
|
154
|
+
lines.push(`${indent} }`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
lines.push(`${indent}}`);
|
|
158
|
+
|
|
159
|
+
// Non-pseudo children — emit at same level (BEM flat)
|
|
160
|
+
const childRules = (node.children || [])
|
|
161
|
+
.filter((c) => !c.pseudo)
|
|
162
|
+
.map((c) => emitNodeRule(c, plan, indent))
|
|
163
|
+
.filter(Boolean);
|
|
164
|
+
|
|
165
|
+
return [lines.join('\n'), ...childRules].join('\n\n');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─── File emission ──────────────────────────────────────────────────
|
|
169
|
+
function emitTokens(tokens) {
|
|
170
|
+
const lines = [':root {'];
|
|
171
|
+
for (const c of tokens.colors) lines.push(` --${c.name}: ${c.value};`);
|
|
172
|
+
for (const t of tokens.typography) {
|
|
173
|
+
lines.push(` --${t.name}-family: ${t.family};`);
|
|
174
|
+
lines.push(` --${t.name}-size: ${t.size};`);
|
|
175
|
+
lines.push(` --${t.name}-weight: ${t.weight};`);
|
|
176
|
+
if (t.lineHeight && t.lineHeight !== 'normal') lines.push(` --${t.name}-line-height: ${t.lineHeight};`);
|
|
177
|
+
if (t.letterSpacing && t.letterSpacing !== 'normal') lines.push(` --${t.name}-letter-spacing: ${t.letterSpacing};`);
|
|
178
|
+
}
|
|
179
|
+
for (const s of tokens.spacing) lines.push(` --${s.name}: ${s.value};`);
|
|
180
|
+
for (const r of tokens.radius) lines.push(` --${r.name}: ${r.value};`);
|
|
181
|
+
for (const s of tokens.shadow) lines.push(` --${s.name}: ${s.value};`);
|
|
182
|
+
lines.push('}');
|
|
183
|
+
return lines.join('\n') + '\n';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function emitBase(feature, stylesheets) {
|
|
187
|
+
const lines = [];
|
|
188
|
+
lines.push('// Auto-generated by clone-to-scss.js — do not edit by hand.');
|
|
189
|
+
lines.push('');
|
|
190
|
+
if (stylesheets && stylesheets.fontFaces) {
|
|
191
|
+
for (const ff of stylesheets.fontFaces) {
|
|
192
|
+
lines.push('@font-face {');
|
|
193
|
+
lines.push(` font-family: '${ff.family}';`);
|
|
194
|
+
if (ff.weight) lines.push(` font-weight: ${ff.weight};`);
|
|
195
|
+
if (ff.style) lines.push(` font-style: ${ff.style};`);
|
|
196
|
+
if (ff.display) lines.push(` font-display: ${ff.display};`);
|
|
197
|
+
const srcs = ff.sources.map((s) => s.format
|
|
198
|
+
? `url('${s.url}') format('${s.format}')`
|
|
199
|
+
: `url('${s.url}')`).join(',\n ');
|
|
200
|
+
lines.push(` src: ${srcs};`);
|
|
201
|
+
lines.push('}');
|
|
202
|
+
lines.push('');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return lines.join('\n');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function emitSection(section, plan) {
|
|
209
|
+
const lines = [];
|
|
210
|
+
lines.push(`// Section: ${section.name}`);
|
|
211
|
+
lines.push('');
|
|
212
|
+
// root + descendants
|
|
213
|
+
lines.push(emitNodeRule({ ...section, id: section.nodeRef, children: section.children }, plan, ''));
|
|
214
|
+
return lines.join('\n') + '\n';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function emitIndex(feature, sections) {
|
|
218
|
+
const lines = [];
|
|
219
|
+
lines.push(`// ${feature} — auto-generated by clone-to-scss.js`);
|
|
220
|
+
lines.push('');
|
|
221
|
+
lines.push(`@use './tokens';`);
|
|
222
|
+
lines.push(`@use './base';`);
|
|
223
|
+
lines.push(`@use './shared';`);
|
|
224
|
+
for (const sec of sections) {
|
|
225
|
+
lines.push(`@use './sections/${kebab(sec.name)}';`);
|
|
226
|
+
}
|
|
227
|
+
return lines.join('\n') + '\n';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ─── Main ───────────────────────────────────────────────────────────
|
|
231
|
+
function main() {
|
|
232
|
+
const data = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
|
|
233
|
+
const feature = opts.feature || data.meta?.feature || 'clone';
|
|
234
|
+
const featureClass = kebab(feature);
|
|
235
|
+
|
|
236
|
+
// Read sibling stylesheets.json if present (from clone-extract.js output)
|
|
237
|
+
const sectionsDir = path.dirname(inputPath);
|
|
238
|
+
const stylesheetsPath = path.join(sectionsDir, 'stylesheets.json');
|
|
239
|
+
let stylesheets = null;
|
|
240
|
+
if (fs.existsSync(stylesheetsPath)) {
|
|
241
|
+
stylesheets = JSON.parse(fs.readFileSync(stylesheetsPath, 'utf8'));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const outDir = opts.out;
|
|
245
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
246
|
+
fs.mkdirSync(path.join(outDir, 'sections'), { recursive: true });
|
|
247
|
+
|
|
248
|
+
const plan = buildClassPlan(data.sections, featureClass);
|
|
249
|
+
|
|
250
|
+
// Write files
|
|
251
|
+
fs.writeFileSync(path.join(outDir, '_tokens.scss'), emitTokens(data.tokens));
|
|
252
|
+
fs.writeFileSync(path.join(outDir, '_base.scss'), emitBase(featureClass, stylesheets));
|
|
253
|
+
fs.writeFileSync(
|
|
254
|
+
path.join(outDir, '_shared.scss'),
|
|
255
|
+
`// Cross-section utilities — extend as needed.\n`,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
for (const sec of data.sections) {
|
|
259
|
+
const fname = `_${kebab(sec.name)}.scss`;
|
|
260
|
+
fs.writeFileSync(path.join(outDir, 'sections', fname), emitSection(sec, plan));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
fs.writeFileSync(path.join(outDir, 'index.scss'), emitIndex(featureClass, data.sections));
|
|
264
|
+
fs.writeFileSync(path.join(outDir, 'class-plan.json'), JSON.stringify(plan, null, 2));
|
|
265
|
+
|
|
266
|
+
console.log(`[clone-to-scss] done → ${outDir}`);
|
|
267
|
+
console.log(` sections: ${data.sections.length}, classes: ${Object.keys(plan).length}`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
try { main(); }
|
|
271
|
+
catch (e) {
|
|
272
|
+
console.error(`[clone-to-scss] FAIL: ${e.message}`);
|
|
273
|
+
if (process.env.DEBUG) console.error(e.stack);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* clone-validate.js — written SCSS vs sections.json (source of truth)
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node clone-validate.js <styles-dir> <sections.json> [--section=<name>]
|
|
8
|
+
*
|
|
9
|
+
* Reads:
|
|
10
|
+
* <styles-dir>/sections/_<section>.scss
|
|
11
|
+
* <styles-dir>/class-plan.json
|
|
12
|
+
* <sections.json>
|
|
13
|
+
*
|
|
14
|
+
* Compares emitted CSS declarations against expectations:
|
|
15
|
+
* - Missing property → P1
|
|
16
|
+
* - Mismatched value → P1 (if box prop and delta > 4px), else P2
|
|
17
|
+
* - Extra property → P3 (informational)
|
|
18
|
+
*
|
|
19
|
+
* Exit codes:
|
|
20
|
+
* 0 PASS, 1 FAIL (P1 found), 2 usage error
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import fs from 'fs';
|
|
24
|
+
import path from 'path';
|
|
25
|
+
|
|
26
|
+
function parseArgs(argv) {
|
|
27
|
+
const [, , stylesDir, sectionsPath, ...rest] = argv;
|
|
28
|
+
const opts = {};
|
|
29
|
+
for (const a of rest) {
|
|
30
|
+
if (a.startsWith('--section=')) opts.section = a.slice(10);
|
|
31
|
+
else if (a === '--quiet') opts.quiet = true;
|
|
32
|
+
}
|
|
33
|
+
return { stylesDir, sectionsPath, opts };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { stylesDir, sectionsPath, opts } = parseArgs(process.argv);
|
|
37
|
+
if (!stylesDir || !sectionsPath) {
|
|
38
|
+
console.error('Usage: node clone-validate.js <styles-dir> <sections.json> [--section=<name>]');
|
|
39
|
+
process.exit(2);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── Minimal SCSS parser ────────────────────────────────────────────
|
|
43
|
+
// Strips comments, expands `&::pseudo` (one level), flattens nested rules.
|
|
44
|
+
// NOT a full SCSS engine — assumes clone-to-scss.js output style.
|
|
45
|
+
|
|
46
|
+
function stripComments(src) {
|
|
47
|
+
return src
|
|
48
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
49
|
+
.replace(/\/\/[^\n]*/g, '');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parseScss(src) {
|
|
53
|
+
const clean = stripComments(src);
|
|
54
|
+
const rules = new Map(); // selector → { prop: value }
|
|
55
|
+
let i = 0;
|
|
56
|
+
const len = clean.length;
|
|
57
|
+
|
|
58
|
+
const skipWs = () => { while (i < len && /\s/.test(clean[i])) i++; };
|
|
59
|
+
|
|
60
|
+
const readUntil = (chars) => {
|
|
61
|
+
let buf = '';
|
|
62
|
+
while (i < len && !chars.includes(clean[i])) buf += clean[i++];
|
|
63
|
+
return buf;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const stack = []; // selector stack
|
|
67
|
+
|
|
68
|
+
while (i < len) {
|
|
69
|
+
skipWs();
|
|
70
|
+
if (i >= len) break;
|
|
71
|
+
const ch = clean[i];
|
|
72
|
+
if (ch === '}') { stack.pop(); i++; continue; }
|
|
73
|
+
|
|
74
|
+
// Lookahead: is this a rule (has '{' before ';' or end) or declaration?
|
|
75
|
+
let depth = 0;
|
|
76
|
+
let j = i;
|
|
77
|
+
let kind = 'decl';
|
|
78
|
+
while (j < len) {
|
|
79
|
+
const c = clean[j];
|
|
80
|
+
if (c === '"' || c === "'") {
|
|
81
|
+
const q = c; j++;
|
|
82
|
+
while (j < len && clean[j] !== q) { if (clean[j] === '\\') j++; j++; }
|
|
83
|
+
}
|
|
84
|
+
if (c === '(') depth++;
|
|
85
|
+
else if (c === ')') depth--;
|
|
86
|
+
else if (depth === 0 && c === ';') { kind = 'decl'; break; }
|
|
87
|
+
else if (depth === 0 && c === '{') { kind = 'rule'; break; }
|
|
88
|
+
else if (depth === 0 && c === '}') { break; }
|
|
89
|
+
j++;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (kind === 'rule') {
|
|
93
|
+
const selector = clean.slice(i, j).trim();
|
|
94
|
+
i = j + 1; // consume '{'
|
|
95
|
+
// Resolve nested selector
|
|
96
|
+
const parent = stack[stack.length - 1] || '';
|
|
97
|
+
let resolved;
|
|
98
|
+
if (selector.startsWith('&')) {
|
|
99
|
+
resolved = parent + selector.slice(1);
|
|
100
|
+
} else if (parent) {
|
|
101
|
+
// descendant
|
|
102
|
+
resolved = selector.split(',').map((s) => parent + ' ' + s.trim()).join(', ');
|
|
103
|
+
} else {
|
|
104
|
+
resolved = selector;
|
|
105
|
+
}
|
|
106
|
+
stack.push(resolved);
|
|
107
|
+
if (!rules.has(resolved)) rules.set(resolved, {});
|
|
108
|
+
} else {
|
|
109
|
+
// declaration
|
|
110
|
+
const text = clean.slice(i, j).trim();
|
|
111
|
+
i = j + 1;
|
|
112
|
+
if (!text) continue;
|
|
113
|
+
const colon = text.indexOf(':');
|
|
114
|
+
if (colon < 0) continue;
|
|
115
|
+
const prop = text.slice(0, colon).trim();
|
|
116
|
+
const val = text.slice(colon + 1).trim();
|
|
117
|
+
const sel = stack[stack.length - 1];
|
|
118
|
+
if (sel) {
|
|
119
|
+
if (!rules.has(sel)) rules.set(sel, {});
|
|
120
|
+
rules.get(sel)[prop] = val;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return rules;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ─── Comparison ─────────────────────────────────────────────────────
|
|
129
|
+
const BOX_PROPS = new Set([
|
|
130
|
+
'width', 'height', 'min-width', 'min-height', 'max-width', 'max-height',
|
|
131
|
+
'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
|
|
132
|
+
'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
|
|
133
|
+
'top', 'right', 'bottom', 'left', 'gap', 'row-gap', 'column-gap',
|
|
134
|
+
'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width',
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
function pxOf(v) {
|
|
138
|
+
if (typeof v !== 'string') return null;
|
|
139
|
+
const m = /^(-?\d+(?:\.\d+)?)px$/.exec(v.trim());
|
|
140
|
+
return m ? Number(m[1]) : null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function normalize(v) {
|
|
144
|
+
if (typeof v !== 'string') return v;
|
|
145
|
+
return v.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function compareValue(prop, expected, actual) {
|
|
149
|
+
if (normalize(expected) === normalize(actual)) return { ok: true };
|
|
150
|
+
if (BOX_PROPS.has(prop)) {
|
|
151
|
+
const ep = pxOf(expected), ap = pxOf(actual);
|
|
152
|
+
if (ep !== null && ap !== null) {
|
|
153
|
+
const delta = Math.abs(ep - ap);
|
|
154
|
+
return { ok: delta <= 4, severity: delta > 4 ? 'P1' : 'P2', delta };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { ok: false, severity: 'P1' };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function walkNodes(section) {
|
|
161
|
+
const flat = [];
|
|
162
|
+
const recur = (n) => {
|
|
163
|
+
flat.push(n);
|
|
164
|
+
for (const c of (n.children || [])) recur(c);
|
|
165
|
+
};
|
|
166
|
+
flat.push({ id: section.nodeRef, css: section.css, tag: section.tag, classes: section.classes });
|
|
167
|
+
for (const c of (section.children || [])) recur(c);
|
|
168
|
+
return flat;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ─── Main ───────────────────────────────────────────────────────────
|
|
172
|
+
function main() {
|
|
173
|
+
const sectionsJson = JSON.parse(fs.readFileSync(sectionsPath, 'utf8'));
|
|
174
|
+
const classPlanPath = path.join(stylesDir, 'class-plan.json');
|
|
175
|
+
if (!fs.existsSync(classPlanPath)) {
|
|
176
|
+
console.error(`class-plan.json not found at ${classPlanPath}. Run clone-to-scss.js first.`);
|
|
177
|
+
process.exit(2);
|
|
178
|
+
}
|
|
179
|
+
const classPlan = JSON.parse(fs.readFileSync(classPlanPath, 'utf8'));
|
|
180
|
+
|
|
181
|
+
const targets = opts.section
|
|
182
|
+
? sectionsJson.sections.filter((s) => s.name === opts.section)
|
|
183
|
+
: sectionsJson.sections;
|
|
184
|
+
|
|
185
|
+
if (opts.section && targets.length === 0) {
|
|
186
|
+
console.error(`Section not found: ${opts.section}`);
|
|
187
|
+
process.exit(2);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const issues = [];
|
|
191
|
+
let totalChecked = 0;
|
|
192
|
+
|
|
193
|
+
for (const section of targets) {
|
|
194
|
+
const kebabName = section.name.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
195
|
+
const scssPath = path.join(stylesDir, 'sections', `_${kebabName}.scss`);
|
|
196
|
+
if (!fs.existsSync(scssPath)) {
|
|
197
|
+
issues.push({ section: section.name, severity: 'P1', kind: 'missing-file', path: scssPath });
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const scss = fs.readFileSync(scssPath, 'utf8');
|
|
201
|
+
const rules = parseScss(scss);
|
|
202
|
+
|
|
203
|
+
const flat = walkNodes(section);
|
|
204
|
+
for (const node of flat) {
|
|
205
|
+
const cls = classPlan[node.id];
|
|
206
|
+
if (!cls || !node.css) continue;
|
|
207
|
+
const selector = `.${cls}`;
|
|
208
|
+
const declared = rules.get(selector) || {};
|
|
209
|
+
|
|
210
|
+
for (const [prop, expected] of Object.entries(node.css)) {
|
|
211
|
+
if (prop.startsWith('--')) continue;
|
|
212
|
+
if (!expected || expected === 'normal' || expected === 'auto' || expected === 'none') continue;
|
|
213
|
+
totalChecked++;
|
|
214
|
+
const actual = declared[prop];
|
|
215
|
+
if (actual === undefined) {
|
|
216
|
+
issues.push({
|
|
217
|
+
section: section.name,
|
|
218
|
+
nodeId: node.id,
|
|
219
|
+
class: cls,
|
|
220
|
+
prop,
|
|
221
|
+
severity: 'P1',
|
|
222
|
+
kind: 'missing-prop',
|
|
223
|
+
expected,
|
|
224
|
+
});
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const cmp = compareValue(prop, expected, actual);
|
|
228
|
+
if (!cmp.ok) {
|
|
229
|
+
issues.push({
|
|
230
|
+
section: section.name,
|
|
231
|
+
nodeId: node.id,
|
|
232
|
+
class: cls,
|
|
233
|
+
prop,
|
|
234
|
+
severity: cmp.severity,
|
|
235
|
+
kind: 'value-mismatch',
|
|
236
|
+
expected,
|
|
237
|
+
actual,
|
|
238
|
+
delta: cmp.delta,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const p1 = issues.filter((i) => i.severity === 'P1');
|
|
246
|
+
const p2 = issues.filter((i) => i.severity === 'P2');
|
|
247
|
+
const p3 = issues.filter((i) => i.severity === 'P3');
|
|
248
|
+
|
|
249
|
+
if (!opts.quiet) {
|
|
250
|
+
console.log(`[clone-validate] checked ${totalChecked} declarations across ${targets.length} section(s)`);
|
|
251
|
+
console.log(` P1: ${p1.length}, P2: ${p2.length}, P3: ${p3.length}`);
|
|
252
|
+
const show = (label, list, limit = 20) => {
|
|
253
|
+
if (list.length === 0) return;
|
|
254
|
+
console.log(`\n${label}:`);
|
|
255
|
+
for (const it of list.slice(0, limit)) {
|
|
256
|
+
const loc = it.class ? `.${it.class}` : it.path || '?';
|
|
257
|
+
if (it.kind === 'missing-file') console.log(` ${it.severity} ${it.section}: missing ${it.path}`);
|
|
258
|
+
else if (it.kind === 'missing-prop') console.log(` ${it.severity} ${loc} { ${it.prop}: MISSING (expected ${it.expected}) }`);
|
|
259
|
+
else console.log(` ${it.severity} ${loc} { ${it.prop}: ${it.actual} ≠ ${it.expected}${it.delta != null ? ` (Δ${it.delta}px)` : ''} }`);
|
|
260
|
+
}
|
|
261
|
+
if (list.length > limit) console.log(` …and ${list.length - limit} more`);
|
|
262
|
+
};
|
|
263
|
+
show('P1 issues', p1);
|
|
264
|
+
show('P2 issues', p2);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (p1.length > 0) {
|
|
268
|
+
if (!opts.quiet) console.error(`\n[clone-validate] FAIL: ${p1.length} P1 issue(s)`);
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
if (!opts.quiet) console.log('\n[clone-validate] PASS');
|
|
272
|
+
process.exit(0);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
try { main(); }
|
|
276
|
+
catch (e) {
|
|
277
|
+
console.error(`[clone-validate] FAIL: ${e.message}`);
|
|
278
|
+
if (process.env.DEBUG) console.error(e.stack);
|
|
279
|
+
process.exit(2);
|
|
280
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Codex notify adapter — Codex `~/.codex/config.toml` 의 `notify` 프로그램으로 호출됨.
|
|
4
|
+
*
|
|
5
|
+
* Codex 는 Claude Code 의 Stop hook 등가물이 없다. 대신 lifecycle 이벤트마다
|
|
6
|
+
* `notify` 에 등록된 프로그램을 마지막 인자로 JSON payload 를 붙여 실행한다.
|
|
7
|
+
* 이 어댑터는 `agent-turn-complete` 이벤트를 CC 의 Stop 에 매핑해, stdin 이 필요
|
|
8
|
+
* 없는 결정적 후처리(auto-commit, devlog-gen)만 재사용한다.
|
|
9
|
+
*
|
|
10
|
+
* - codex-review-gate: stdout→Claude 주입 방식이라 Codex 에서 의미 없음 → 제외
|
|
11
|
+
* - stop-notify: Codex 가 자체 완료 표시를 하므로 중복 알림 방지 위해 제외
|
|
12
|
+
*
|
|
13
|
+
* 재귀 가드: VIBE_HOOK_DEPTH 가 있으면(자식 세션) 건너뛴다.
|
|
14
|
+
*/
|
|
15
|
+
import { spawnSync } from 'child_process';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
|
|
19
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
|
|
21
|
+
if (process.env.VIBE_HOOK_DEPTH) process.exit(0);
|
|
22
|
+
|
|
23
|
+
// Codex 는 payload JSON 을 마지막 인자로 전달한다.
|
|
24
|
+
function parsePayload() {
|
|
25
|
+
const raw = process.argv[process.argv.length - 1];
|
|
26
|
+
if (!raw || raw === path.basename(process.argv[1] ?? '')) return null;
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(raw);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const payload = parsePayload();
|
|
35
|
+
const type = payload?.type ?? payload?.['type'];
|
|
36
|
+
|
|
37
|
+
// turn 완료 이벤트만 처리 (CC 의 Stop 등가)
|
|
38
|
+
if (type !== 'agent-turn-complete') process.exit(0);
|
|
39
|
+
|
|
40
|
+
const TURN_COMPLETE_SCRIPTS = ['auto-commit.js', 'devlog-gen.js'];
|
|
41
|
+
|
|
42
|
+
for (const script of TURN_COMPLETE_SCRIPTS) {
|
|
43
|
+
spawnSync(process.execPath, [path.join(__dirname, script)], {
|
|
44
|
+
stdio: ['ignore', 'inherit', 'inherit'],
|
|
45
|
+
env: { ...process.env, VIBE_HOOK_DEPTH: '1' },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
process.exit(0);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PreToolUse Hook - 모든 Bash 명령어를 타임스탬프와 함께 로깅
|
|
3
3
|
*
|
|
4
|
-
* 로그 위치: .vibe/command-log.txt (legacy .claude/vibe/
|
|
4
|
+
* 로그 위치: .vibe/command-log.txt (legacy .claude/vibe/ 사용 중이면 그쪽)
|
|
5
5
|
* exit 0 항상 통과 (로깅만 수행, 차단하지 않음)
|
|
6
6
|
*/
|
|
7
7
|
import { appendFileSync, mkdirSync, existsSync } from 'fs';
|
|
@@ -26,12 +26,11 @@ const SCRIPTS_DIR = path.resolve(__dirname, '..');
|
|
|
26
26
|
|
|
27
27
|
function loadHookConfig() {
|
|
28
28
|
try {
|
|
29
|
-
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.
|
|
30
|
-
// Vibe config 탐색 — `.vibe/` 를 SSOT 로 삼고, legacy `.claude/vibe
|
|
29
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
30
|
+
// Vibe config 탐색 — `.vibe/` 를 SSOT 로 삼고, legacy `.claude/vibe/` fallback
|
|
31
31
|
const candidates = [
|
|
32
32
|
path.join(projectDir, '.vibe', 'config.json'),
|
|
33
33
|
path.join(projectDir, '.claude', 'vibe', 'config.json'),
|
|
34
|
-
path.join(projectDir, '.coco', 'vibe', 'config.json'),
|
|
35
34
|
];
|
|
36
35
|
const configPath = candidates.find(p => fs.existsSync(p));
|
|
37
36
|
if (!configPath) return {};
|
|
@@ -27,7 +27,6 @@ export function isScopeGuardEnabled(projectDir) {
|
|
|
27
27
|
const candidates = [
|
|
28
28
|
path.join(projectDir, '.vibe', 'config.json'),
|
|
29
29
|
path.join(projectDir, '.claude', 'vibe', 'config.json'),
|
|
30
|
-
path.join(projectDir, '.coco', 'vibe', 'config.json'),
|
|
31
30
|
];
|
|
32
31
|
for (const p of candidates) {
|
|
33
32
|
if (!fs.existsSync(p)) continue;
|
|
@@ -39,14 +38,13 @@ export function isScopeGuardEnabled(projectDir) {
|
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
/**
|
|
42
|
-
* Vibe 에셋 루트 해석 — `.vibe/` (SSOT) 우선, legacy `.claude/vibe
|
|
41
|
+
* Vibe 에셋 루트 해석 — `.vibe/` (SSOT) 우선, legacy `.claude/vibe/` fallback.
|
|
43
42
|
* 반환값은 projectDir 기준 상대 경로 문자열 (예: `.vibe`, `.claude/vibe`).
|
|
44
43
|
*/
|
|
45
44
|
function detectVibeRoot(projectDir) {
|
|
46
45
|
try {
|
|
47
46
|
if (fs.existsSync(path.join(projectDir, '.vibe'))) return '.vibe';
|
|
48
47
|
if (fs.existsSync(path.join(projectDir, '.claude', 'vibe'))) return '.claude/vibe';
|
|
49
|
-
if (fs.existsSync(path.join(projectDir, '.coco', 'vibe'))) return '.coco/vibe';
|
|
50
48
|
} catch { /* ignore */ }
|
|
51
49
|
return '.vibe';
|
|
52
50
|
}
|
|
@@ -108,7 +106,7 @@ function extractPaths(markdown) {
|
|
|
108
106
|
// 명백한 비경로 제외
|
|
109
107
|
if (/^[A-Z_]+$/.test(raw)) continue; // 상수
|
|
110
108
|
if (/^\d+$/.test(raw)) continue; // 숫자
|
|
111
|
-
if (raw.startsWith('.') && !raw.startsWith('./') && !raw.startsWith('.vibe') && !raw.startsWith('.claude')
|
|
109
|
+
if (raw.startsWith('.') && !raw.startsWith('./') && !raw.startsWith('.vibe') && !raw.startsWith('.claude')) continue;
|
|
112
110
|
|
|
113
111
|
const normalized = raw.replace(/^\.\//, '').replace(/\\/g, '/');
|
|
114
112
|
// 각 세그먼트 안전성 재검증 — "a.b.c" 같은 필드 경로 제거
|