oh-my-design-cli 0.1.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/.claude/hooks/post-edit-watch.cjs +99 -0
  2. package/.claude/hooks/session-end-foldin.cjs +96 -0
  3. package/.claude/hooks/session-state-loader.cjs +64 -0
  4. package/.claude/hooks/skill-activation.cjs +73 -0
  5. package/.claude/settings.json +55 -0
  6. package/.claude/skills/skill-rules.json +87 -0
  7. package/AGENTS.md +111 -0
  8. package/README.md +75 -202
  9. package/agents/AGENT.md +53 -0
  10. package/agents/omd-3d-blender.md +269 -0
  11. package/agents/omd-a11y-auditor.md +97 -0
  12. package/agents/omd-asset-curator.md +260 -0
  13. package/agents/omd-critic.md +181 -0
  14. package/agents/omd-master.md +548 -0
  15. package/agents/omd-microcopy.md +63 -0
  16. package/agents/omd-persona-tester.md +118 -0
  17. package/agents/omd-ui-junior.md +129 -0
  18. package/agents/omd-ux-engineer.md +265 -0
  19. package/agents/omd-ux-researcher.md +62 -0
  20. package/agents/omd-ux-writer.md +181 -0
  21. package/data/opt-out-corpus.json +141 -0
  22. package/data/reference-fingerprints.json +1495 -0
  23. package/dist/bin/oh-my-design.js +3 -818
  24. package/dist/bin/oh-my-design.js.map +1 -1
  25. package/dist/install-skills-SVIYKXOE.js +442 -0
  26. package/dist/install-skills-SVIYKXOE.js.map +1 -0
  27. package/package.json +23 -23
  28. package/scripts/context.cjs +91 -0
  29. package/scripts/postinstall.cjs +54 -0
  30. package/skills/omd-apply/SKILL.md +64 -53
  31. package/skills/omd-harness/SKILL.md +271 -0
  32. package/skills/omd-learn/SKILL.md +55 -35
  33. package/skills/omd-remember/SKILL.md +93 -15
  34. package/skills/omd-sync/SKILL.md +140 -16
  35. package/dist/chunk-6YNSV3VY.js +0 -35
  36. package/dist/chunk-6YNSV3VY.js.map +0 -1
  37. package/dist/chunk-MHFYGZSO.js +0 -337
  38. package/dist/chunk-MHFYGZSO.js.map +0 -1
  39. package/dist/chunk-N2JG6N4Q.js +0 -264
  40. package/dist/chunk-N2JG6N4Q.js.map +0 -1
  41. package/dist/chunk-OOQQEUGX.js +0 -46
  42. package/dist/chunk-OOQQEUGX.js.map +0 -1
  43. package/dist/chunk-OR5DHENY.js +0 -250
  44. package/dist/chunk-OR5DHENY.js.map +0 -1
  45. package/dist/customizer-CM76752R.js +0 -8
  46. package/dist/customizer-CM76752R.js.map +0 -1
  47. package/dist/index.d.ts +0 -559
  48. package/dist/index.js +0 -3113
  49. package/dist/index.js.map +0 -1
  50. package/dist/init-UMM4XIV5.js +0 -675
  51. package/dist/init-UMM4XIV5.js.map +0 -1
  52. package/dist/install-skills-CM6VXFZJ.js +0 -152
  53. package/dist/install-skills-CM6VXFZJ.js.map +0 -1
  54. package/dist/learn-33LHKEJA.js +0 -140
  55. package/dist/learn-33LHKEJA.js.map +0 -1
  56. package/dist/reference-YMNAOXJQ.js +0 -47
  57. package/dist/reference-YMNAOXJQ.js.map +0 -1
  58. package/dist/reference-parser-TM3CJPNE.js +0 -10
  59. package/dist/reference-parser-TM3CJPNE.js.map +0 -1
  60. package/dist/remember-UAFA5B2O.js +0 -78
  61. package/dist/remember-UAFA5B2O.js.map +0 -1
  62. package/dist/sync-FDYRKNFE.js +0 -417
  63. package/dist/sync-FDYRKNFE.js.map +0 -1
  64. package/dist/templates/templates/design-md.hbs +0 -44
  65. package/dist/templates/templates/partials/agent-prompt-guide.hbs +0 -28
  66. package/dist/templates/templates/partials/color-palette.hbs +0 -49
  67. package/dist/templates/templates/partials/component-stylings.hbs +0 -28
  68. package/dist/templates/templates/partials/depth-elevation.hbs +0 -31
  69. package/dist/templates/templates/partials/dos-donts.hbs +0 -13
  70. package/dist/templates/templates/partials/layout.hbs +0 -30
  71. package/dist/templates/templates/partials/responsive.hbs +0 -25
  72. package/dist/templates/templates/partials/shadcn-tokens.hbs +0 -64
  73. package/dist/templates/templates/partials/typography.hbs +0 -43
  74. package/dist/templates/templates/partials/visual-theme.hbs +0 -26
@@ -1,675 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/cli/init.ts
4
- import * as p from "@clack/prompts";
5
- import pc from "picocolors";
6
- import {
7
- writeFileSync as writeFileSync2,
8
- readFileSync as readFileSync4,
9
- mkdirSync as mkdirSync2,
10
- existsSync as existsSync2
11
- } from "fs";
12
- import { join as join4, relative, dirname as dirname4 } from "path";
13
- import { fileURLToPath as fileURLToPath3 } from "url";
14
-
15
- // src/core/vocabulary.ts
16
- import { readFileSync } from "fs";
17
- import { fileURLToPath } from "url";
18
- import { dirname, join } from "path";
19
- var cachedVocab = null;
20
- var cachedSynonyms = null;
21
- function dataDir() {
22
- const here = dirname(fileURLToPath(import.meta.url));
23
- const candidates = [
24
- join(here, "..", "data"),
25
- join(here, "..", "..", "data")
26
- ];
27
- for (const c of candidates) {
28
- try {
29
- readFileSync(join(c, "vocabulary.json"), "utf8");
30
- return c;
31
- } catch {
32
- }
33
- }
34
- throw new Error("data/vocabulary.json not found relative to " + here);
35
- }
36
- function loadVocabulary() {
37
- if (cachedVocab) return cachedVocab;
38
- const path = join(dataDir(), "vocabulary.json");
39
- cachedVocab = JSON.parse(readFileSync(path, "utf8"));
40
- return cachedVocab;
41
- }
42
- function loadSynonyms() {
43
- if (cachedSynonyms) return cachedSynonyms;
44
- const path = join(dataDir(), "synonyms.json");
45
- cachedSynonyms = JSON.parse(readFileSync(path, "utf8"));
46
- return cachedSynonyms;
47
- }
48
- var MODIFIER_RE = /\b(primarily|mostly|slightly|very|not)\s+([a-z][a-z-]*)/gi;
49
- function tokenize(description) {
50
- return description.toLowerCase().split(/[^a-z-]+/).filter(Boolean);
51
- }
52
- function extractKeywords(description) {
53
- const vocab = loadVocabulary();
54
- const { map: synonyms } = loadSynonyms();
55
- const lower = description.toLowerCase();
56
- const modifierOverrides = /* @__PURE__ */ new Map();
57
- let match;
58
- const modRe = new RegExp(MODIFIER_RE.source, MODIFIER_RE.flags);
59
- while ((match = modRe.exec(lower)) !== null) {
60
- const modName = match[1];
61
- const target = match[2];
62
- const value = vocab.modifiers[modName];
63
- if (value !== void 0) modifierOverrides.set(target, value);
64
- }
65
- const tokens = tokenize(description);
66
- const seen = /* @__PURE__ */ new Set();
67
- const results = [];
68
- for (const token of tokens) {
69
- if (seen.has(token)) continue;
70
- seen.add(token);
71
- if (vocab.keywords[token]) {
72
- results.push({
73
- keyword: token,
74
- modifier: modifierOverrides.get(token) ?? 1,
75
- matchedAs: "direct"
76
- });
77
- continue;
78
- }
79
- const syn = synonyms[token];
80
- if (syn && vocab.keywords[syn]) {
81
- results.push({
82
- keyword: syn,
83
- modifier: modifierOverrides.get(token) ?? 1,
84
- matchedAs: "synonym",
85
- synonymSource: token
86
- });
87
- }
88
- }
89
- return results;
90
- }
91
- function resolveConflicts(keywords) {
92
- const vocab = loadVocabulary();
93
- const kept = [];
94
- const dropped = [];
95
- for (const kw of keywords) {
96
- const spec = vocab.keywords[kw.keyword];
97
- if (!spec) continue;
98
- const conflictingHere = keywords.filter(
99
- (other) => other.keyword !== kw.keyword && spec.conflicts_with.includes(other.keyword)
100
- );
101
- if (conflictingHere.length === 0) {
102
- kept.push(kw);
103
- continue;
104
- }
105
- const strictlyHigherThanAll = conflictingHere.every(
106
- (c) => kw.modifier > c.modifier + 1e-9
107
- );
108
- if (strictlyHigherThanAll) {
109
- kept.push(kw);
110
- continue;
111
- }
112
- dropped.push({
113
- keyword: kw.keyword,
114
- reason: `conflicts with ${conflictingHere.map((c) => c.keyword).join(",")}`
115
- });
116
- }
117
- const warnings = [];
118
- const keptSet = new Set(kept.map((k) => k.keyword));
119
- const warned = /* @__PURE__ */ new Set();
120
- for (const kw of keywords) {
121
- const spec = vocab.keywords[kw.keyword];
122
- if (!spec) continue;
123
- const conflictingHere = keywords.filter(
124
- (other) => other.keyword !== kw.keyword && spec.conflicts_with.includes(other.keyword)
125
- );
126
- if (conflictingHere.length === 0) continue;
127
- const groupKeys = [kw.keyword, ...conflictingHere.map((c) => c.keyword)];
128
- const groupHasWinner = groupKeys.some((k) => keptSet.has(k));
129
- if (groupHasWinner) continue;
130
- const pairKey = [...new Set(groupKeys)].sort().join(",");
131
- if (warned.has(pairKey)) continue;
132
- warned.add(pairKey);
133
- warnings.push(
134
- `${kw.keyword} \u2194 ${conflictingHere.map((c) => c.keyword).join(",")}: use "primarily <kw>" or "very <kw>" to pick a winner`
135
- );
136
- }
137
- return { kept, dropped, warnings };
138
- }
139
- function clamp(value, lo, hi) {
140
- return Math.max(lo, Math.min(hi, value));
141
- }
142
- function snap(value, spec) {
143
- if (spec.type === "int") return Math.round(value);
144
- if (spec.type === "enum") {
145
- const [lo, hi] = spec.domain;
146
- return Math.abs(value - lo) < Math.abs(value - hi) ? lo : hi;
147
- }
148
- return Number(value.toFixed(3));
149
- }
150
- function buildDeltaSet(description) {
151
- const vocab = loadVocabulary();
152
- const matched = extractKeywords(description);
153
- const { kept, dropped, warnings } = resolveConflicts(matched);
154
- const axes = {};
155
- const voiceHintSet = /* @__PURE__ */ new Set();
156
- for (const kw of kept) {
157
- const spec = vocab.keywords[kw.keyword];
158
- const multiplier = kw.modifier;
159
- for (const hint of spec.voice_hints) voiceHintSet.add(hint);
160
- for (const [axisName, axisDelta] of Object.entries(spec.axes)) {
161
- const bucket = axes[axisName] ?? {
162
- value: 0,
163
- rangeUnion: [axisDelta.range[0], axisDelta.range[1]],
164
- sources: []
165
- };
166
- bucket.value += axisDelta.delta * multiplier;
167
- bucket.rangeUnion = [
168
- Math.min(bucket.rangeUnion[0], axisDelta.range[0]),
169
- Math.max(bucket.rangeUnion[1], axisDelta.range[1])
170
- ];
171
- bucket.sources.push(kw.keyword);
172
- axes[axisName] = bucket;
173
- }
174
- }
175
- for (const [axisName, bucket] of Object.entries(axes)) {
176
- const registry = vocab.axis_registry[axisName];
177
- if (!registry) continue;
178
- let v = clamp(bucket.value, bucket.rangeUnion[0], bucket.rangeUnion[1]);
179
- v = clamp(v, registry.domain[0], registry.domain[1]);
180
- bucket.value = snap(v, registry);
181
- bucket.sources.sort();
182
- }
183
- return {
184
- axes,
185
- voiceHints: [...voiceHintSet],
186
- unresolved: [],
187
- warnings,
188
- droppedKeywords: dropped,
189
- matchedKeywords: kept
190
- };
191
- }
192
-
193
- // src/core/recommend.ts
194
- import { readFileSync as readFileSync2 } from "fs";
195
- import { fileURLToPath as fileURLToPath2 } from "url";
196
- import { dirname as dirname2, join as join2 } from "path";
197
- var CATEGORY_HINTS = {
198
- Consumer: [
199
- "marketplace",
200
- "shopping",
201
- "ecommerce",
202
- "consumer",
203
- "b2c",
204
- "retail",
205
- "subscription",
206
- "family",
207
- "families",
208
- "meal",
209
- "meals",
210
- "meal-kit",
211
- "food",
212
- "travel",
213
- "social",
214
- "community",
215
- "buyer",
216
- "seller",
217
- "parents",
218
- "kids",
219
- "lifestyle",
220
- "recipe",
221
- "recipes"
222
- ],
223
- Fintech: [
224
- "fintech",
225
- "banking",
226
- "bank",
227
- "payment",
228
- "payments",
229
- "crypto",
230
- "trading",
231
- "wallet",
232
- "invest",
233
- "investing",
234
- "money",
235
- "finance",
236
- "financial",
237
- "lending",
238
- "remittance",
239
- "tax"
240
- ],
241
- "Developer Tools": [
242
- "developer",
243
- "devtool",
244
- "devtools",
245
- "dev-tool",
246
- "deploy",
247
- "deployment",
248
- "build",
249
- "ci",
250
- "cd",
251
- "cli",
252
- "sdk",
253
- "editor",
254
- "ide",
255
- "engineering",
256
- "compiler",
257
- "runtime"
258
- ],
259
- AI: [
260
- "ai",
261
- "ml",
262
- "llm",
263
- "agent",
264
- "agents",
265
- "model",
266
- "models",
267
- "inference",
268
- "gpt",
269
- "chatbot",
270
- "rag",
271
- "embedding",
272
- "embeddings",
273
- "mcp"
274
- ],
275
- "Design Tools": [
276
- "design",
277
- "design-tool",
278
- "whiteboard",
279
- "prototype",
280
- "prototyping",
281
- "wireframe",
282
- "wireframes",
283
- "mockup",
284
- "mockups",
285
- "figma-like",
286
- "illustration",
287
- "canvas"
288
- ],
289
- Productivity: [
290
- "saas",
291
- "workspace",
292
- "team",
293
- "teams",
294
- "project-management",
295
- "enterprise",
296
- "b2b",
297
- "crm",
298
- "docs",
299
- "wiki",
300
- "collaboration",
301
- "kanban",
302
- "scheduling",
303
- "meetings"
304
- ],
305
- Backend: [
306
- "backend",
307
- "database",
308
- "db",
309
- "api",
310
- "apis",
311
- "observability",
312
- "monitoring",
313
- "logging",
314
- "analytics",
315
- "pipeline",
316
- "data-pipeline",
317
- "streaming",
318
- "queue",
319
- "cache"
320
- ],
321
- Automotive: [
322
- "car",
323
- "cars",
324
- "vehicle",
325
- "vehicles",
326
- "auto",
327
- "automotive",
328
- "driving",
329
- "ev",
330
- "electric-vehicle"
331
- ],
332
- Marketing: [
333
- "marketing",
334
- "seo",
335
- "campaign",
336
- "campaigns",
337
- "newsletter",
338
- "email-marketing",
339
- "attribution"
340
- ]
341
- };
342
- function matchedCategoriesFor(queryTokens, queryStems) {
343
- const out = /* @__PURE__ */ new Set();
344
- for (const [category, hints] of Object.entries(CATEGORY_HINTS)) {
345
- for (const hint of hints) {
346
- if (queryTokens.has(hint) || queryStems.has(stem(hint))) {
347
- out.add(category);
348
- break;
349
- }
350
- }
351
- }
352
- return out;
353
- }
354
- var cachedTags = null;
355
- function stem(s) {
356
- let out = s;
357
- for (const suffix of ["ing", "ed", "ly", "es", "s"]) {
358
- if (out.length - suffix.length >= 3 && out.endsWith(suffix)) {
359
- out = out.slice(0, -suffix.length);
360
- break;
361
- }
362
- }
363
- return out;
364
- }
365
- function tagsFilePath() {
366
- const here = dirname2(fileURLToPath2(import.meta.url));
367
- const candidates = [
368
- join2(here, "..", "data", "reference-tags.md"),
369
- join2(here, "..", "..", "data", "reference-tags.md")
370
- ];
371
- for (const c of candidates) {
372
- try {
373
- readFileSync2(c, "utf8");
374
- return c;
375
- } catch {
376
- }
377
- }
378
- throw new Error("data/reference-tags.md not found");
379
- }
380
- var ROW_RE = /^\|\s*([a-z0-9._-]+)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|$/i;
381
- function loadReferenceTags() {
382
- if (cachedTags) return cachedTags;
383
- const raw = readFileSync2(tagsFilePath(), "utf8");
384
- const rows = [];
385
- for (const line of raw.split("\n")) {
386
- const m = ROW_RE.exec(line);
387
- if (!m) continue;
388
- const [, id, color, category, keywordsRaw] = m;
389
- if (id === "id") continue;
390
- if (id.startsWith("---")) continue;
391
- rows.push({
392
- id: id.trim(),
393
- color: color.trim(),
394
- category: category.trim(),
395
- keywords: keywordsRaw.split(",").map((k) => k.trim().toLowerCase()).filter(Boolean)
396
- });
397
- }
398
- cachedTags = rows;
399
- return rows;
400
- }
401
- function recommend(description, opts = {}) {
402
- const topK = opts.topK ?? 5;
403
- const diversityByCategory = opts.diversityByCategory ?? true;
404
- const tags = loadReferenceTags();
405
- const rawTokens = [
406
- ...tokenize(description),
407
- ...description.toLowerCase().split(/[^a-z0-9-]+/).filter(Boolean)
408
- ];
409
- const queryTokens = new Set(rawTokens);
410
- const queryStems = new Set(rawTokens.map(stem));
411
- const matchedCategories = matchedCategoriesFor(queryTokens, queryStems);
412
- const tagMatchByRef = tags.map(
413
- (t) => t.keywords.filter(
414
- (kw) => queryTokens.has(kw) || queryStems.has(stem(kw))
415
- )
416
- );
417
- const totalTagMatches = tagMatchByRef.reduce((a, m) => a + m.length, 0);
418
- const scored = tags.map((t, i) => {
419
- const matched = tagMatchByRef[i];
420
- const tagScore = matched.length;
421
- const categoryHit = matchedCategories.has(t.category);
422
- const categoryBonus = categoryHit && (tagScore > 0 || totalTagMatches === 0) ? 0.5 : 0;
423
- const score = tagScore + categoryBonus;
424
- const ratio = matched.length / Math.max(1, t.keywords.length);
425
- return {
426
- id: t.id,
427
- category: t.category,
428
- color: t.color,
429
- keywords: t.keywords,
430
- score,
431
- matchedKeywords: matched,
432
- matchedCategories: categoryHit ? [t.category] : [],
433
- _ratio: ratio
434
- };
435
- });
436
- scored.sort(
437
- (a, b) => b.score - a.score || b._ratio - a._ratio || a.id.localeCompare(b.id)
438
- );
439
- const stripRatio = (s) => s.map(({ _ratio, ...rest }) => {
440
- void _ratio;
441
- return rest;
442
- });
443
- if (!diversityByCategory) return stripRatio(scored.slice(0, topK));
444
- const picked = stripRatio(scored).slice(0, 0);
445
- const pickedSet = /* @__PURE__ */ new Set();
446
- const usedCategories = /* @__PURE__ */ new Set();
447
- const allHits = stripRatio(scored);
448
- for (const hit of allHits) {
449
- if (picked.length >= topK) break;
450
- if (usedCategories.has(hit.category)) continue;
451
- picked.push(hit);
452
- pickedSet.add(hit.id);
453
- usedCategories.add(hit.category);
454
- }
455
- for (const hit of allHits) {
456
- if (picked.length >= topK) break;
457
- if (pickedSet.has(hit.id)) continue;
458
- picked.push(hit);
459
- pickedSet.add(hit.id);
460
- }
461
- return picked;
462
- }
463
-
464
- // src/core/init-deprecate.ts
465
- import {
466
- existsSync,
467
- readFileSync as readFileSync3,
468
- writeFileSync,
469
- unlinkSync,
470
- mkdirSync
471
- } from "fs";
472
- import { dirname as dirname3, join as join3 } from "path";
473
- function deprecationHeader(opts) {
474
- const now = (opts.now ?? /* @__PURE__ */ new Date()).toISOString();
475
- const lines = [
476
- "<!--",
477
- "omd:deprecated",
478
- ` deprecated_at: ${now}`
479
- ];
480
- if (opts.previousReference)
481
- lines.push(` previous_reference: ${opts.previousReference}`);
482
- lines.push(` new_reference: ${opts.newReference}`);
483
- if (opts.preferencesReplayed !== void 0)
484
- lines.push(` preferences_replayed: ${opts.preferencesReplayed}`);
485
- if (opts.preferencesOrphaned !== void 0)
486
- lines.push(` preferences_orphaned: ${opts.preferencesOrphaned}`);
487
- if (opts.orphanFile) lines.push(` orphan_file: ${opts.orphanFile}`);
488
- lines.push(` reason: ${opts.reason}`);
489
- lines.push("-->", "", "");
490
- return lines.join("\n");
491
- }
492
- function deprecateDesignMd(opts) {
493
- const from = join3(opts.projectRoot, "DESIGN.md");
494
- const baseTo = join3(opts.projectRoot, "DESIGN_DEPRECATED.md");
495
- if (!existsSync(from)) {
496
- return { renamed: false, from, to: baseTo };
497
- }
498
- let target = baseTo;
499
- if (existsSync(baseTo)) {
500
- const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
501
- target = join3(opts.projectRoot, `DESIGN_DEPRECATED.${ts}.md`);
502
- }
503
- mkdirSync(dirname3(target), { recursive: true });
504
- const prior = readFileSync3(from, "utf8");
505
- writeFileSync(target, deprecationHeader(opts) + prior, "utf8");
506
- unlinkSync(from);
507
- return { renamed: true, from, to: target };
508
- }
509
-
510
- // src/cli/init.ts
511
- function runInitRecommend(opts) {
512
- const trimmed = opts.description.trim();
513
- if (!trimmed) {
514
- if (opts.json) {
515
- process.stdout.write(
516
- JSON.stringify({ error: "description is empty" }, null, 2) + "\n"
517
- );
518
- } else {
519
- console.error(
520
- pc.red(
521
- 'omd init recommend: description is required. Try a few keywords like "warm fintech dashboard" or "minimal dev tool".'
522
- )
523
- );
524
- }
525
- return 1;
526
- }
527
- const hits = recommend(opts.description, { topK: opts.topK ?? 5 });
528
- const delta = buildDeltaSet(opts.description);
529
- if (opts.json) {
530
- process.stdout.write(
531
- JSON.stringify(
532
- {
533
- description: opts.description,
534
- recommendations: hits,
535
- delta_set: delta
536
- },
537
- null,
538
- 2
539
- )
540
- );
541
- process.stdout.write("\n");
542
- return 0;
543
- }
544
- p.intro(pc.bold("omd init \u2014 recommend"));
545
- p.log.message(pc.dim(`Query: "${opts.description}"
546
- `));
547
- if (delta.matchedKeywords.length > 0) {
548
- p.log.message(
549
- pc.bold("Matched keywords: ") + delta.matchedKeywords.map((k) => pc.cyan(k.keyword) + pc.dim(` (${k.modifier.toFixed(2)})`)).join(", ")
550
- );
551
- } else {
552
- p.log.warn(
553
- "No vocabulary keywords matched. Recommendations will rank by tag overlap only \u2014 try adding tone words (warm / minimal / playful / premium / dense / casual / formal / etc.) for stronger matches. See https://github.com/kwakseongjae/oh-my-design#vocabulary for the full list."
554
- );
555
- }
556
- if (delta.warnings.length > 0) {
557
- for (const w of delta.warnings) p.log.warn(w);
558
- }
559
- p.log.message(pc.bold("\nTop references:"));
560
- for (const [i, hit] of hits.entries()) {
561
- const scoreStr = pc.dim(`[${hit.score.toFixed(2)}]`);
562
- const matched = hit.matchedKeywords.length > 0 ? pc.green(hit.matchedKeywords.join(", ")) : pc.dim("(no direct tag match)");
563
- p.log.message(
564
- ` ${i + 1}. ${pc.bold(hit.id.padEnd(14))} ${scoreStr} ${pc.dim(hit.category.padEnd(14))} ${matched}`
565
- );
566
- }
567
- p.outro(
568
- pc.dim('Next: `omd init prepare --ref <id> --description "..."` to stage.')
569
- );
570
- return 0;
571
- }
572
- function runInitPrepare(opts) {
573
- const projectRoot = opts.dir ?? process.cwd();
574
- const relRoot = relative(process.cwd(), projectRoot) || ".";
575
- if (!opts.description?.trim()) {
576
- console.error(
577
- pc.red(
578
- "omd init prepare: --description is required and cannot be empty."
579
- )
580
- );
581
- return 1;
582
- }
583
- const refPath = findReferencePath(opts.ref);
584
- if (!refPath) {
585
- console.error(
586
- pc.red(`omd init prepare: reference not found: ${opts.ref}`)
587
- );
588
- console.error(
589
- pc.dim(" Run `omd reference list` to see all available references.")
590
- );
591
- return 1;
592
- }
593
- const referenceMd = readFileSync4(refPath, "utf8");
594
- const delta = buildDeltaSet(opts.description);
595
- const deprecate = deprecateDesignMd({
596
- projectRoot,
597
- newReference: opts.ref,
598
- reason: opts.reason ?? "user-initiated omd init"
599
- });
600
- const contextPath = join4(projectRoot, ".omd", "init-context.json");
601
- mkdirSync2(join4(projectRoot, ".omd"), { recursive: true });
602
- const context = {
603
- schema: "omd.init-context/v1",
604
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
605
- reference_id: opts.ref,
606
- description: opts.description,
607
- delta_set: delta,
608
- deprecated_from: deprecate.renamed ? deprecate.to : null
609
- };
610
- writeFileSync2(contextPath, JSON.stringify(context, null, 2) + "\n", "utf8");
611
- if (opts.json) {
612
- process.stdout.write(
613
- JSON.stringify(
614
- {
615
- project_root: projectRoot,
616
- reference_path: refPath,
617
- context_path: contextPath,
618
- deprecated_from: deprecate.renamed ? deprecate.to : null,
619
- reference_md: referenceMd,
620
- delta_set: delta
621
- },
622
- null,
623
- 2
624
- )
625
- );
626
- process.stdout.write("\n");
627
- return 0;
628
- }
629
- p.intro(pc.bold("omd init \u2014 prepare") + pc.dim(` (${relRoot})`));
630
- p.log.message(`Reference: ${pc.cyan(opts.ref)}`);
631
- p.log.message(`Description: ${pc.dim(opts.description)}`);
632
- if (deprecate.renamed) {
633
- p.log.warn(
634
- `Existing DESIGN.md renamed \u2192 ${relative(projectRoot, deprecate.to)}`
635
- );
636
- }
637
- p.log.success(
638
- `Context staged \u2192 ${relative(projectRoot, contextPath)}`
639
- );
640
- if (!skillsInstalled(projectRoot)) {
641
- p.log.warn(
642
- "No omd:* skills installed in this project \u2014 your agent won't know how to consume this context. Run `npx oh-my-design-cli install-skills` first."
643
- );
644
- }
645
- p.outro(
646
- pc.dim(
647
- "Next: have your agent (Claude Code / Codex / OpenCode) run the `omd:init` skill to generate DESIGN.md from this context."
648
- )
649
- );
650
- return 0;
651
- }
652
- function skillsInstalled(projectRoot) {
653
- return existsSync2(join4(projectRoot, ".claude", "skills", "omd-init", "SKILL.md")) || existsSync2(join4(projectRoot, ".codex", "skills", "omd-init", "SKILL.md")) || existsSync2(join4(projectRoot, ".opencode", "agents", "omd-init.md"));
654
- }
655
- function findReferencePath(refId) {
656
- const root = findRepoRoot();
657
- if (!root) return null;
658
- const path = join4(root, "references", refId, "DESIGN.md");
659
- return existsSync2(path) ? path : null;
660
- }
661
- function findRepoRoot() {
662
- let cur = dirname4(fileURLToPath3(import.meta.url));
663
- for (let i = 0; i < 8; i++) {
664
- if (existsSync2(join4(cur, "references"))) return cur;
665
- const parent = dirname4(cur);
666
- if (parent === cur) break;
667
- cur = parent;
668
- }
669
- return null;
670
- }
671
- export {
672
- runInitPrepare,
673
- runInitRecommend
674
- };
675
- //# sourceMappingURL=init-UMM4XIV5.js.map