@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.
Files changed (182) hide show
  1. package/CLAUDE.md +28 -12
  2. package/README.en.md +2 -3
  3. package/README.md +33 -27
  4. package/agents/{teams/figma → figma}/figma-analyst.md +2 -2
  5. package/agents/research/{best-practices-agent.md → best-practices.md} +1 -1
  6. package/agents/research/{codebase-patterns-agent.md → codebase-patterns.md} +1 -1
  7. package/agents/research/{framework-docs-agent.md → framework-docs.md} +1 -1
  8. package/agents/research/{security-advisory-agent.md → security-advisory.md} +1 -1
  9. package/agents/teams/research-team.md +4 -4
  10. package/agents/teams/review-debate-team.md +2 -2
  11. package/agents/teams/security-team.md +4 -4
  12. package/dist/cli/auth.d.ts +0 -1
  13. package/dist/cli/auth.d.ts.map +1 -1
  14. package/dist/cli/auth.js +1 -18
  15. package/dist/cli/auth.js.map +1 -1
  16. package/dist/cli/collaborator.d.ts +3 -3
  17. package/dist/cli/collaborator.js +4 -4
  18. package/dist/cli/collaborator.js.map +1 -1
  19. package/dist/cli/commands/info.d.ts.map +1 -1
  20. package/dist/cli/commands/info.js +0 -1
  21. package/dist/cli/commands/info.js.map +1 -1
  22. package/dist/cli/commands/init.d.ts +3 -4
  23. package/dist/cli/commands/init.d.ts.map +1 -1
  24. package/dist/cli/commands/init.js +15 -20
  25. package/dist/cli/commands/init.js.map +1 -1
  26. package/dist/cli/commands/remove.d.ts.map +1 -1
  27. package/dist/cli/commands/remove.js +2 -7
  28. package/dist/cli/commands/remove.js.map +1 -1
  29. package/dist/cli/commands/update.d.ts.map +1 -1
  30. package/dist/cli/commands/update.js +10 -10
  31. package/dist/cli/commands/update.js.map +1 -1
  32. package/dist/cli/index.js +1 -3
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/postinstall/claude-agents.d.ts +3 -1
  35. package/dist/cli/postinstall/claude-agents.d.ts.map +1 -1
  36. package/dist/cli/postinstall/claude-agents.js +47 -9
  37. package/dist/cli/postinstall/claude-agents.js.map +1 -1
  38. package/dist/cli/postinstall/constants.d.ts +5 -0
  39. package/dist/cli/postinstall/constants.d.ts.map +1 -1
  40. package/dist/cli/postinstall/constants.js +165 -23
  41. package/dist/cli/postinstall/constants.js.map +1 -1
  42. package/dist/cli/postinstall/cursor-skills.js +2 -2
  43. package/dist/cli/postinstall/main.d.ts.map +1 -1
  44. package/dist/cli/postinstall/main.js +26 -24
  45. package/dist/cli/postinstall/main.js.map +1 -1
  46. package/dist/cli/setup/LegacyMigration.d.ts +3 -3
  47. package/dist/cli/setup/LegacyMigration.d.ts.map +1 -1
  48. package/dist/cli/setup/LegacyMigration.js +3 -5
  49. package/dist/cli/setup/LegacyMigration.js.map +1 -1
  50. package/dist/cli/setup/ProjectSetup.d.ts +18 -8
  51. package/dist/cli/setup/ProjectSetup.d.ts.map +1 -1
  52. package/dist/cli/setup/ProjectSetup.js +70 -19
  53. package/dist/cli/setup/ProjectSetup.js.map +1 -1
  54. package/dist/cli/setup.d.ts +1 -1
  55. package/dist/cli/setup.d.ts.map +1 -1
  56. package/dist/cli/setup.js +1 -1
  57. package/dist/cli/setup.js.map +1 -1
  58. package/dist/cli/utils/cli-detector.d.ts +0 -7
  59. package/dist/cli/utils/cli-detector.d.ts.map +1 -1
  60. package/dist/cli/utils/cli-detector.js +0 -95
  61. package/dist/cli/utils/cli-detector.js.map +1 -1
  62. package/dist/cli/utils.d.ts +1 -1
  63. package/dist/cli/utils.d.ts.map +1 -1
  64. package/dist/cli/utils.js +1 -2
  65. package/dist/cli/utils.js.map +1 -1
  66. package/dist/infra/lib/OrchestrateWorkflow.js +1 -1
  67. package/dist/infra/lib/OrchestrateWorkflow.js.map +1 -1
  68. package/dist/infra/lib/memory/MemoryStorage.d.ts +1 -1
  69. package/dist/infra/lib/memory/MemoryStorage.d.ts.map +1 -1
  70. package/dist/infra/lib/memory/MemoryStorage.js +2 -3
  71. package/dist/infra/lib/memory/MemoryStorage.js.map +1 -1
  72. package/dist/infra/lib/telemetry/SkillTelemetry.test.js +4 -4
  73. package/dist/infra/lib/telemetry/SkillTelemetry.test.js.map +1 -1
  74. package/dist/infra/orchestrator/parallelResearch.js +4 -4
  75. package/dist/infra/orchestrator/parallelResearch.js.map +1 -1
  76. package/hooks/scripts/__tests__/pre-tool-guard.test.js +1 -1
  77. package/hooks/scripts/clone-extract.js +712 -0
  78. package/hooks/scripts/clone-refine.js +510 -0
  79. package/hooks/scripts/clone-to-scss.js +275 -0
  80. package/hooks/scripts/clone-validate.js +280 -0
  81. package/hooks/scripts/codex-notify.js +49 -0
  82. package/hooks/scripts/command-log.js +1 -1
  83. package/hooks/scripts/lib/dispatcher.js +2 -3
  84. package/hooks/scripts/lib/scope-from-spec.js +2 -4
  85. package/hooks/scripts/llm-orchestrate.js +2 -7
  86. package/hooks/scripts/prompt-dispatcher.js +3 -3
  87. package/hooks/scripts/step-counter.js +1 -1
  88. package/hooks/scripts/utils.js +5 -10
  89. package/package.json +2 -1
  90. package/skills/agents-md/SKILL.md +2 -0
  91. package/skills/arch-guard/SKILL.md +2 -0
  92. package/skills/brand-assets/SKILL.md +1 -0
  93. package/skills/capability-loop/SKILL.md +2 -0
  94. package/skills/characterization-test/SKILL.md +2 -0
  95. package/skills/chub-usage/SKILL.md +1 -0
  96. package/skills/claude-md-guide/SKILL.md +2 -0
  97. package/skills/clone/SKILL.md +361 -0
  98. package/skills/commerce-patterns/SKILL.md +1 -0
  99. package/skills/commit-push-pr/SKILL.md +1 -0
  100. package/skills/context7-usage/SKILL.md +1 -0
  101. package/skills/{vibe-contract → contract}/SKILL.md +7 -8
  102. package/skills/create-prd/SKILL.md +1 -0
  103. package/skills/design-audit/SKILL.md +1 -0
  104. package/skills/design-critique/SKILL.md +1 -0
  105. package/skills/design-distill/SKILL.md +1 -0
  106. package/skills/design-normalize/SKILL.md +1 -0
  107. package/skills/design-polish/SKILL.md +1 -0
  108. package/skills/design-teach/SKILL.md +2 -0
  109. package/skills/devlog/SKILL.md +1 -0
  110. package/skills/{vibe-docs → docs}/SKILL.md +8 -8
  111. package/skills/e2e-commerce/SKILL.md +1 -0
  112. package/skills/event-comms/SKILL.md +1 -0
  113. package/skills/event-ops/SKILL.md +1 -0
  114. package/skills/event-planning/SKILL.md +1 -0
  115. package/skills/exec-plan/SKILL.md +2 -0
  116. package/skills/{vibe-figma → figma}/SKILL.md +4 -3
  117. package/skills/{vibe-figma-convert → figma-convert}/SKILL.md +4 -3
  118. package/skills/{vibe-figma-extract → figma-extract}/SKILL.md +4 -3
  119. package/skills/git-worktree/SKILL.md +1 -0
  120. package/skills/handoff/SKILL.md +2 -0
  121. package/skills/{vibe-interview → interview}/SKILL.md +16 -16
  122. package/skills/parallel-research/SKILL.md +2 -0
  123. package/skills/{vibe-plan → plan}/SKILL.md +9 -9
  124. package/skills/prioritization-frameworks/SKILL.md +1 -0
  125. package/skills/priority-todos/SKILL.md +2 -0
  126. package/skills/{vibe-regress → regress}/SKILL.md +5 -6
  127. package/skills/rob-pike/SKILL.md +2 -0
  128. package/skills/seo-checklist/SKILL.md +1 -0
  129. package/skills/{vibe-spec → spec}/SKILL.md +14 -14
  130. package/skills/{vibe-spec-review → spec-review}/SKILL.md +8 -9
  131. package/skills/systematic-debugging/SKILL.md +2 -0
  132. package/skills/techdebt/SKILL.md +2 -0
  133. package/skills/{vibe-test → test}/SKILL.md +19 -19
  134. package/skills/tool-fallback/SKILL.md +1 -0
  135. package/skills/typescript-advanced-types/SKILL.md +1 -0
  136. package/skills/ui-ux-pro-max/SKILL.md +1 -0
  137. package/skills/user-personas/SKILL.md +1 -0
  138. package/skills/vercel-react-best-practices/SKILL.md +1 -0
  139. package/skills/vibe/SKILL.md +288 -0
  140. package/{commands/vibe.analyze.md → skills/vibe.analyze/SKILL.md} +2 -0
  141. package/skills/vibe.clone/SKILL.md +117 -0
  142. package/{commands/vibe.contract.md → skills/vibe.contract/SKILL.md} +3 -1
  143. package/{commands/vibe.docs.md → skills/vibe.docs/SKILL.md} +3 -1
  144. package/{commands/vibe.event.md → skills/vibe.event/SKILL.md} +2 -0
  145. package/{commands/vibe.figma.md → skills/vibe.figma/SKILL.md} +25 -23
  146. package/{commands/vibe.harness.md → skills/vibe.harness/SKILL.md} +2 -0
  147. package/{commands/vibe.reason.md → skills/vibe.reason/SKILL.md} +2 -0
  148. package/{commands/vibe.regress.md → skills/vibe.regress/SKILL.md} +5 -3
  149. package/{commands/vibe.review.md → skills/vibe.review/SKILL.md} +2 -0
  150. package/{commands/vibe.run.md → skills/vibe.run/SKILL.md} +5 -1
  151. package/{commands/vibe.scaffold.md → skills/vibe.scaffold/SKILL.md} +2 -0
  152. package/{commands/vibe.spec.md → skills/vibe.spec/SKILL.md} +36 -34
  153. package/{commands/vibe.test.md → skills/vibe.test/SKILL.md} +8 -6
  154. package/{commands/vibe.trace.md → skills/vibe.trace/SKILL.md} +7 -0
  155. package/{commands/vibe.utils.md → skills/vibe.utils/SKILL.md} +2 -0
  156. package/{commands/vibe.verify.md → skills/vibe.verify/SKILL.md} +4 -2
  157. package/skills/video-production/SKILL.md +1 -0
  158. package/vibe/rules/principles/dual-harness-doctrine.md +50 -0
  159. /package/agents/{teams/figma → figma}/figma-architect.md +0 -0
  160. /package/agents/{teams/figma → figma}/figma-auditor.md +0 -0
  161. /package/agents/{teams/figma → figma}/figma-builder.md +0 -0
  162. /package/skills/{vibe-docs → docs}/templates/architecture.md +0 -0
  163. /package/skills/{vibe-docs → docs}/templates/behavioral-principles.md +0 -0
  164. /package/skills/{vibe-docs → docs}/templates/readme.md +0 -0
  165. /package/skills/{vibe-docs → docs}/templates/release-notes.md +0 -0
  166. /package/skills/{vibe-figma → figma}/rubrics/extraction-checklist.md +0 -0
  167. /package/skills/{vibe-figma → figma}/templates/component-index.md +0 -0
  168. /package/skills/{vibe-figma → figma}/templates/component-spec.md +0 -0
  169. /package/skills/{vibe-figma → figma}/templates/figma-handoff.md +0 -0
  170. /package/skills/{vibe-figma → figma}/templates/remapped-tree.md +0 -0
  171. /package/skills/{vibe-figma-convert → figma-convert}/rubrics/conversion-rules.md +0 -0
  172. /package/skills/{vibe-figma-convert → figma-convert}/templates/component.md +0 -0
  173. /package/skills/{vibe-figma-extract → figma-extract}/rubrics/image-rules.md +0 -0
  174. /package/skills/{vibe-interview → interview}/checklists/api.md +0 -0
  175. /package/skills/{vibe-interview → interview}/checklists/feature.md +0 -0
  176. /package/skills/{vibe-interview → interview}/checklists/library.md +0 -0
  177. /package/skills/{vibe-interview → interview}/checklists/mobile.md +0 -0
  178. /package/skills/{vibe-interview → interview}/checklists/webapp.md +0 -0
  179. /package/skills/{vibe-interview → interview}/checklists/website.md +0 -0
  180. /package/skills/{vibe-regress → regress}/templates/bug.md +0 -0
  181. /package/skills/{vibe-regress → regress}/templates/test-jest.md +0 -0
  182. /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/ 또는 .coco/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.env.COCO_PROJECT_DIR || process.cwd();
30
- // Vibe config 탐색 — `.vibe/` 를 SSOT 로 삼고, legacy `.claude/vibe/`, `.coco/vibe/` fallback
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/`, `.coco/vibe/` fallback.
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') && !raw.startsWith('.coco')) continue;
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" 같은 필드 경로 제거