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.
- package/.claude/hooks/post-edit-watch.cjs +99 -0
- package/.claude/hooks/session-end-foldin.cjs +96 -0
- package/.claude/hooks/session-state-loader.cjs +64 -0
- package/.claude/hooks/skill-activation.cjs +73 -0
- package/.claude/settings.json +55 -0
- package/.claude/skills/skill-rules.json +87 -0
- package/AGENTS.md +111 -0
- package/README.md +75 -202
- package/agents/AGENT.md +53 -0
- package/agents/omd-3d-blender.md +269 -0
- package/agents/omd-a11y-auditor.md +97 -0
- package/agents/omd-asset-curator.md +260 -0
- package/agents/omd-critic.md +181 -0
- package/agents/omd-master.md +548 -0
- package/agents/omd-microcopy.md +63 -0
- package/agents/omd-persona-tester.md +118 -0
- package/agents/omd-ui-junior.md +129 -0
- package/agents/omd-ux-engineer.md +265 -0
- package/agents/omd-ux-researcher.md +62 -0
- package/agents/omd-ux-writer.md +181 -0
- package/data/opt-out-corpus.json +141 -0
- package/data/reference-fingerprints.json +1495 -0
- package/dist/bin/oh-my-design.js +3 -818
- package/dist/bin/oh-my-design.js.map +1 -1
- package/dist/install-skills-SVIYKXOE.js +442 -0
- package/dist/install-skills-SVIYKXOE.js.map +1 -0
- package/package.json +23 -23
- package/scripts/context.cjs +91 -0
- package/scripts/postinstall.cjs +54 -0
- package/skills/omd-apply/SKILL.md +64 -53
- package/skills/omd-harness/SKILL.md +271 -0
- package/skills/omd-learn/SKILL.md +55 -35
- package/skills/omd-remember/SKILL.md +93 -15
- package/skills/omd-sync/SKILL.md +140 -16
- package/dist/chunk-6YNSV3VY.js +0 -35
- package/dist/chunk-6YNSV3VY.js.map +0 -1
- package/dist/chunk-MHFYGZSO.js +0 -337
- package/dist/chunk-MHFYGZSO.js.map +0 -1
- package/dist/chunk-N2JG6N4Q.js +0 -264
- package/dist/chunk-N2JG6N4Q.js.map +0 -1
- package/dist/chunk-OOQQEUGX.js +0 -46
- package/dist/chunk-OOQQEUGX.js.map +0 -1
- package/dist/chunk-OR5DHENY.js +0 -250
- package/dist/chunk-OR5DHENY.js.map +0 -1
- package/dist/customizer-CM76752R.js +0 -8
- package/dist/customizer-CM76752R.js.map +0 -1
- package/dist/index.d.ts +0 -559
- package/dist/index.js +0 -3113
- package/dist/index.js.map +0 -1
- package/dist/init-UMM4XIV5.js +0 -675
- package/dist/init-UMM4XIV5.js.map +0 -1
- package/dist/install-skills-CM6VXFZJ.js +0 -152
- package/dist/install-skills-CM6VXFZJ.js.map +0 -1
- package/dist/learn-33LHKEJA.js +0 -140
- package/dist/learn-33LHKEJA.js.map +0 -1
- package/dist/reference-YMNAOXJQ.js +0 -47
- package/dist/reference-YMNAOXJQ.js.map +0 -1
- package/dist/reference-parser-TM3CJPNE.js +0 -10
- package/dist/reference-parser-TM3CJPNE.js.map +0 -1
- package/dist/remember-UAFA5B2O.js +0 -78
- package/dist/remember-UAFA5B2O.js.map +0 -1
- package/dist/sync-FDYRKNFE.js +0 -417
- package/dist/sync-FDYRKNFE.js.map +0 -1
- package/dist/templates/templates/design-md.hbs +0 -44
- package/dist/templates/templates/partials/agent-prompt-guide.hbs +0 -28
- package/dist/templates/templates/partials/color-palette.hbs +0 -49
- package/dist/templates/templates/partials/component-stylings.hbs +0 -28
- package/dist/templates/templates/partials/depth-elevation.hbs +0 -31
- package/dist/templates/templates/partials/dos-donts.hbs +0 -13
- package/dist/templates/templates/partials/layout.hbs +0 -30
- package/dist/templates/templates/partials/responsive.hbs +0 -25
- package/dist/templates/templates/partials/shadcn-tokens.hbs +0 -64
- package/dist/templates/templates/partials/typography.hbs +0 -43
- package/dist/templates/templates/partials/visual-theme.hbs +0 -26
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/init.ts","../src/core/vocabulary.ts","../src/core/recommend.ts","../src/core/init-deprecate.ts"],"sourcesContent":["import * as p from '@clack/prompts';\nimport pc from 'picocolors';\nimport {\n writeFileSync,\n readFileSync,\n mkdirSync,\n existsSync,\n} from 'node:fs';\nimport { join, relative, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { buildDeltaSet } from '../core/vocabulary.js';\nimport { recommend } from '../core/recommend.js';\nimport { deprecateDesignMd } from '../core/init-deprecate.js';\n\nexport interface InitRecommendOptions {\n description: string;\n topK?: number;\n json?: boolean;\n}\n\nexport interface InitPrepareOptions {\n dir?: string;\n ref: string;\n description: string;\n reason?: string;\n json?: boolean;\n}\n\nexport function runInitRecommend(opts: InitRecommendOptions): number {\n const trimmed = opts.description.trim();\n if (!trimmed) {\n if (opts.json) {\n process.stdout.write(\n JSON.stringify({ error: 'description is empty' }, null, 2) + '\\n'\n );\n } else {\n console.error(\n pc.red(\n 'omd init recommend: description is required. Try a few keywords like \"warm fintech dashboard\" or \"minimal dev tool\".'\n )\n );\n }\n return 1;\n }\n\n const hits = recommend(opts.description, { topK: opts.topK ?? 5 });\n const delta = buildDeltaSet(opts.description);\n\n if (opts.json) {\n process.stdout.write(\n JSON.stringify(\n {\n description: opts.description,\n recommendations: hits,\n delta_set: delta,\n },\n null,\n 2\n )\n );\n process.stdout.write('\\n');\n return 0;\n }\n\n p.intro(pc.bold('omd init — recommend'));\n p.log.message(pc.dim(`Query: \"${opts.description}\"\\n`));\n\n if (delta.matchedKeywords.length > 0) {\n p.log.message(\n pc.bold('Matched keywords: ') +\n delta.matchedKeywords\n .map((k) => pc.cyan(k.keyword) + pc.dim(` (${k.modifier.toFixed(2)})`))\n .join(', ')\n );\n } else {\n p.log.warn(\n 'No vocabulary keywords matched. Recommendations will rank by tag overlap only — 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.'\n );\n }\n if (delta.warnings.length > 0) {\n for (const w of delta.warnings) p.log.warn(w);\n }\n\n p.log.message(pc.bold('\\nTop references:'));\n for (const [i, hit] of hits.entries()) {\n const scoreStr = pc.dim(`[${hit.score.toFixed(2)}]`);\n const matched = hit.matchedKeywords.length > 0\n ? pc.green(hit.matchedKeywords.join(', '))\n : pc.dim('(no direct tag match)');\n p.log.message(\n ` ${i + 1}. ${pc.bold(hit.id.padEnd(14))} ${scoreStr} ${pc.dim(hit.category.padEnd(14))} ${matched}`\n );\n }\n\n p.outro(\n pc.dim('Next: `omd init prepare --ref <id> --description \"...\"` to stage.')\n );\n return 0;\n}\n\nexport function runInitPrepare(opts: InitPrepareOptions): number {\n const projectRoot = opts.dir ?? process.cwd();\n const relRoot = relative(process.cwd(), projectRoot) || '.';\n\n if (!opts.description?.trim()) {\n console.error(\n pc.red(\n 'omd init prepare: --description is required and cannot be empty.'\n )\n );\n return 1;\n }\n\n const refPath = findReferencePath(opts.ref);\n if (!refPath) {\n console.error(\n pc.red(`omd init prepare: reference not found: ${opts.ref}`)\n );\n console.error(\n pc.dim(' Run `omd reference list` to see all available references.')\n );\n return 1;\n }\n\n const referenceMd = readFileSync(refPath, 'utf8');\n const delta = buildDeltaSet(opts.description);\n\n // Handle existing DESIGN.md\n const deprecate = deprecateDesignMd({\n projectRoot,\n newReference: opts.ref,\n reason: opts.reason ?? 'user-initiated omd init',\n });\n\n // Write init-context.json that the omd:init skill consumes.\n // Note: we deliberately do NOT persist reference_path (absolute paths are\n // fragile across machines / npm reinstalls). The skill reads reference_md\n // directly from this command's --json output, and can re-fetch reference\n // content via `omd reference show <id>` if needed.\n const contextPath = join(projectRoot, '.omd', 'init-context.json');\n mkdirSync(join(projectRoot, '.omd'), { recursive: true });\n const context = {\n schema: 'omd.init-context/v1',\n created_at: new Date().toISOString(),\n reference_id: opts.ref,\n description: opts.description,\n delta_set: delta,\n deprecated_from: deprecate.renamed ? deprecate.to : null,\n };\n writeFileSync(contextPath, JSON.stringify(context, null, 2) + '\\n', 'utf8');\n\n if (opts.json) {\n process.stdout.write(\n JSON.stringify(\n {\n project_root: projectRoot,\n reference_path: refPath,\n context_path: contextPath,\n deprecated_from: deprecate.renamed ? deprecate.to : null,\n reference_md: referenceMd,\n delta_set: delta,\n },\n null,\n 2\n )\n );\n process.stdout.write('\\n');\n return 0;\n }\n\n p.intro(pc.bold('omd init — prepare') + pc.dim(` (${relRoot})`));\n p.log.message(`Reference: ${pc.cyan(opts.ref)}`);\n p.log.message(`Description: ${pc.dim(opts.description)}`);\n if (deprecate.renamed) {\n p.log.warn(\n `Existing DESIGN.md renamed → ${relative(projectRoot, deprecate.to)}`\n );\n }\n p.log.success(\n `Context staged → ${relative(projectRoot, contextPath)}`\n );\n\n if (!skillsInstalled(projectRoot)) {\n p.log.warn(\n 'No omd:* skills installed in this project — your agent won\\'t know how to consume this context. Run `npx oh-my-design-cli install-skills` first.'\n );\n }\n\n p.outro(\n pc.dim(\n 'Next: have your agent (Claude Code / Codex / OpenCode) run the `omd:init` skill to generate DESIGN.md from this context.'\n )\n );\n return 0;\n}\n\nfunction skillsInstalled(projectRoot: string): boolean {\n return (\n existsSync(join(projectRoot, '.claude', 'skills', 'omd-init', 'SKILL.md')) ||\n existsSync(join(projectRoot, '.codex', 'skills', 'omd-init', 'SKILL.md')) ||\n existsSync(join(projectRoot, '.opencode', 'agents', 'omd-init.md'))\n );\n}\n\nfunction findReferencePath(refId: string): string | null {\n const root = findRepoRoot();\n if (!root) return null;\n const path = join(root, 'references', refId, 'DESIGN.md');\n return existsSync(path) ? path : null;\n}\n\nfunction findRepoRoot(): string | null {\n let cur = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 8; i++) {\n if (existsSync(join(cur, 'references'))) return cur;\n const parent = dirname(cur);\n if (parent === cur) break;\n cur = parent;\n }\n return null;\n}\n","import { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nexport interface AxisSpec {\n type: 'int' | 'float' | 'enum';\n domain: [number, number];\n unit?: string;\n step?: number;\n}\n\nexport interface KeywordAxes {\n [axis: string]: { delta: number; range: [number, number] };\n}\n\nexport interface KeywordSpec {\n group: 'tone' | 'density' | 'formality' | 'domain';\n axes: KeywordAxes;\n conflicts_with: string[];\n voice_hints: string[];\n coupled_axes?: string[];\n sources?: string[];\n}\n\nexport interface Vocabulary {\n version: number;\n generated_at: string;\n axis_registry: Record<string, AxisSpec>;\n modifiers: Record<string, number>;\n keywords: Record<string, KeywordSpec>;\n}\n\nexport interface SynonymsFile {\n version: number;\n map: Record<string, string>;\n}\n\nlet cachedVocab: Vocabulary | null = null;\nlet cachedSynonyms: SynonymsFile | null = null;\n\nfunction dataDir(): string {\n const here = dirname(fileURLToPath(import.meta.url));\n // When built (dist/) we are at dist/*.js → data/ is ../data\n // When running from src/ via tsx we are at src/core/ → data/ is ../../data\n const candidates = [\n join(here, '..', 'data'),\n join(here, '..', '..', 'data'),\n ];\n for (const c of candidates) {\n try {\n readFileSync(join(c, 'vocabulary.json'), 'utf8');\n return c;\n } catch {\n // continue\n }\n }\n throw new Error('data/vocabulary.json not found relative to ' + here);\n}\n\nexport function loadVocabulary(): Vocabulary {\n if (cachedVocab) return cachedVocab;\n const path = join(dataDir(), 'vocabulary.json');\n cachedVocab = JSON.parse(readFileSync(path, 'utf8')) as Vocabulary;\n return cachedVocab;\n}\n\nexport function loadSynonyms(): SynonymsFile {\n if (cachedSynonyms) return cachedSynonyms;\n const path = join(dataDir(), 'synonyms.json');\n cachedSynonyms = JSON.parse(readFileSync(path, 'utf8')) as SynonymsFile;\n return cachedSynonyms;\n}\n\nexport interface ResolvedKeyword {\n keyword: string;\n modifier: number;\n matchedAs: 'direct' | 'synonym';\n synonymSource?: string;\n}\n\nconst MODIFIER_RE = /\\b(primarily|mostly|slightly|very|not)\\s+([a-z][a-z-]*)/gi;\n\nexport function tokenize(description: string): string[] {\n return description\n .toLowerCase()\n .split(/[^a-z-]+/)\n .filter(Boolean);\n}\n\nexport function extractKeywords(description: string): ResolvedKeyword[] {\n const vocab = loadVocabulary();\n const { map: synonyms } = loadSynonyms();\n\n const lower = description.toLowerCase();\n const modifierOverrides = new Map<string, number>();\n let match: RegExpExecArray | null;\n const modRe = new RegExp(MODIFIER_RE.source, MODIFIER_RE.flags);\n while ((match = modRe.exec(lower)) !== null) {\n const modName = match[1];\n const target = match[2];\n const value = vocab.modifiers[modName];\n if (value !== undefined) modifierOverrides.set(target, value);\n }\n\n const tokens = tokenize(description);\n const seen = new Set<string>();\n const results: ResolvedKeyword[] = [];\n\n for (const token of tokens) {\n if (seen.has(token)) continue;\n seen.add(token);\n\n if (vocab.keywords[token]) {\n results.push({\n keyword: token,\n modifier: modifierOverrides.get(token) ?? 1.0,\n matchedAs: 'direct',\n });\n continue;\n }\n\n const syn = synonyms[token];\n if (syn && vocab.keywords[syn]) {\n results.push({\n keyword: syn,\n modifier: modifierOverrides.get(token) ?? 1.0,\n matchedAs: 'synonym',\n synonymSource: token,\n });\n }\n }\n\n return results;\n}\n\nexport interface ConflictResolution {\n kept: ResolvedKeyword[];\n dropped: Array<{ keyword: string; reason: string }>;\n warnings: string[];\n}\n\nexport function resolveConflicts(\n keywords: ResolvedKeyword[]\n): ConflictResolution {\n const vocab = loadVocabulary();\n const kept: ResolvedKeyword[] = [];\n const dropped: Array<{ keyword: string; reason: string }> = [];\n\n // Classify each keyword: kept if strictly higher modifier than any conflicter, else dropped.\n for (const kw of keywords) {\n const spec = vocab.keywords[kw.keyword];\n if (!spec) continue;\n\n const conflictingHere = keywords.filter(\n (other) =>\n other.keyword !== kw.keyword &&\n spec.conflicts_with.includes(other.keyword)\n );\n\n if (conflictingHere.length === 0) {\n kept.push(kw);\n continue;\n }\n\n const strictlyHigherThanAll = conflictingHere.every(\n (c) => kw.modifier > c.modifier + 1e-9\n );\n if (strictlyHigherThanAll) {\n kept.push(kw);\n continue;\n }\n\n dropped.push({\n keyword: kw.keyword,\n reason: `conflicts with ${conflictingHere.map((c) => c.keyword).join(',')}`,\n });\n }\n\n // Emit warnings ONLY for conflict groups where no member was kept\n // (genuine tie — user should disambiguate).\n const warnings: string[] = [];\n const keptSet = new Set(kept.map((k) => k.keyword));\n const warned = new Set<string>();\n for (const kw of keywords) {\n const spec = vocab.keywords[kw.keyword];\n if (!spec) continue;\n const conflictingHere = keywords.filter(\n (other) =>\n other.keyword !== kw.keyword &&\n spec.conflicts_with.includes(other.keyword)\n );\n if (conflictingHere.length === 0) continue;\n const groupKeys = [kw.keyword, ...conflictingHere.map((c) => c.keyword)];\n const groupHasWinner = groupKeys.some((k) => keptSet.has(k));\n if (groupHasWinner) continue;\n const pairKey = [...new Set(groupKeys)].sort().join(',');\n if (warned.has(pairKey)) continue;\n warned.add(pairKey);\n warnings.push(\n `${kw.keyword} ↔ ${conflictingHere.map((c) => c.keyword).join(',')}: use \"primarily <kw>\" or \"very <kw>\" to pick a winner`\n );\n }\n\n return { kept, dropped, warnings };\n}\n\nexport interface DeltaSet {\n axes: Record<string, { value: number; rangeUnion: [number, number]; sources: string[] }>;\n voiceHints: string[];\n unresolved: string[];\n warnings: string[];\n droppedKeywords: Array<{ keyword: string; reason: string }>;\n matchedKeywords: ResolvedKeyword[];\n}\n\nfunction clamp(value: number, lo: number, hi: number): number {\n return Math.max(lo, Math.min(hi, value));\n}\n\nfunction snap(value: number, spec: AxisSpec): number {\n if (spec.type === 'int') return Math.round(value);\n if (spec.type === 'enum') {\n const [lo, hi] = spec.domain;\n return Math.abs(value - lo) < Math.abs(value - hi) ? lo : hi;\n }\n return Number(value.toFixed(3));\n}\n\nexport function buildDeltaSet(description: string): DeltaSet {\n const vocab = loadVocabulary();\n const matched = extractKeywords(description);\n const { kept, dropped, warnings } = resolveConflicts(matched);\n\n const axes: DeltaSet['axes'] = {};\n const voiceHintSet = new Set<string>();\n\n for (const kw of kept) {\n const spec = vocab.keywords[kw.keyword];\n const multiplier = kw.modifier;\n\n for (const hint of spec.voice_hints) voiceHintSet.add(hint);\n\n for (const [axisName, axisDelta] of Object.entries(spec.axes)) {\n const bucket = axes[axisName] ?? {\n value: 0,\n rangeUnion: [axisDelta.range[0], axisDelta.range[1]] as [number, number],\n sources: [] as string[],\n };\n bucket.value += axisDelta.delta * multiplier;\n bucket.rangeUnion = [\n Math.min(bucket.rangeUnion[0], axisDelta.range[0]),\n Math.max(bucket.rangeUnion[1], axisDelta.range[1]),\n ];\n bucket.sources.push(kw.keyword);\n axes[axisName] = bucket;\n }\n }\n\n for (const [axisName, bucket] of Object.entries(axes)) {\n const registry = vocab.axis_registry[axisName];\n if (!registry) continue;\n let v = clamp(bucket.value, bucket.rangeUnion[0], bucket.rangeUnion[1]);\n v = clamp(v, registry.domain[0], registry.domain[1]);\n bucket.value = snap(v, registry);\n bucket.sources.sort();\n }\n\n return {\n axes,\n voiceHints: [...voiceHintSet],\n unresolved: [],\n warnings,\n droppedKeywords: dropped,\n matchedKeywords: kept,\n };\n}\n\nexport function listKeywords(): string[] {\n return Object.keys(loadVocabulary().keywords).sort();\n}\n","import { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\nimport { tokenize } from './vocabulary.js';\n\nexport interface ReferenceTag {\n id: string;\n color: string;\n category: string;\n keywords: string[];\n}\n\nexport interface RecommendHit {\n id: string;\n category: string;\n color: string;\n keywords: string[];\n score: number;\n matchedKeywords: string[];\n matchedCategories: string[];\n}\n\nconst CATEGORY_HINTS: Record<string, string[]> = {\n Consumer: [\n 'marketplace',\n 'shopping',\n 'ecommerce',\n 'consumer',\n 'b2c',\n 'retail',\n 'subscription',\n 'family',\n 'families',\n 'meal',\n 'meals',\n 'meal-kit',\n 'food',\n 'travel',\n 'social',\n 'community',\n 'buyer',\n 'seller',\n 'parents',\n 'kids',\n 'lifestyle',\n 'recipe',\n 'recipes',\n ],\n Fintech: [\n 'fintech',\n 'banking',\n 'bank',\n 'payment',\n 'payments',\n 'crypto',\n 'trading',\n 'wallet',\n 'invest',\n 'investing',\n 'money',\n 'finance',\n 'financial',\n 'lending',\n 'remittance',\n 'tax',\n ],\n 'Developer Tools': [\n 'developer',\n 'devtool',\n 'devtools',\n 'dev-tool',\n 'deploy',\n 'deployment',\n 'build',\n 'ci',\n 'cd',\n 'cli',\n 'sdk',\n 'editor',\n 'ide',\n 'engineering',\n 'compiler',\n 'runtime',\n ],\n AI: [\n 'ai',\n 'ml',\n 'llm',\n 'agent',\n 'agents',\n 'model',\n 'models',\n 'inference',\n 'gpt',\n 'chatbot',\n 'rag',\n 'embedding',\n 'embeddings',\n 'mcp',\n ],\n 'Design Tools': [\n 'design',\n 'design-tool',\n 'whiteboard',\n 'prototype',\n 'prototyping',\n 'wireframe',\n 'wireframes',\n 'mockup',\n 'mockups',\n 'figma-like',\n 'illustration',\n 'canvas',\n ],\n Productivity: [\n 'saas',\n 'workspace',\n 'team',\n 'teams',\n 'project-management',\n 'enterprise',\n 'b2b',\n 'crm',\n 'docs',\n 'wiki',\n 'collaboration',\n 'kanban',\n 'scheduling',\n 'meetings',\n ],\n Backend: [\n 'backend',\n 'database',\n 'db',\n 'api',\n 'apis',\n 'observability',\n 'monitoring',\n 'logging',\n 'analytics',\n 'pipeline',\n 'data-pipeline',\n 'streaming',\n 'queue',\n 'cache',\n ],\n Automotive: [\n 'car',\n 'cars',\n 'vehicle',\n 'vehicles',\n 'auto',\n 'automotive',\n 'driving',\n 'ev',\n 'electric-vehicle',\n ],\n Marketing: [\n 'marketing',\n 'seo',\n 'campaign',\n 'campaigns',\n 'newsletter',\n 'email-marketing',\n 'attribution',\n ],\n};\n\nfunction matchedCategoriesFor(\n queryTokens: Set<string>,\n queryStems: Set<string>\n): Set<string> {\n const out = new Set<string>();\n for (const [category, hints] of Object.entries(CATEGORY_HINTS)) {\n for (const hint of hints) {\n if (queryTokens.has(hint) || queryStems.has(stem(hint))) {\n out.add(category);\n break;\n }\n }\n }\n return out;\n}\n\nlet cachedTags: ReferenceTag[] | null = null;\n\nfunction stem(s: string): string {\n // Minimal English suffix stripping for recall across -ing/-ed/-s variants.\n // Preserves stems of length ≥3 so we don't collapse short words.\n let out = s;\n for (const suffix of ['ing', 'ed', 'ly', 'es', 's']) {\n if (out.length - suffix.length >= 3 && out.endsWith(suffix)) {\n out = out.slice(0, -suffix.length);\n break;\n }\n }\n return out;\n}\n\nfunction tagsFilePath(): string {\n const here = dirname(fileURLToPath(import.meta.url));\n const candidates = [\n join(here, '..', 'data', 'reference-tags.md'),\n join(here, '..', '..', 'data', 'reference-tags.md'),\n ];\n for (const c of candidates) {\n try {\n readFileSync(c, 'utf8');\n return c;\n } catch {\n // continue\n }\n }\n throw new Error('data/reference-tags.md not found');\n}\n\nconst ROW_RE = /^\\|\\s*([a-z0-9._-]+)\\s*\\|\\s*([^|]*?)\\s*\\|\\s*([^|]*?)\\s*\\|\\s*([^|]*?)\\s*\\|$/i;\n\nexport function loadReferenceTags(): ReferenceTag[] {\n if (cachedTags) return cachedTags;\n const raw = readFileSync(tagsFilePath(), 'utf8');\n const rows: ReferenceTag[] = [];\n for (const line of raw.split('\\n')) {\n const m = ROW_RE.exec(line);\n if (!m) continue;\n const [, id, color, category, keywordsRaw] = m;\n if (id === 'id') continue; // header\n if (id.startsWith('---')) continue;\n rows.push({\n id: id.trim(),\n color: color.trim(),\n category: category.trim(),\n keywords: keywordsRaw\n .split(',')\n .map((k) => k.trim().toLowerCase())\n .filter(Boolean),\n });\n }\n cachedTags = rows;\n return rows;\n}\n\nexport interface RecommendOptions {\n topK?: number;\n diversityByCategory?: boolean;\n}\n\nexport function recommend(\n description: string,\n opts: RecommendOptions = {}\n): RecommendHit[] {\n const topK = opts.topK ?? 5;\n const diversityByCategory = opts.diversityByCategory ?? true;\n\n const tags = loadReferenceTags();\n const rawTokens = [\n ...tokenize(description),\n ...description\n .toLowerCase()\n .split(/[^a-z0-9-]+/)\n .filter(Boolean),\n ];\n const queryTokens = new Set(rawTokens);\n const queryStems = new Set(rawTokens.map(stem));\n const matchedCategories = matchedCategoriesFor(queryTokens, queryStems);\n\n // First pass: tag matches per ref, to know whether to gate category bonus.\n const tagMatchByRef = tags.map((t) =>\n t.keywords.filter(\n (kw) => queryTokens.has(kw) || queryStems.has(stem(kw))\n )\n );\n const totalTagMatches = tagMatchByRef.reduce((a, m) => a + m.length, 0);\n\n const scored: Array<RecommendHit & { _ratio: number }> = tags.map((t, i) => {\n const matched = tagMatchByRef[i];\n const tagScore = matched.length;\n const categoryHit = matchedCategories.has(t.category);\n // Category bonus normally requires ≥1 tag match (prevents flooding).\n // But when the description produces zero tag matches anywhere, we\n // fall back to category-only scoring so users still get a useful\n // top-K instead of an alphabetical no-op.\n const categoryBonus =\n categoryHit && (tagScore > 0 || totalTagMatches === 0) ? 0.5 : 0;\n const score = tagScore + categoryBonus;\n const ratio = matched.length / Math.max(1, t.keywords.length);\n return {\n id: t.id,\n category: t.category,\n color: t.color,\n keywords: t.keywords,\n score,\n matchedKeywords: matched,\n matchedCategories: categoryHit ? [t.category] : [],\n _ratio: ratio,\n };\n });\n\n scored.sort(\n (a, b) =>\n b.score - a.score || b._ratio - a._ratio || a.id.localeCompare(b.id)\n );\n\n const stripRatio = (s: typeof scored): RecommendHit[] =>\n s.map(({ _ratio, ...rest }) => {\n void _ratio;\n return rest;\n });\n\n if (!diversityByCategory) return stripRatio(scored.slice(0, topK));\n\n // Category-bucketed diversity: one top hit per category, fill with next best.\n const picked = stripRatio(scored).slice(0, 0);\n const pickedSet = new Set<string>();\n const usedCategories = new Set<string>();\n const allHits = stripRatio(scored);\n for (const hit of allHits) {\n if (picked.length >= topK) break;\n if (usedCategories.has(hit.category)) continue;\n picked.push(hit);\n pickedSet.add(hit.id);\n usedCategories.add(hit.category);\n }\n for (const hit of allHits) {\n if (picked.length >= topK) break;\n if (pickedSet.has(hit.id)) continue;\n picked.push(hit);\n pickedSet.add(hit.id);\n }\n return picked;\n}\n","import {\n existsSync,\n readFileSync,\n writeFileSync,\n unlinkSync,\n mkdirSync,\n} from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nexport interface DeprecateOptions {\n projectRoot: string;\n previousReference?: string;\n newReference: string;\n preferencesReplayed?: number;\n preferencesOrphaned?: number;\n orphanFile?: string;\n reason: string;\n now?: Date;\n}\n\nexport interface DeprecateResult {\n renamed: boolean;\n from: string;\n to: string;\n}\n\nfunction deprecationHeader(opts: DeprecateOptions): string {\n const now = (opts.now ?? new Date()).toISOString();\n const lines = [\n '<!--',\n 'omd:deprecated',\n ` deprecated_at: ${now}`,\n ];\n if (opts.previousReference)\n lines.push(` previous_reference: ${opts.previousReference}`);\n lines.push(` new_reference: ${opts.newReference}`);\n if (opts.preferencesReplayed !== undefined)\n lines.push(` preferences_replayed: ${opts.preferencesReplayed}`);\n if (opts.preferencesOrphaned !== undefined)\n lines.push(` preferences_orphaned: ${opts.preferencesOrphaned}`);\n if (opts.orphanFile) lines.push(` orphan_file: ${opts.orphanFile}`);\n lines.push(` reason: ${opts.reason}`);\n lines.push('-->', '', '');\n return lines.join('\\n');\n}\n\nexport function deprecateDesignMd(opts: DeprecateOptions): DeprecateResult {\n const from = join(opts.projectRoot, 'DESIGN.md');\n const baseTo = join(opts.projectRoot, 'DESIGN_DEPRECATED.md');\n\n if (!existsSync(from)) {\n return { renamed: false, from, to: baseTo };\n }\n\n let target = baseTo;\n if (existsSync(baseTo)) {\n const ts = (opts.now ?? new Date()).toISOString().replace(/[:.]/g, '-');\n target = join(opts.projectRoot, `DESIGN_DEPRECATED.${ts}.md`);\n }\n\n mkdirSync(dirname(target), { recursive: true });\n const prior = readFileSync(from, 'utf8');\n writeFileSync(target, deprecationHeader(opts) + prior, 'utf8');\n unlinkSync(from);\n\n return { renamed: true, from, to: target };\n}\n"],"mappings":"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A;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,mBAAmB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,IAAI;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,gBAAgB;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,cAAc;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAA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","names":["writeFileSync","readFileSync","mkdirSync","existsSync","join","dirname","fileURLToPath","readFileSync","fileURLToPath","dirname","join","dirname","fileURLToPath","join","readFileSync","readFileSync","dirname","join","readFileSync","join","mkdirSync","writeFileSync","existsSync","dirname","fileURLToPath"]}
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
detectInstalledAgents
|
|
4
|
-
} from "./chunk-6YNSV3VY.js";
|
|
5
|
-
|
|
6
|
-
// src/cli/install-skills.ts
|
|
7
|
-
import * as p from "@clack/prompts";
|
|
8
|
-
import pc from "picocolors";
|
|
9
|
-
import {
|
|
10
|
-
readFileSync,
|
|
11
|
-
readdirSync,
|
|
12
|
-
writeFileSync,
|
|
13
|
-
existsSync,
|
|
14
|
-
mkdirSync
|
|
15
|
-
} from "fs";
|
|
16
|
-
import { join, dirname, relative } from "path";
|
|
17
|
-
import { fileURLToPath } from "url";
|
|
18
|
-
function findPackageRoot() {
|
|
19
|
-
let cur = dirname(fileURLToPath(import.meta.url));
|
|
20
|
-
for (let i = 0; i < 8; i++) {
|
|
21
|
-
if (existsSync(join(cur, "skills"))) return cur;
|
|
22
|
-
const parent = dirname(cur);
|
|
23
|
-
if (parent === cur) break;
|
|
24
|
-
cur = parent;
|
|
25
|
-
}
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
function listShippedSkills(packageRoot) {
|
|
29
|
-
const skillsDir = join(packageRoot, "skills");
|
|
30
|
-
if (!existsSync(skillsDir)) return [];
|
|
31
|
-
return readdirSync(skillsDir).filter((name) => existsSync(join(skillsDir, name, "SKILL.md"))).sort();
|
|
32
|
-
}
|
|
33
|
-
function planForTarget(projectRoot, target) {
|
|
34
|
-
switch (target) {
|
|
35
|
-
case "claude-code":
|
|
36
|
-
return {
|
|
37
|
-
target,
|
|
38
|
-
destDir: join(projectRoot, ".claude", "skills"),
|
|
39
|
-
layout: "folder"
|
|
40
|
-
};
|
|
41
|
-
case "codex":
|
|
42
|
-
return {
|
|
43
|
-
target,
|
|
44
|
-
destDir: join(projectRoot, ".codex", "skills"),
|
|
45
|
-
layout: "folder"
|
|
46
|
-
};
|
|
47
|
-
case "opencode":
|
|
48
|
-
return {
|
|
49
|
-
target,
|
|
50
|
-
destDir: join(projectRoot, ".opencode", "agents"),
|
|
51
|
-
layout: "flat"
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
var MANAGED_HEADER = "<!-- omd:installed-skill \u2014 managed by `omd install-skills`. Do not edit; rerun the command to refresh. -->";
|
|
56
|
-
function installOne(packageRoot, plan, skill, force) {
|
|
57
|
-
const src = readFileSync(
|
|
58
|
-
join(packageRoot, "skills", skill, "SKILL.md"),
|
|
59
|
-
"utf8"
|
|
60
|
-
);
|
|
61
|
-
const managed = MANAGED_HEADER + "\n\n" + src;
|
|
62
|
-
const destPath = plan.layout === "folder" ? join(plan.destDir, skill, "SKILL.md") : join(plan.destDir, skill + ".md");
|
|
63
|
-
const exists = existsSync(destPath);
|
|
64
|
-
const existing = exists ? readFileSync(destPath, "utf8") : "";
|
|
65
|
-
if (exists && existing === managed) {
|
|
66
|
-
return { target: plan.target, skill, destPath, status: "unchanged" };
|
|
67
|
-
}
|
|
68
|
-
if (exists && !existing.startsWith(MANAGED_HEADER) && !force) {
|
|
69
|
-
return { target: plan.target, skill, destPath, status: "skipped-drift" };
|
|
70
|
-
}
|
|
71
|
-
mkdirSync(dirname(destPath), { recursive: true });
|
|
72
|
-
writeFileSync(destPath, managed, "utf8");
|
|
73
|
-
return {
|
|
74
|
-
target: plan.target,
|
|
75
|
-
skill,
|
|
76
|
-
destPath,
|
|
77
|
-
status: exists ? "updated" : "created"
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
var STATUS_LABEL = {
|
|
81
|
-
created: pc.green("created"),
|
|
82
|
-
updated: pc.cyan("updated"),
|
|
83
|
-
unchanged: pc.dim("unchanged"),
|
|
84
|
-
"skipped-drift": pc.yellow("skipped")
|
|
85
|
-
};
|
|
86
|
-
function autoDetectTargets(projectRoot) {
|
|
87
|
-
const presence = detectInstalledAgents(projectRoot);
|
|
88
|
-
const targets = [];
|
|
89
|
-
if (presence.claudeCode) targets.push("claude-code");
|
|
90
|
-
if (presence.codex) targets.push("codex");
|
|
91
|
-
if (presence.opencode) targets.push("opencode");
|
|
92
|
-
if (targets.length === 0) {
|
|
93
|
-
return ["claude-code", "codex", "opencode"];
|
|
94
|
-
}
|
|
95
|
-
return targets;
|
|
96
|
-
}
|
|
97
|
-
async function runInstallSkills(opts = {}) {
|
|
98
|
-
const projectRoot = opts.dir ?? process.cwd();
|
|
99
|
-
const packageRoot = findPackageRoot();
|
|
100
|
-
if (!packageRoot) {
|
|
101
|
-
console.error(pc.red("omd install-skills: package data not found"));
|
|
102
|
-
return 1;
|
|
103
|
-
}
|
|
104
|
-
const skills = listShippedSkills(packageRoot);
|
|
105
|
-
if (skills.length === 0) {
|
|
106
|
-
console.error(pc.red("omd install-skills: no skills found in package"));
|
|
107
|
-
return 1;
|
|
108
|
-
}
|
|
109
|
-
const targets = opts.agents ?? autoDetectTargets(projectRoot);
|
|
110
|
-
const plans = targets.map((t) => planForTarget(projectRoot, t));
|
|
111
|
-
const force = opts.force ?? false;
|
|
112
|
-
p.intro(
|
|
113
|
-
pc.bold("omd install-skills") + pc.dim(` (${relative(process.cwd(), projectRoot) || "."})`)
|
|
114
|
-
);
|
|
115
|
-
p.log.message(
|
|
116
|
-
pc.bold("Targets: ") + targets.map((t) => pc.cyan(t)).join(", ")
|
|
117
|
-
);
|
|
118
|
-
p.log.message(
|
|
119
|
-
pc.bold("Skills: ") + skills.map((s) => pc.cyan(s)).join(", ")
|
|
120
|
-
);
|
|
121
|
-
const results = [];
|
|
122
|
-
for (const plan of plans) {
|
|
123
|
-
for (const skill of skills) {
|
|
124
|
-
results.push(installOne(packageRoot, plan, skill, force));
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
p.log.message(pc.bold("\nResults:"));
|
|
128
|
-
for (const r of results) {
|
|
129
|
-
const rel = relative(projectRoot, r.destPath);
|
|
130
|
-
p.log.message(
|
|
131
|
-
` ${STATUS_LABEL[r.status]} ${pc.dim(r.target.padEnd(12))} ${rel}`
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
const driftCount = results.filter((r) => r.status === "skipped-drift").length;
|
|
135
|
-
const writtenCount = results.filter(
|
|
136
|
-
(r) => r.status === "created" || r.status === "updated"
|
|
137
|
-
).length;
|
|
138
|
-
if (driftCount > 0) {
|
|
139
|
-
p.outro(
|
|
140
|
-
pc.yellow(
|
|
141
|
-
`${writtenCount} written, ${driftCount} skipped (existing files lack the omd marker \u2014 rerun with --force to overwrite).`
|
|
142
|
-
)
|
|
143
|
-
);
|
|
144
|
-
} else {
|
|
145
|
-
p.outro(pc.green(`${writtenCount} skill files written.`));
|
|
146
|
-
}
|
|
147
|
-
return 0;
|
|
148
|
-
}
|
|
149
|
-
export {
|
|
150
|
-
runInstallSkills
|
|
151
|
-
};
|
|
152
|
-
//# sourceMappingURL=install-skills-CM6VXFZJ.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/install-skills.ts"],"sourcesContent":["import * as p from '@clack/prompts';\nimport pc from 'picocolors';\nimport {\n readFileSync,\n readdirSync,\n writeFileSync,\n existsSync,\n mkdirSync,\n} from 'node:fs';\nimport { join, dirname, relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { detectInstalledAgents } from '../core/agent-detect.js';\n\nexport type SkillTarget = 'claude-code' | 'codex' | 'opencode';\n\nexport interface InstallSkillsOptions {\n dir?: string;\n agents?: SkillTarget[];\n force?: boolean;\n}\n\ninterface InstallPlan {\n target: SkillTarget;\n destDir: string;\n layout: 'folder' | 'flat';\n}\n\nfunction findPackageRoot(): string | null {\n let cur = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 8; i++) {\n if (existsSync(join(cur, 'skills'))) return cur;\n const parent = dirname(cur);\n if (parent === cur) break;\n cur = parent;\n }\n return null;\n}\n\nfunction listShippedSkills(packageRoot: string): string[] {\n const skillsDir = join(packageRoot, 'skills');\n if (!existsSync(skillsDir)) return [];\n return readdirSync(skillsDir)\n .filter((name) => existsSync(join(skillsDir, name, 'SKILL.md')))\n .sort();\n}\n\nfunction planForTarget(projectRoot: string, target: SkillTarget): InstallPlan {\n switch (target) {\n case 'claude-code':\n return {\n target,\n destDir: join(projectRoot, '.claude', 'skills'),\n layout: 'folder',\n };\n case 'codex':\n return {\n target,\n destDir: join(projectRoot, '.codex', 'skills'),\n layout: 'folder',\n };\n case 'opencode':\n return {\n target,\n destDir: join(projectRoot, '.opencode', 'agents'),\n layout: 'flat',\n };\n }\n}\n\nconst MANAGED_HEADER =\n '<!-- omd:installed-skill — managed by `omd install-skills`. Do not edit; rerun the command to refresh. -->';\n\ninterface InstallResult {\n target: SkillTarget;\n skill: string;\n destPath: string;\n status: 'created' | 'updated' | 'unchanged' | 'skipped-drift';\n}\n\nfunction installOne(\n packageRoot: string,\n plan: InstallPlan,\n skill: string,\n force: boolean\n): InstallResult {\n const src = readFileSync(\n join(packageRoot, 'skills', skill, 'SKILL.md'),\n 'utf8'\n );\n const managed = MANAGED_HEADER + '\\n\\n' + src;\n const destPath =\n plan.layout === 'folder'\n ? join(plan.destDir, skill, 'SKILL.md')\n : join(plan.destDir, skill + '.md');\n\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n\n if (exists && existing === managed) {\n return { target: plan.target, skill, destPath, status: 'unchanged' };\n }\n\n if (exists && !existing.startsWith(MANAGED_HEADER) && !force) {\n return { target: plan.target, skill, destPath, status: 'skipped-drift' };\n }\n\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, managed, 'utf8');\n return {\n target: plan.target,\n skill,\n destPath,\n status: exists ? 'updated' : 'created',\n };\n}\n\nconst STATUS_LABEL: Record<InstallResult['status'], string> = {\n created: pc.green('created'),\n updated: pc.cyan('updated'),\n unchanged: pc.dim('unchanged'),\n 'skipped-drift': pc.yellow('skipped'),\n};\n\nfunction autoDetectTargets(projectRoot: string): SkillTarget[] {\n const presence = detectInstalledAgents(projectRoot);\n const targets: SkillTarget[] = [];\n if (presence.claudeCode) targets.push('claude-code');\n if (presence.codex) targets.push('codex');\n if (presence.opencode) targets.push('opencode');\n // Cursor uses .mdc rules, not skills — installed via `omd sync`.\n if (targets.length === 0) {\n // Fallback: install for all three so user gets coverage even without\n // explicit signal. Idempotent so cost is low.\n return ['claude-code', 'codex', 'opencode'];\n }\n return targets;\n}\n\nexport async function runInstallSkills(\n opts: InstallSkillsOptions = {}\n): Promise<number> {\n const projectRoot = opts.dir ?? process.cwd();\n const packageRoot = findPackageRoot();\n if (!packageRoot) {\n console.error(pc.red('omd install-skills: package data not found'));\n return 1;\n }\n\n const skills = listShippedSkills(packageRoot);\n if (skills.length === 0) {\n console.error(pc.red('omd install-skills: no skills found in package'));\n return 1;\n }\n\n const targets = opts.agents ?? autoDetectTargets(projectRoot);\n const plans = targets.map((t) => planForTarget(projectRoot, t));\n const force = opts.force ?? false;\n\n p.intro(\n pc.bold('omd install-skills') +\n pc.dim(` (${relative(process.cwd(), projectRoot) || '.'})`)\n );\n\n p.log.message(\n pc.bold('Targets: ') +\n targets.map((t) => pc.cyan(t)).join(', ')\n );\n p.log.message(\n pc.bold('Skills: ') + skills.map((s) => pc.cyan(s)).join(', ')\n );\n\n const results: InstallResult[] = [];\n for (const plan of plans) {\n for (const skill of skills) {\n results.push(installOne(packageRoot, plan, skill, force));\n }\n }\n\n p.log.message(pc.bold('\\nResults:'));\n for (const r of results) {\n const rel = relative(projectRoot, r.destPath);\n p.log.message(\n ` ${STATUS_LABEL[r.status]} ${pc.dim(r.target.padEnd(12))} ${rel}`\n );\n }\n\n const driftCount = results.filter((r) => r.status === 'skipped-drift').length;\n const writtenCount = results.filter(\n (r) => r.status === 'created' || r.status === 'updated'\n ).length;\n\n if (driftCount > 0) {\n p.outro(\n pc.yellow(\n `${writtenCount} written, ${driftCount} skipped (existing files lack the omd marker — rerun with --force to overwrite).`\n )\n );\n } else {\n p.outro(pc.green(`${writtenCount} skill files written.`));\n }\n return 0;\n}\n"],"mappings":"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","names":[]}
|
package/dist/learn-33LHKEJA.js
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
hashContent
|
|
4
|
-
} from "./chunk-OOQQEUGX.js";
|
|
5
|
-
import {
|
|
6
|
-
filterByStatus,
|
|
7
|
-
groupByScope,
|
|
8
|
-
readFile,
|
|
9
|
-
updateEntryStatus
|
|
10
|
-
} from "./chunk-OR5DHENY.js";
|
|
11
|
-
|
|
12
|
-
// src/cli/learn.ts
|
|
13
|
-
import * as p from "@clack/prompts";
|
|
14
|
-
import pc from "picocolors";
|
|
15
|
-
import { existsSync, readFileSync } from "fs";
|
|
16
|
-
import { join, relative } from "path";
|
|
17
|
-
var STATUS_COLOR = {
|
|
18
|
-
pending: pc.yellow,
|
|
19
|
-
applied: pc.green,
|
|
20
|
-
rejected: pc.red,
|
|
21
|
-
superseded: pc.dim
|
|
22
|
-
};
|
|
23
|
-
function currentDesignMdHash(projectRoot) {
|
|
24
|
-
const path = join(projectRoot, "DESIGN.md");
|
|
25
|
-
if (!existsSync(path)) return "";
|
|
26
|
-
return hashContent(readFileSync(path, "utf8"));
|
|
27
|
-
}
|
|
28
|
-
function printEntries(entries) {
|
|
29
|
-
const grouped = groupByScope(entries);
|
|
30
|
-
const scopes = [...grouped.keys()].sort();
|
|
31
|
-
for (const scope of scopes) {
|
|
32
|
-
const bucket = grouped.get(scope);
|
|
33
|
-
p.log.message(pc.bold(`
|
|
34
|
-
${scope}`) + pc.dim(` (${bucket.length})`));
|
|
35
|
-
for (const entry of bucket) {
|
|
36
|
-
const color = STATUS_COLOR[entry.meta.status];
|
|
37
|
-
p.log.message(
|
|
38
|
-
` ${color(entry.meta.status.padEnd(10))} ${pc.cyan(entry.meta.id)} ${pc.dim(entry.meta.timestamp.slice(0, 19))}`
|
|
39
|
-
);
|
|
40
|
-
const firstLine = entry.body.split("\n")[0].trim();
|
|
41
|
-
if (firstLine) p.log.message(` ${pc.dim("\u2514")} ${firstLine}`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
async function runLearn(opts = {}) {
|
|
46
|
-
const projectRoot = opts.dir ?? process.cwd();
|
|
47
|
-
const relRoot = relative(process.cwd(), projectRoot) || ".";
|
|
48
|
-
if (opts.markApplied) {
|
|
49
|
-
const hash = opts.hash ?? currentDesignMdHash(projectRoot);
|
|
50
|
-
if (!hash) {
|
|
51
|
-
console.error(
|
|
52
|
-
pc.red(
|
|
53
|
-
"omd learn --mark-applied: DESIGN.md not found; pass --hash <value> explicitly."
|
|
54
|
-
)
|
|
55
|
-
);
|
|
56
|
-
return 1;
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
const entry = updateEntryStatus(projectRoot, {
|
|
60
|
-
id: opts.markApplied,
|
|
61
|
-
status: "applied",
|
|
62
|
-
applied_design_md_hash: hash
|
|
63
|
-
});
|
|
64
|
-
p.log.success(
|
|
65
|
-
`Marked ${pc.bold(entry.meta.id)} applied (DESIGN.md hash ${pc.cyan(hash)}).`
|
|
66
|
-
);
|
|
67
|
-
return 0;
|
|
68
|
-
} catch (err) {
|
|
69
|
-
console.error(pc.red(String(err)));
|
|
70
|
-
return 1;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
if (opts.markRejected) {
|
|
74
|
-
if (!opts.reason) {
|
|
75
|
-
console.error(
|
|
76
|
-
pc.red("omd learn --mark-rejected: --reason is required.")
|
|
77
|
-
);
|
|
78
|
-
return 1;
|
|
79
|
-
}
|
|
80
|
-
try {
|
|
81
|
-
const entry = updateEntryStatus(projectRoot, {
|
|
82
|
-
id: opts.markRejected,
|
|
83
|
-
status: "rejected",
|
|
84
|
-
rejected_reason: opts.reason
|
|
85
|
-
});
|
|
86
|
-
p.log.success(
|
|
87
|
-
`Marked ${pc.bold(entry.meta.id)} rejected (${pc.dim(opts.reason)}).`
|
|
88
|
-
);
|
|
89
|
-
return 0;
|
|
90
|
-
} catch (err) {
|
|
91
|
-
console.error(pc.red(String(err)));
|
|
92
|
-
return 1;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
p.intro(pc.bold("omd learn") + pc.dim(` (${relRoot})`));
|
|
96
|
-
const file = readFile(projectRoot);
|
|
97
|
-
if (!file) {
|
|
98
|
-
p.outro(
|
|
99
|
-
pc.yellow(
|
|
100
|
-
"No .omd/preferences.md found. Log preferences with `omd remember <note>`."
|
|
101
|
-
)
|
|
102
|
-
);
|
|
103
|
-
return 0;
|
|
104
|
-
}
|
|
105
|
-
let entries = file.entries;
|
|
106
|
-
if (!opts.all && !opts.status) {
|
|
107
|
-
entries = filterByStatus(entries, "pending");
|
|
108
|
-
}
|
|
109
|
-
if (opts.status) {
|
|
110
|
-
entries = filterByStatus(entries, opts.status);
|
|
111
|
-
}
|
|
112
|
-
if (opts.scope) {
|
|
113
|
-
entries = entries.filter((e) => e.meta.scope === opts.scope);
|
|
114
|
-
}
|
|
115
|
-
const counts = {
|
|
116
|
-
pending: 0,
|
|
117
|
-
applied: 0,
|
|
118
|
-
rejected: 0,
|
|
119
|
-
superseded: 0
|
|
120
|
-
};
|
|
121
|
-
for (const e of file.entries) counts[e.meta.status]++;
|
|
122
|
-
p.log.message(
|
|
123
|
-
pc.bold("Totals: ") + `${pc.yellow("pending " + counts.pending)} \xB7 ${pc.green("applied " + counts.applied)} \xB7 ${pc.red("rejected " + counts.rejected)} \xB7 ${pc.dim("superseded " + counts.superseded)}`
|
|
124
|
-
);
|
|
125
|
-
if (entries.length === 0) {
|
|
126
|
-
p.outro(pc.dim("No entries match the current filter."));
|
|
127
|
-
return 0;
|
|
128
|
-
}
|
|
129
|
-
printEntries(entries);
|
|
130
|
-
p.outro(
|
|
131
|
-
pc.dim(
|
|
132
|
-
`Showing ${entries.length} entries. Use --mark-applied <id> or --mark-rejected <id> --reason <text> to update.`
|
|
133
|
-
)
|
|
134
|
-
);
|
|
135
|
-
return 0;
|
|
136
|
-
}
|
|
137
|
-
export {
|
|
138
|
-
runLearn
|
|
139
|
-
};
|
|
140
|
-
//# sourceMappingURL=learn-33LHKEJA.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/learn.ts"],"sourcesContent":["import * as p from '@clack/prompts';\nimport pc from 'picocolors';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport {\n readFile,\n groupByScope,\n filterByStatus,\n updateEntryStatus,\n type PreferenceStatus,\n type PreferenceEntry,\n} from '../core/preferences.js';\nimport { hashContent } from '../core/sync-marker.js';\n\nexport interface LearnOptions {\n dir?: string;\n all?: boolean;\n scope?: string;\n status?: PreferenceStatus;\n markApplied?: string;\n markRejected?: string;\n reason?: string;\n hash?: string;\n}\n\nconst STATUS_COLOR: Record<PreferenceStatus, (s: string) => string> = {\n pending: pc.yellow,\n applied: pc.green,\n rejected: pc.red,\n superseded: pc.dim,\n};\n\nfunction currentDesignMdHash(projectRoot: string): string {\n const path = join(projectRoot, 'DESIGN.md');\n if (!existsSync(path)) return '';\n return hashContent(readFileSync(path, 'utf8'));\n}\n\nfunction printEntries(entries: PreferenceEntry[]): void {\n const grouped = groupByScope(entries);\n const scopes = [...grouped.keys()].sort();\n\n for (const scope of scopes) {\n const bucket = grouped.get(scope)!;\n p.log.message(pc.bold(`\\n${scope}`) + pc.dim(` (${bucket.length})`));\n for (const entry of bucket) {\n const color = STATUS_COLOR[entry.meta.status];\n p.log.message(\n ` ${color(entry.meta.status.padEnd(10))} ${pc.cyan(entry.meta.id)} ${pc.dim(entry.meta.timestamp.slice(0, 19))}`\n );\n const firstLine = entry.body.split('\\n')[0].trim();\n if (firstLine) p.log.message(` ${pc.dim('└')} ${firstLine}`);\n }\n }\n}\n\nexport async function runLearn(opts: LearnOptions = {}): Promise<number> {\n const projectRoot = opts.dir ?? process.cwd();\n const relRoot = relative(process.cwd(), projectRoot) || '.';\n\n // Mutation paths — handle first.\n if (opts.markApplied) {\n const hash = opts.hash ?? currentDesignMdHash(projectRoot);\n if (!hash) {\n console.error(\n pc.red(\n 'omd learn --mark-applied: DESIGN.md not found; pass --hash <value> explicitly.'\n )\n );\n return 1;\n }\n try {\n const entry = updateEntryStatus(projectRoot, {\n id: opts.markApplied,\n status: 'applied',\n applied_design_md_hash: hash,\n });\n p.log.success(\n `Marked ${pc.bold(entry.meta.id)} applied (DESIGN.md hash ${pc.cyan(hash)}).`\n );\n return 0;\n } catch (err) {\n console.error(pc.red(String(err)));\n return 1;\n }\n }\n\n if (opts.markRejected) {\n if (!opts.reason) {\n console.error(\n pc.red('omd learn --mark-rejected: --reason is required.')\n );\n return 1;\n }\n try {\n const entry = updateEntryStatus(projectRoot, {\n id: opts.markRejected,\n status: 'rejected',\n rejected_reason: opts.reason,\n });\n p.log.success(\n `Marked ${pc.bold(entry.meta.id)} rejected (${pc.dim(opts.reason)}).`\n );\n return 0;\n } catch (err) {\n console.error(pc.red(String(err)));\n return 1;\n }\n }\n\n // Read path — list.\n p.intro(pc.bold('omd learn') + pc.dim(` (${relRoot})`));\n\n const file = readFile(projectRoot);\n if (!file) {\n p.outro(\n pc.yellow(\n 'No .omd/preferences.md found. Log preferences with `omd remember <note>`.'\n )\n );\n return 0;\n }\n\n let entries = file.entries;\n\n if (!opts.all && !opts.status) {\n entries = filterByStatus(entries, 'pending');\n }\n if (opts.status) {\n entries = filterByStatus(entries, opts.status);\n }\n if (opts.scope) {\n entries = entries.filter((e) => e.meta.scope === opts.scope);\n }\n\n const counts: Record<PreferenceStatus, number> = {\n pending: 0,\n applied: 0,\n rejected: 0,\n superseded: 0,\n };\n for (const e of file.entries) counts[e.meta.status]++;\n\n p.log.message(\n pc.bold('Totals: ') +\n `${pc.yellow('pending ' + counts.pending)} · ` +\n `${pc.green('applied ' + counts.applied)} · ` +\n `${pc.red('rejected ' + counts.rejected)} · ` +\n `${pc.dim('superseded ' + counts.superseded)}`\n );\n\n if (entries.length === 0) {\n p.outro(pc.dim('No entries match the current filter.'));\n return 0;\n }\n\n printEntries(entries);\n\n p.outro(\n pc.dim(\n `Showing ${entries.length} entries. Use --mark-applied <id> or --mark-rejected <id> --reason <text> to update.`\n )\n );\n return 0;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/cli/reference.ts
|
|
4
|
-
import { readFileSync, readdirSync, statSync, existsSync } from "fs";
|
|
5
|
-
import { join, dirname } from "path";
|
|
6
|
-
import { fileURLToPath } from "url";
|
|
7
|
-
import pc from "picocolors";
|
|
8
|
-
function findRepoRoot() {
|
|
9
|
-
let cur = dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
for (let i = 0; i < 8; i++) {
|
|
11
|
-
if (existsSync(join(cur, "references"))) return cur;
|
|
12
|
-
const parent = dirname(cur);
|
|
13
|
-
if (parent === cur) break;
|
|
14
|
-
cur = parent;
|
|
15
|
-
}
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
function runReferenceShow(refId) {
|
|
19
|
-
const root = findRepoRoot();
|
|
20
|
-
if (!root) {
|
|
21
|
-
console.error(pc.red("omd reference show: package data not found"));
|
|
22
|
-
return 1;
|
|
23
|
-
}
|
|
24
|
-
const path = join(root, "references", refId, "DESIGN.md");
|
|
25
|
-
if (!existsSync(path)) {
|
|
26
|
-
console.error(pc.red(`omd reference show: reference not found: ${refId}`));
|
|
27
|
-
return 1;
|
|
28
|
-
}
|
|
29
|
-
process.stdout.write(readFileSync(path, "utf8"));
|
|
30
|
-
return 0;
|
|
31
|
-
}
|
|
32
|
-
function runReferenceList() {
|
|
33
|
-
const root = findRepoRoot();
|
|
34
|
-
if (!root) {
|
|
35
|
-
console.error(pc.red("omd reference list: package data not found"));
|
|
36
|
-
return 1;
|
|
37
|
-
}
|
|
38
|
-
const refDir = join(root, "references");
|
|
39
|
-
const ids = readdirSync(refDir).filter((name) => statSync(join(refDir, name)).isDirectory()).filter((name) => existsSync(join(refDir, name, "DESIGN.md"))).sort();
|
|
40
|
-
for (const id of ids) process.stdout.write(id + "\n");
|
|
41
|
-
return 0;
|
|
42
|
-
}
|
|
43
|
-
export {
|
|
44
|
-
runReferenceList,
|
|
45
|
-
runReferenceShow
|
|
46
|
-
};
|
|
47
|
-
//# sourceMappingURL=reference-YMNAOXJQ.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/reference.ts"],"sourcesContent":["import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport pc from 'picocolors';\n\nfunction findRepoRoot(): string | null {\n let cur = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 8; i++) {\n if (existsSync(join(cur, 'references'))) return cur;\n const parent = dirname(cur);\n if (parent === cur) break;\n cur = parent;\n }\n return null;\n}\n\nexport function runReferenceShow(refId: string): number {\n const root = findRepoRoot();\n if (!root) {\n console.error(pc.red('omd reference show: package data not found'));\n return 1;\n }\n const path = join(root, 'references', refId, 'DESIGN.md');\n if (!existsSync(path)) {\n console.error(pc.red(`omd reference show: reference not found: ${refId}`));\n return 1;\n }\n process.stdout.write(readFileSync(path, 'utf8'));\n return 0;\n}\n\nexport function runReferenceList(): number {\n const root = findRepoRoot();\n if (!root) {\n console.error(pc.red('omd reference list: package data not found'));\n return 1;\n }\n const refDir = join(root, 'references');\n const ids = readdirSync(refDir)\n .filter((name) => statSync(join(refDir, name)).isDirectory())\n .filter((name) => existsSync(join(refDir, name, 'DESIGN.md')))\n .sort();\n for (const id of ids) process.stdout.write(id + '\\n');\n return 0;\n}\n"],"mappings":";;;AAAA,SAAS,cAAc,aAAa,UAAU,kBAAkB;AAChE,SAAS,MAAM,eAAe;AAC9B,SAAS,qBAAqB;AAC9B,OAAO,QAAQ;AAEf,SAAS,eAA8B;AACrC,MAAI,MAAM,QAAQ,cAAc,YAAY,GAAG,CAAC;AAChD,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,QAAI,WAAW,KAAK,KAAK,YAAY,CAAC,EAAG,QAAO;AAChD,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,IAAK;AACpB,UAAM;AAAA,EACR;AACA,SAAO;AACT;AAEO,SAAS,iBAAiB,OAAuB;AACtD,QAAM,OAAO,aAAa;AAC1B,MAAI,CAAC,MAAM;AACT,YAAQ,MAAM,GAAG,IAAI,4CAA4C,CAAC;AAClE,WAAO;AAAA,EACT;AACA,QAAM,OAAO,KAAK,MAAM,cAAc,OAAO,WAAW;AACxD,MAAI,CAAC,WAAW,IAAI,GAAG;AACrB,YAAQ,MAAM,GAAG,IAAI,4CAA4C,KAAK,EAAE,CAAC;AACzE,WAAO;AAAA,EACT;AACA,UAAQ,OAAO,MAAM,aAAa,MAAM,MAAM,CAAC;AAC/C,SAAO;AACT;AAEO,SAAS,mBAA2B;AACzC,QAAM,OAAO,aAAa;AAC1B,MAAI,CAAC,MAAM;AACT,YAAQ,MAAM,GAAG,IAAI,4CAA4C,CAAC;AAClE,WAAO;AAAA,EACT;AACA,QAAM,SAAS,KAAK,MAAM,YAAY;AACtC,QAAM,MAAM,YAAY,MAAM,EAC3B,OAAO,CAAC,SAAS,SAAS,KAAK,QAAQ,IAAI,CAAC,EAAE,YAAY,CAAC,EAC3D,OAAO,CAAC,SAAS,WAAW,KAAK,QAAQ,MAAM,WAAW,CAAC,CAAC,EAC5D,KAAK;AACR,aAAW,MAAM,IAAK,SAAQ,OAAO,MAAM,KAAK,IAAI;AACpD,SAAO;AACT;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
detectCallingAgent
|
|
4
|
-
} from "./chunk-6YNSV3VY.js";
|
|
5
|
-
import {
|
|
6
|
-
appendEntry,
|
|
7
|
-
inferScope
|
|
8
|
-
} from "./chunk-OR5DHENY.js";
|
|
9
|
-
|
|
10
|
-
// src/cli/remember.ts
|
|
11
|
-
import * as p from "@clack/prompts";
|
|
12
|
-
import pc from "picocolors";
|
|
13
|
-
import { relative } from "path";
|
|
14
|
-
var ALLOWED_SCOPE_PREFIXES = [
|
|
15
|
-
"visualTheme",
|
|
16
|
-
"color",
|
|
17
|
-
"typography",
|
|
18
|
-
"spacing",
|
|
19
|
-
"voice",
|
|
20
|
-
"motion",
|
|
21
|
-
"layout",
|
|
22
|
-
"components."
|
|
23
|
-
];
|
|
24
|
-
function validateScope(raw) {
|
|
25
|
-
for (const prefix of ALLOWED_SCOPE_PREFIXES) {
|
|
26
|
-
if (prefix.endsWith(".")) {
|
|
27
|
-
if (raw.startsWith(prefix) && raw.length > prefix.length) {
|
|
28
|
-
return raw;
|
|
29
|
-
}
|
|
30
|
-
} else if (raw === prefix) {
|
|
31
|
-
return raw;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
async function runRemember(note, opts = {}) {
|
|
37
|
-
const projectRoot = opts.dir ?? process.cwd();
|
|
38
|
-
const trimmed = note.trim();
|
|
39
|
-
if (!trimmed) {
|
|
40
|
-
console.error(pc.red("omd remember: note is required"));
|
|
41
|
-
return 1;
|
|
42
|
-
}
|
|
43
|
-
let scope;
|
|
44
|
-
if (opts.scope) {
|
|
45
|
-
const validated = validateScope(opts.scope);
|
|
46
|
-
if (!validated) {
|
|
47
|
-
console.error(
|
|
48
|
-
pc.red(`omd remember: invalid scope "${opts.scope}"`)
|
|
49
|
-
);
|
|
50
|
-
return 1;
|
|
51
|
-
}
|
|
52
|
-
scope = validated;
|
|
53
|
-
} else {
|
|
54
|
-
scope = inferScope(trimmed);
|
|
55
|
-
}
|
|
56
|
-
const detectedAgent = detectCallingAgent();
|
|
57
|
-
const sourceAgent = opts.agent ?? (detectedAgent !== "unknown" ? detectedAgent : void 0);
|
|
58
|
-
const entry = appendEntry(projectRoot, {
|
|
59
|
-
note: trimmed,
|
|
60
|
-
scope,
|
|
61
|
-
signal: "user-statement",
|
|
62
|
-
confidence: "explicit",
|
|
63
|
-
source_agent: sourceAgent,
|
|
64
|
-
source_context: opts.context
|
|
65
|
-
});
|
|
66
|
-
const rel = relative(process.cwd(), projectRoot) || ".";
|
|
67
|
-
p.log.success(
|
|
68
|
-
`Logged ${pc.bold(entry.meta.id)} (${pc.cyan(entry.meta.scope)}) \u2192 ${rel}/.omd/preferences.md`
|
|
69
|
-
);
|
|
70
|
-
p.log.info(
|
|
71
|
-
pc.dim("Run `omd learn --apply` to fold pending preferences into DESIGN.md.")
|
|
72
|
-
);
|
|
73
|
-
return 0;
|
|
74
|
-
}
|
|
75
|
-
export {
|
|
76
|
-
runRemember
|
|
77
|
-
};
|
|
78
|
-
//# sourceMappingURL=remember-UAFA5B2O.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/remember.ts"],"sourcesContent":["import * as p from '@clack/prompts';\nimport pc from 'picocolors';\nimport { relative } from 'node:path';\nimport {\n appendEntry,\n inferScope,\n type PreferenceScope,\n} from '../core/preferences.js';\nimport { detectCallingAgent } from '../core/agent-detect.js';\n\nexport interface RememberOptions {\n dir?: string;\n scope?: PreferenceScope;\n agent?: string;\n context?: string;\n}\n\nconst ALLOWED_SCOPE_PREFIXES = [\n 'visualTheme',\n 'color',\n 'typography',\n 'spacing',\n 'voice',\n 'motion',\n 'layout',\n 'components.',\n] as const;\n\nfunction validateScope(raw: string): PreferenceScope | null {\n for (const prefix of ALLOWED_SCOPE_PREFIXES) {\n if (prefix.endsWith('.')) {\n if (raw.startsWith(prefix) && raw.length > prefix.length) {\n return raw as PreferenceScope;\n }\n } else if (raw === prefix) {\n return raw as PreferenceScope;\n }\n }\n return null;\n}\n\nexport async function runRemember(\n note: string,\n opts: RememberOptions = {}\n): Promise<number> {\n const projectRoot = opts.dir ?? process.cwd();\n const trimmed = note.trim();\n\n if (!trimmed) {\n console.error(pc.red('omd remember: note is required'));\n return 1;\n }\n\n let scope: PreferenceScope;\n if (opts.scope) {\n const validated = validateScope(opts.scope);\n if (!validated) {\n console.error(\n pc.red(`omd remember: invalid scope \"${opts.scope}\"`)\n );\n return 1;\n }\n scope = validated;\n } else {\n scope = inferScope(trimmed);\n }\n\n const detectedAgent = detectCallingAgent();\n const sourceAgent =\n opts.agent ?? (detectedAgent !== 'unknown' ? detectedAgent : undefined);\n\n const entry = appendEntry(projectRoot, {\n note: trimmed,\n scope,\n signal: 'user-statement',\n confidence: 'explicit',\n source_agent: sourceAgent,\n source_context: opts.context,\n });\n\n const rel = relative(process.cwd(), projectRoot) || '.';\n p.log.success(\n `Logged ${pc.bold(entry.meta.id)} (${pc.cyan(entry.meta.scope)}) → ${rel}/.omd/preferences.md`\n );\n p.log.info(\n pc.dim('Run `omd learn --apply` to fold pending preferences into DESIGN.md.')\n );\n return 0;\n}\n"],"mappings":";;;;;;;;;;AAAA,YAAY,OAAO;AACnB,OAAO,QAAQ;AACf,SAAS,gBAAgB;AAezB,IAAM,yBAAyB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,cAAc,KAAqC;AAC1D,aAAW,UAAU,wBAAwB;AAC3C,QAAI,OAAO,SAAS,GAAG,GAAG;AACxB,UAAI,IAAI,WAAW,MAAM,KAAK,IAAI,SAAS,OAAO,QAAQ;AACxD,eAAO;AAAA,MACT;AAAA,IACF,WAAW,QAAQ,QAAQ;AACzB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,YACpB,MACA,OAAwB,CAAC,GACR;AACjB,QAAM,cAAc,KAAK,OAAO,QAAQ,IAAI;AAC5C,QAAM,UAAU,KAAK,KAAK;AAE1B,MAAI,CAAC,SAAS;AACZ,YAAQ,MAAM,GAAG,IAAI,gCAAgC,CAAC;AACtD,WAAO;AAAA,EACT;AAEA,MAAI;AACJ,MAAI,KAAK,OAAO;AACd,UAAM,YAAY,cAAc,KAAK,KAAK;AAC1C,QAAI,CAAC,WAAW;AACd,cAAQ;AAAA,QACN,GAAG,IAAI,gCAAgC,KAAK,KAAK,GAAG;AAAA,MACtD;AACA,aAAO;AAAA,IACT;AACA,YAAQ;AAAA,EACV,OAAO;AACL,YAAQ,WAAW,OAAO;AAAA,EAC5B;AAEA,QAAM,gBAAgB,mBAAmB;AACzC,QAAM,cACJ,KAAK,UAAU,kBAAkB,YAAY,gBAAgB;AAE/D,QAAM,QAAQ,YAAY,aAAa;AAAA,IACrC,MAAM;AAAA,IACN;AAAA,IACA,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,gBAAgB,KAAK;AAAA,EACvB,CAAC;AAED,QAAM,MAAM,SAAS,QAAQ,IAAI,GAAG,WAAW,KAAK;AACpD,EAAE,MAAI;AAAA,IACJ,UAAU,GAAG,KAAK,MAAM,KAAK,EAAE,CAAC,KAAK,GAAG,KAAK,MAAM,KAAK,KAAK,CAAC,YAAO,GAAG;AAAA,EAC1E;AACA,EAAE,MAAI;AAAA,IACJ,GAAG,IAAI,qEAAqE;AAAA,EAC9E;AACA,SAAO;AACT;","names":[]}
|