oh-my-design-cli 1.7.2 → 1.8.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/lib/preferences-parser.cjs +10 -1
- package/.claude/hooks/lib/preferences-writer.cjs +118 -0
- package/.claude/hooks/post-edit-watch.cjs +217 -29
- package/.claude/hooks/session-end-foldin.cjs +61 -5
- package/.claude/hooks/session-state-loader.cjs +49 -1
- package/README.ja.md +1 -1
- package/README.ko.md +2 -2
- package/README.md +2 -2
- package/README.zh-TW.md +1 -1
- package/agents/omd-master.md +8 -5
- package/agents/omd-ux-engineer.md +9 -7
- package/agents/omd-ux-writer.md +1 -1
- package/dist/bin/oh-my-design.js +1 -1
- package/dist/{install-skills-KDW74C5K.js → install-skills-7UUDOLG2.js} +28 -24
- package/dist/install-skills-7UUDOLG2.js.map +1 -0
- package/package.json +2 -1
- package/skills/omd-designer-review/SKILL.md +34 -0
- package/skills/omd-final-qa/SKILL.md +29 -0
- package/skills/omd-harness/SKILL.md +8 -1
- package/skills/omd-init/SKILL.md +7 -11
- package/skills/omd-kr-writer/SKILL.md +73 -3
- package/skills/omd-learn/SKILL.md +20 -0
- package/skills/omd-reference-capture/SKILL.md +10 -6
- package/skills/omd-taste/SKILL.md +79 -0
- package/dist/install-skills-KDW74C5K.js.map +0 -1
|
@@ -20,7 +20,7 @@ inputs:
|
|
|
20
20
|
...
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
지원 preset 12개.
|
|
23
|
+
지원 preset 12개. 각 preset의 핵심 spec은 아래 **§9 (preset 요약 spec)** 에 self-contained로 수록 — 이 SKILL.md만으로 12개 preset 전부 실행 가능. (풀 9-field spec + verbatim 한국어 examples + 출처는 dev 레포의 `data/research/2026-05-18-kr-style-presets.md` — npx 배포본에는 미포함.)
|
|
24
24
|
|
|
25
25
|
| # | preset_id | 종결 | 분량 (한글) | 용도 |
|
|
26
26
|
|---|---|---|---|---|
|
|
@@ -39,11 +39,11 @@ inputs:
|
|
|
39
39
|
|
|
40
40
|
**호출 시 preset 미지정 → `toss-tech-design` 적용.**
|
|
41
41
|
|
|
42
|
-
본 문서의 § 1~8은 default preset (`toss-tech-design`)의 상세 spec. 다른 preset 선택 시
|
|
42
|
+
본 문서의 § 1~8은 default preset (`toss-tech-design`)의 상세 spec. 다른 preset 선택 시 §9의 해당 preset 요약 spec 룰로 substitution.
|
|
43
43
|
|
|
44
44
|
### 전환 매트릭스 (자주 쓰는 6개)
|
|
45
45
|
|
|
46
|
-
같은 콘텐츠를 다른 preset으로 옮길 때 standard rewrite
|
|
46
|
+
같은 콘텐츠를 다른 preset으로 옮길 때 standard rewrite 룰 — 아래 핵심 변환 + §9 spec만으로 수행 가능. (확장 5-step rule은 dev 레포 research doc §14 — 배포본 미포함.)
|
|
47
47
|
|
|
48
48
|
| from | to | 핵심 변환 |
|
|
49
49
|
|---|---|---|
|
|
@@ -224,6 +224,76 @@ Toss 글은 단락 2~3개 당 이미지 1개. oh-my-design 본문에서:
|
|
|
224
224
|
- 첫 단락 "안녕하세요" 인사 포함
|
|
225
225
|
- 가져가도 좋은 것 / 따라하면 안 되는 것 = 각 5개
|
|
226
226
|
|
|
227
|
+
## 9. Preset 요약 spec (non-default 11개)
|
|
228
|
+
|
|
229
|
+
toss-tech-design 외 preset 호출 시 아래 요약 spec이 §1 (voice) / §5 (안티패턴) / §6 (어휘) 룰을 대체한다. 분량은 §0 표의 분량 컬럼, 구조(§2~4)는 preset 성격에 맞게 준용.
|
|
230
|
+
|
|
231
|
+
### karrot-neighborly — 당근 동네체
|
|
232
|
+
- register: 해요체 (가끔 "~답니다"/"~지요") · 문장 25~45자
|
|
233
|
+
- opening: "안녕하세요, [동네] 이웃 여러분." / closing: "오늘도 따뜻한 하루 보내세요."
|
|
234
|
+
- 금지: 차가운 비즈니스 한자어("고객", "유저") · 외래어 남발("커뮤니티"보단 "동네") · 단정적 -다 종결
|
|
235
|
+
- 선호: "동네/이웃/우리/근처" lexicon · 다정한 호명("이웃님") · 골목·계절·온도가 묻어나는 묘사어
|
|
236
|
+
|
|
237
|
+
### brunch-maker-popular — 브런치 에세이체
|
|
238
|
+
- register: 해요체 80% + 회상 해체 혼용 (1~2문단당 한 번 "-했다") · 문장 40~80자, 시처럼 끊기는 호흡
|
|
239
|
+
- opening: 개인 일화 한 줄 ("그날 저녁, 나는 ~") / closing: 여운 남기는 단문 ("그래서 나는 오늘도 ~.")
|
|
240
|
+
- 금지: 보고서식 bullet · 차트 인용 · 단정적 톤("~해야 한다")
|
|
241
|
+
- 선호: 1인칭 "나"의 일화 · 감각어(냄새·빛·소리) · 단문/만연체 교차 · 은유 · 짧은 의문문("정말 그럴까?")
|
|
242
|
+
|
|
243
|
+
### naver-d2-engineering — NAVER D2 기술체
|
|
244
|
+
- register: 하십시오체(-합니다/-입니다) · 문장 40~70자, 명확한 주어-서술 구조
|
|
245
|
+
- opening: "안녕하세요. [팀명] [이름]입니다. 이 글에서는 ~을 다룹니다." / closing: "이 글이 ~ 도움이 되길 바랍니다."
|
|
246
|
+
- 금지: 과도한 1인칭 감정 표현 · 비속어/인터넷 약어 · "~해요" 혼용 (문서 내 통일성)
|
|
247
|
+
- 선호: 정확한 기술 용어 + 영문 병기 · "먼저/다음으로/마지막으로" 구조 신호어 · 코드 블록·도식과 본문 1:1
|
|
248
|
+
|
|
249
|
+
### biz-formal-report — 보고서 사무체
|
|
250
|
+
- register: 하십시오체(-합니다) 또는 명사형 종결(-함/-임/-하였음) · 문장 30~55자, 짧고 단정
|
|
251
|
+
- opening: "1. 개요 / 본 보고서는 ~을 목적으로 작성되었습니다." / closing: "이상입니다." / "끝."
|
|
252
|
+
- 금지: 1인칭 "저/우리" 남발 (회사·부서 단위로 대체) · 감정 형용사 · 의문문 · 이모지/외래어 남용
|
|
253
|
+
- 선호: 섹션 번호(1.1, 1.2.1) · 명사형 종결("진행함", "확인됨") · 사무 한자어("검토/보고/회신/추진") · 결론→근거 역피라미드 · 핵심 수치 bold
|
|
254
|
+
|
|
255
|
+
### academic-paper — 학술 논문체
|
|
256
|
+
- register: 해라체(-한다/-이다) 평어체 · 문장 50~90자, 복문 빈번
|
|
257
|
+
- opening: "본 연구는 ~을 목적으로 한다." / closing: "후속 연구에서는 ~을 다룰 필요가 있다." / "본 연구의 한계는 ~이다."
|
|
258
|
+
- 금지: 비격식체 · 1인칭 단수("필자"·"본 연구자"로 대체) · 감정 형용사 · 인용 없는 단정
|
|
259
|
+
- 선호: 한자어 비중 높음("선행연구/유의수준/검정") · 수동·피동("측정되었다") · 괄호 인용(저자, 연도) · "따라서/그러나/한편" 접속어
|
|
260
|
+
|
|
261
|
+
### journalism-broadsheet — 신문 기사체
|
|
262
|
+
- register: 해라체(-다/-었다) · 문장 35~60자, 리드 문장 명확
|
|
263
|
+
- opening: 리드 = 핵심 사실 한 줄 ("~가 ~했다고 ~일 밝혔다.") / closing: "한편, ~." (배경 정보 마무리)
|
|
264
|
+
- 금지: 1인칭 (기명 칼럼 제외) · "~요/~네요" · 추측만 나열 · 감정 형용사
|
|
265
|
+
- 선호: 5W1H 리드 · 직접 인용("~라고 밝혔다/말했다/강조했다") · 출처 표기("OOO 대표") · "한편/이에 대해/앞서"
|
|
266
|
+
|
|
267
|
+
### kakao-warm-product — 카카오 따뜻한 프로덕트체
|
|
268
|
+
- register: 해요체, Toss보다 약간 더 감정적 · 문장 20~40자 (microcopy는 더 짧게)
|
|
269
|
+
- opening: "~ 축하해요!" / "~을(를) 시작해볼까요?" / closing: "오늘도 좋은 하루 보내세요." / "더 알아보기 →"
|
|
270
|
+
- 금지: 비즈니스 한자어("승인/수행/처리" → 부드러운 동사) · 부정·경고 직설("실패/오류" → "다시 시도해볼까요?") · 명령형("입력하시오" → "입력해주세요")
|
|
271
|
+
- 선호: "축하해요/고마워요/함께해요" · "혜택/선물/응원" 어휘 · 짧은 의문문("같이 시작해볼까요?")
|
|
272
|
+
|
|
273
|
+
### line-global-saas — LINE 한국어 (cross-locale)
|
|
274
|
+
- register: 하십시오체(-합니다), D2보다 한자어 비중 낮음 · 문장 35~55자, 번역 친화적 단순 구조
|
|
275
|
+
- opening: "이 글에서는 ~을 소개합니다." / "안녕하세요, LINE의 ~입니다." / closing: "감사합니다." / "다음 글에서 ~을 이어 다루겠습니다."
|
|
276
|
+
- 금지: 한국 내수 전용 비유·시사 (동시 번역 시 문제) · 4자 숙어("일석이조") · 비격식체
|
|
277
|
+
- 선호: SVO 단순 구조·능동태 · 영문 약어 병기("LINE 디자인 시스템(LDS)") · terminology 통일 · 중립 호명("독자/사용자")
|
|
278
|
+
|
|
279
|
+
### academic-lecture-essay — 교양 강연체
|
|
280
|
+
- register: 해요체 우세 + 학술 한자어 자유 혼용 · 문장 40~70자, 강의처럼 호흡 길게
|
|
281
|
+
- opening: "여러분, ~을 한 번 생각해볼까요?" / closing: "결국 ~인 거죠." / "그래서 저는 ~라고 생각해요."
|
|
282
|
+
- 금지: 보고서식 bullet · 인용 없는 단정
|
|
283
|
+
- 선호: "~인 거죠/~라는 거예요" · 청자에게 묻는 구조("그렇다면 왜 그럴까요?") · 비유와 일상 예시 · 학문 한자어 즉시 풀이("엔트로피, 그러니까 무질서의 정도")
|
|
284
|
+
|
|
285
|
+
### emotional-brand — 패션 커머스 트렌디체
|
|
286
|
+
- register: 해요체 + 체언/명사 종결 자유 혼용("오늘의 픽.") · 헤드라인 15~30자, 본문 40자 내외
|
|
287
|
+
- opening: 명사형 후크 ("이번 주의 무드." / "여름밤, 우리가 입어야 할 것.") / closing: "지금, [브랜드]에서." / "더 보기 →"
|
|
288
|
+
- 금지: 보고서 한자어 · "~합니다" 일변도 (브랜드 거리감) · 길고 설명적인 문장
|
|
289
|
+
- 선호: 외래어·영문 자유("무드/감도/베이직/에디트") · 컬러·재질·실루엣 어휘 · 시즌·시간성 cue("이번 주/오늘 밤/5월의") · 짧은 명령형("입어볼 것.")
|
|
290
|
+
|
|
291
|
+
### legal-disclosure — 법무 약관체
|
|
292
|
+
- register: 하십시오체(-합니다) 우세 + 명사 종결("~함") · 문장 50~100자, 의도적으로 길고 한정조건 빽빽
|
|
293
|
+
- opening: "본 약관은 ~을 규정함을 목적으로 합니다." / "제1조 (목적)" / closing: "본 약관은 ~일부터 시행합니다." / "부칙. ~"
|
|
294
|
+
- 금지: 1·2인칭 친밀 표현 · 비격식체 · 감정 형용사 · 모호한 수식("좀", "약간")
|
|
295
|
+
- 선호: "~합니다/~됩니다/~할 수 있습니다" · 수동·피동("간주됩니다/처리됩니다") · 정의 조항("이 약관에서 사용하는 용어의 정의는 다음과 같습니다.") · 법률 한자어("의무/책임/면책/준수") · 조·항·호 번호 체계
|
|
296
|
+
|
|
227
297
|
---
|
|
228
298
|
|
|
229
299
|
이 스킬은 oh-my-design 블로그 한국어 본문의 **단일 source of truth voice guide**. 모든 KR 글은 이 guide를 따른다.
|
|
@@ -83,6 +83,23 @@ spacing (1 pending):
|
|
|
83
83
|
Review .omd/preferences.md for details.
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
## Fold-in 제안에서 호출된 경우 (`.omd/foldin-proposal.json`)
|
|
87
|
+
|
|
88
|
+
SessionStart 컨텍스트의 OMD FOLD-IN PROPOSAL → AskUserQuestion 승인 경로로 호출되었으면 Phase 2 확인은 이미 끝난 것 — 다시 묻지 말 것.
|
|
89
|
+
|
|
90
|
+
제안 없이 사용자가 직접 omd:learn을 부른 경우에도 `.omd/foldin-proposal.json`이
|
|
91
|
+
`"status": "proposed"`로 존재하면: 그 scopes를 이번 폴드 대상에 포함할지 Phase 2에서
|
|
92
|
+
함께 확인하고, 처리 후 아래와 동일하게 status를 갱신한다 (proposed인 채로 방치 금지 —
|
|
93
|
+
다음 세션이 또 물어본다).
|
|
94
|
+
|
|
95
|
+
- **승인된 scope만** Phase 3-4로 처리. 미승인 scope의 pending 엔트리는 건드리지 않는다
|
|
96
|
+
- 처리 후 `.omd/foldin-proposal.json`의 status를 Edit 툴로 갱신:
|
|
97
|
+
- 전부 반영 → `"status": "applied"` + `"applied_at": "<ISO timestamp>"` 필드 추가
|
|
98
|
+
- 일부만 반영 → `"status": "partial"` + `scopes` 배열을 **남은(미승인) scope만**으로 갱신
|
|
99
|
+
- 전부 거절("나중에") → `"status": "snoozed"` + `"snoozed_at": "<ISO timestamp>"` 필드 추가
|
|
100
|
+
- status 값은 **JSON 계약상 영문 고정** (`proposed`/`applied`/`partial`/`snoozed`) —
|
|
101
|
+
번역·한글화 금지 (훅이 문자열 비교로 읽는다)
|
|
102
|
+
|
|
86
103
|
## 옵션 패턴
|
|
87
104
|
|
|
88
105
|
사용자가 특정 작업만 요청하는 경우:
|
|
@@ -91,6 +108,9 @@ Review .omd/preferences.md for details.
|
|
|
91
108
|
- **"X scope만 반영"** → 해당 scope만 Phase 3에서 처리
|
|
92
109
|
- **"<pref_id>를 applied로 표시"** → Phase 4의 single-entry 플립만
|
|
93
110
|
- **"<pref_id>를 rejected로 표시 + 이유"** → 동일
|
|
111
|
+
- 플립 전 현재 status를 먼저 Read로 확인: 이미 같은 값이면 no-op 보고,
|
|
112
|
+
`superseded`/`rejected` → `applied` 전환은 **금지** (이력 오염 — 사용자에게
|
|
113
|
+
"이 항목은 X 상태예요. 되살리려면 omd:remember로 재캡처하세요"라고 안내)
|
|
94
114
|
|
|
95
115
|
## 금지
|
|
96
116
|
|
|
@@ -102,12 +102,16 @@ id가 카탈로그에 없으면 종료 + "X는 reference 카탈로그에 없어
|
|
|
102
102
|
|
|
103
103
|
## Phase 2 — 라이브 URL 수집
|
|
104
104
|
|
|
105
|
-
**reference 자료 경로 `<refdir>`** 는
|
|
106
|
-
1. `.claude/data/references/<id>/` (installer가 복사 — npx 설치 기본 경로; **DESIGN.md만** 보장)
|
|
107
|
-
2. `node_modules/oh-my-design-cli/web/references/<id>/` (로컬 npm 설치 — _promo.json/_research.md 포함)
|
|
108
|
-
3. `web/references/<id>/` (개발 레포)
|
|
105
|
+
**reference 자료 경로 `<refdir>`** 는 reference DESIGN.md 위치 기준으로 resolve (먼저 존재하는 것 사용 — omd:init Phase 4.1과 동일한 카탈로그 resolution order):
|
|
109
106
|
|
|
110
|
-
|
|
107
|
+
<!-- omd:catalog-resolution-order — omd-init/omd-harness SKILL.md + agents/omd-master.md 와 동일 순서 강제. drift guard: test/unit/core/catalog-resolution-order.test.ts -->
|
|
108
|
+
|
|
109
|
+
1. `.claude/data/references/<id>/DESIGN.md` (installer가 복사 — npx 설치 기본 경로; 디렉토리에는 **DESIGN.md만** 보장)
|
|
110
|
+
2. `node_modules/oh-my-design-cli/web/references/<id>/DESIGN.md` (로컬 npm 설치 직접 경로 — 디렉토리에 _promo.json/_research.md 포함)
|
|
111
|
+
3. `web/references/<id>/DESIGN.md` (개발 레포)
|
|
112
|
+
4. `https://oh-my-design.kr/design-systems/<id>.md` 를 fetch (WebFetch 또는 `curl -fsSL`) — 1~3 로컬 경로가 전부 없을 때. 200이면 본문이 곧 reference DESIGN.md. 가져온 내용을 `.claude/data/references/<id>/DESIGN.md`로 캐시해 다음부터는 로컬 캐시(경로 1)로 잡히게 한다.
|
|
113
|
+
|
|
114
|
+
`<refdir>` = resolve된 DESIGN.md가 있는 디렉토리 (tier 4로 fetch한 경우 캐시 후 `.claude/data/references/<id>/`). `_promo.json`/`_research.md`는 (1)/(4)에 없을 수 있으니, 없으면 (2)/(3)로 폴백하고 그래도 없으면 fingerprints 기반 추론으로 진행.
|
|
111
115
|
|
|
112
116
|
다음을 순서대로 시도:
|
|
113
117
|
|
|
@@ -381,7 +385,7 @@ playwright MCP를 사용해 homepage에 navigate한 뒤 computed styles를 추
|
|
|
381
385
|
}
|
|
382
386
|
```
|
|
383
387
|
|
|
384
|
-
inspect
|
|
388
|
+
inspect 패턴: 5-10 elements 샘플링 (`body` / `header nav` / hero CTA / card / `footer` 류 대표 element의 getComputedStyle 추출 — Phase 3.9의 JS 패턴과 동일한 방식). 캡쳐하려는 brand가 카탈로그에 없으면 웹 카탈로그(`oh-my-design.kr/design-systems`)에서 먼저 찾아보고, 카탈로그에 새 brand를 직접 추가하는 워크플로우(omd-add-reference)는 dev 레포(github.com/kwakseongjae/oh-my-design)에만 존재 — 배포본(npx 설치)에는 포함되지 않는다.
|
|
385
389
|
|
|
386
390
|
## Phase 4.5 — 구조 cue 캡쳐 (structure.json)
|
|
387
391
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: omd:taste
|
|
3
|
+
description: "시스템이 학습한 사용자의 디자인 취향을 한 뷰로 렌더. .omd/preferences.md + foldin-proposal.json + DESIGN.md를 읽어 반영됨/대기 중/보류됨/모르는 것 4그룹으로 보여준다. '/omd-taste', '내 취향 보여줘', '취향 현황', 'what are my preferences', 'show my taste', 「私の好みを見せて」, 「顯示我的偏好」류의 발화에 트리거. 기록은 omd:remember, DESIGN.md 반영은 omd:learn."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# omd:taste — Taste Dashboard
|
|
8
|
+
|
|
9
|
+
시스템이 사용자에 대해 알고 있는 취향을 **한 뷰**로 렌더한다. 신뢰는 가시성에서 나온다 — 루프가 뭘 배웠고, 뭘 기다리고, 뭘 모르는지 보여주면 사용자가 루프에 더 먹인다. 기본 동작은 **읽기 전용** — 기록은 `omd:remember`, 반영은 `omd:learn`의 몫이고, 이 스킬은 보여주고 다음 행동만 안내한다. **CLI 호출 없음** — Read/Edit 툴로 직접 처리.
|
|
10
|
+
|
|
11
|
+
## 입력 (모두 Read 툴)
|
|
12
|
+
|
|
13
|
+
1. `.omd/preferences.md` — canonical 포맷 (`## <heading>` + ` ```omd-meta` 블록 + body — `omd:remember` 스킬의 포맷 정의 참조). **모든 status**의 엔트리를 파싱: `id` / `timestamp` / `scope` / `signal` / `confidence` / `status`
|
|
14
|
+
2. `.omd/foldin-proposal.json` — 있으면 `status` (`proposed` / `applied` / `partial` / `snoozed`)와 `scopes[]` (scope, count, score, summary)
|
|
15
|
+
3. `DESIGN.md` — § 라우팅 표시 + "모르는 것" 축 도출용
|
|
16
|
+
|
|
17
|
+
## 빈 상태
|
|
18
|
+
|
|
19
|
+
`.omd/preferences.md`가 없거나 파싱된 엔트리가 0개면 두 줄로 끝낸다:
|
|
20
|
+
|
|
21
|
+
> 아직 학습된 취향이 없어요.
|
|
22
|
+
> 작업 중 "앞으로는 ~로 해"라고 말하면 omd:remember가 기록하고, 같은 취향이 쌓이면 omd:learn이 DESIGN.md에 정식 반영해요.
|
|
23
|
+
|
|
24
|
+
이때 `.omd/` 디렉토리나 파일을 **만들지 말 것** — 뷰 스킬이 상태를 생성하면 안 된다.
|
|
25
|
+
|
|
26
|
+
## 렌더 — 4 그룹 한 뷰
|
|
27
|
+
|
|
28
|
+
### ① 반영됨 (`status: applied`)
|
|
29
|
+
|
|
30
|
+
scope별 한 줄: body 요약 + 반영된 DESIGN.md 섹션 + `applied_at` (있으면). 섹션은 omd:learn Phase 3의 scope→§ 라우팅으로 도출:
|
|
31
|
+
|
|
32
|
+
| scope | DESIGN.md § |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `components.*` | §8 (Components) 또는 §13 |
|
|
35
|
+
| `color` | §2 (Color Palette) |
|
|
36
|
+
| `typography` | §3 |
|
|
37
|
+
| `spacing` | §4 |
|
|
38
|
+
| `voice` | §10 (Voice & Tone) |
|
|
39
|
+
| `motion` | §15 (Motion & Easing) |
|
|
40
|
+
| `visualTheme` | §1 (Visual Theme) |
|
|
41
|
+
|
|
42
|
+
DESIGN.md에 해당 섹션이 실제로 없으면 § 표기는 생략 (추측으로 적지 말 것).
|
|
43
|
+
|
|
44
|
+
### ② 대기 중 (`status: pending`)
|
|
45
|
+
|
|
46
|
+
scope별로 그룹화해 한 그룹당 한 줄:
|
|
47
|
+
|
|
48
|
+
- **발생 횟수** — 해당 scope의 pending 엔트리 수
|
|
49
|
+
- **confidence** — explicit / inferred 구성 (예: `explicit ×2, inferred ×1`)
|
|
50
|
+
- **한 줄 요약** — 최신 엔트리 body의 첫 줄
|
|
51
|
+
- **폴드 제안까지 남은 횟수** — 자동 fold-in 게이트(session-end hook)의 기본 임계는 **최근 7일 창 안에서 같은 scope 3회**. `max(0, 3 - 7일 내 발생 횟수)`로 계산해 "N회 더 쌓이면 제안돼요"로 표기. 이미 충족이면 "다음 세션 종료 시 제안 예정"
|
|
52
|
+
|
|
53
|
+
### ③ 보류됨 (snoozed)
|
|
54
|
+
|
|
55
|
+
`.omd/foldin-proposal.json`의 `status`가 `snoozed`면 그 `scopes[]`를 나열 — scope + summary + `snoozed_at`. "지금 반영하려면 omd:learn을 부르세요" 한 줄 덧붙임. proposal 파일이 없거나 snoozed가 아니면 이 그룹은 생략.
|
|
56
|
+
|
|
57
|
+
### ④ 시스템이 모르는 것 (가볍게)
|
|
58
|
+
|
|
59
|
+
DESIGN.md에는 정의돼 있지만 preference 엔트리(어느 status든)로 한 번도 확인된 적 없는 주요 축을 **1-2개만** 짚는다 (예: "motion은 DESIGN.md 기본값 그대로 — 아직 취향을 들은 적이 없어요"). 절대 전 축을 나열하지 말 것 — 가볍게 한두 줄.
|
|
60
|
+
|
|
61
|
+
## 다음 행동 안내 (뷰 끝에 항상)
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
다음 중 하나로 이어갈 수 있어요:
|
|
65
|
+
- "지금 반영" → omd:learn — pending을 DESIGN.md에 fold
|
|
66
|
+
- "잊어줘 <id|scope>" → 해당 엔트리 status를 rejected로 플립
|
|
67
|
+
- "수정: <내용>" → omd:remember로 재기록 (옛 엔트리는 superseded)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- **"지금 반영"** → `omd:learn` 트리거 (전체 또는 사용자가 지정한 scope만)
|
|
71
|
+
- **"잊어줘"** → 해당 엔트리의 omd-meta 블록만 Edit 툴로 `status: rejected` + `rejected_reason: "user-forget"` 플립 (omd:learn의 single-entry 플립과 동일 절차). body는 건드리지 않는다
|
|
72
|
+
- **"수정"** → body 직접 편집 금지 (영구 기록) — `omd:remember`로 새 엔트리 재기록 후 옛 엔트리를 `status: superseded` + `superseded_by: <새 id>`로 플립
|
|
73
|
+
|
|
74
|
+
## 금지
|
|
75
|
+
|
|
76
|
+
- "잊어줘"/"수정" 후속 요청의 status 플립 외에는 어떤 파일도 수정하지 말 것 — DESIGN.md 수정은 항상 omd:learn 경유
|
|
77
|
+
- 엔트리 전체 dump 금지 — scope 그룹 요약으로
|
|
78
|
+
- ④번 그룹을 채우려고 억지 축을 만들지 말 것 — 짚을 게 없으면 생략
|
|
79
|
+
- preferences.md 포맷 해석은 canonical 규칙(omd:remember)만 따를 것 — 자체 변형 포맷 발명 금지
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/install-skills.ts","../src/core/agent-detect.ts"],"sourcesContent":["import * as p from '@clack/prompts';\nimport pc from 'picocolors';\nimport {\n readFileSync,\n readdirSync,\n writeFileSync,\n existsSync,\n mkdirSync,\n cpSync,\n} from 'node:fs';\nimport { join, dirname, relative } from 'node:path';\nimport { homedir } from 'node:os';\nimport { createHash } from 'node:crypto';\nimport { fileURLToPath } from 'node:url';\nimport { detectInstalledAgents } from '../core/agent-detect.js';\n\nexport type SkillTarget = 'claude-code' | 'codex' | 'opencode' | 'cursor';\n\n/** Channels that host SKILL.md trees. Cursor is NOT one — it consumes a\n * `.cursor/rules` shim + the shared `.claude/data` catalog (issue #20). */\ntype SkillChannel = Exclude<SkillTarget, 'cursor'>;\n\nexport interface InstallSkillsOptions {\n dir?: string;\n agents?: SkillTarget[];\n force?: boolean;\n /** Non-interactive: install all skills + all agents without TUI prompt.\n * Default false → interactive multiselect when TTY available. */\n all?: boolean;\n /** Pre-select specific skill names from CLI flag (`--skills omd-init,omd-apply`).\n * Overrides interactive prompt when set. */\n skillsFilter?: string[];\n /** Pre-select specific agent names. Overrides interactive prompt when set. */\n agentsFilter?: string[];\n /** Minimal install: only the named skill files — skip sub-agents, data files,\n * hooks, and settings.json. Ideal for shipping a single standalone skill. */\n skillsOnly?: boolean;\n /** Install to the user-level dir (~/.claude/skills) instead of this project.\n * Writes skills + sub-agents (+ data); never touches global hooks/settings.\n * When unset and interactive, the TUI asks project-vs-global. */\n global?: boolean;\n}\n\ninterface InstallPlan {\n target: SkillChannel;\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\n/**\n * Canonical agent definitions live at `agents/<name>.md` (markdown with YAML\n * frontmatter). Channel-specific files (.claude/agents/*.md, .codex/agents/*.toml)\n * are generated artifacts — never the source of truth.\n *\n * The package ships only `agents/` and the generator emits per-channel files\n * into the user's project on `omd install-skills`.\n */\nfunction listCanonicalAgents(packageRoot: string): string[] {\n const dir = join(packageRoot, 'agents');\n if (!existsSync(dir)) return [];\n return readdirSync(dir)\n .filter((name) => name.startsWith('omd-') && name.endsWith('.md'))\n .sort();\n}\n\ninterface ParsedAgent {\n name: string;\n description: string;\n tools: string[];\n model: string;\n body: string;\n}\n\n/** Parse `agents/<name>.md` YAML frontmatter + body into structured form. */\nfunction parseCanonicalAgent(packageRoot: string, filename: string): ParsedAgent {\n const src = readFileSync(join(packageRoot, 'agents', filename), 'utf8');\n const match = /^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/.exec(src);\n if (!match) {\n throw new Error(`agents/${filename}: missing YAML frontmatter`);\n }\n const fm = match[1];\n const body = match[2];\n const grab = (key: string): string => {\n const re = new RegExp(`^${key}:\\\\s*(.+)$`, 'm');\n const m = re.exec(fm);\n return m ? m[1].trim().replace(/^[\"']|[\"']$/g, '') : '';\n };\n return {\n name: grab('name') || filename.replace(/\\.md$/, ''),\n description: grab('description'),\n tools: grab('tools')\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean),\n model: grab('model') || 'sonnet',\n body,\n };\n}\n\n/** Map Claude tool names to Codex tool names (best-effort). */\nfunction claudeToolsToCodex(tools: string[]): string[] {\n const m: Record<string, string> = {\n Read: 'read_file',\n Write: 'write_file',\n Edit: 'edit_file',\n Bash: 'shell',\n Glob: 'search',\n Grep: 'search',\n WebFetch: 'web_fetch',\n WebSearch: 'search',\n Agent: 'spawn_agent',\n TaskCreate: 'task',\n TaskUpdate: 'task',\n TaskList: 'task',\n };\n const out = new Set<string>();\n for (const t of tools) out.add(m[t] ?? t.toLowerCase());\n return [...out].sort();\n}\n\n/** Map Claude model alias to Codex/OpenAI model id (best-effort). */\nfunction claudeModelToCodex(model: string): string {\n const m: Record<string, string> = {\n haiku: 'gpt-4.1-mini',\n sonnet: 'gpt-4.1',\n opus: 'gpt-4.1',\n };\n return m[model.toLowerCase()] ?? 'gpt-4.1';\n}\n\n/** Render a canonical agent as a Claude Code subagent file.\n * IMPORTANT: Claude Code's subagent parser requires YAML frontmatter (`---`)\n * as the FIRST line of the file. Any preceding content (HTML comments, blank\n * lines) breaks discovery. So we encode the managed-by-omd marker as a\n * custom frontmatter field (`omd_managed: true`) instead of an HTML comment.\n */\nfunction renderClaudeAgent(a: ParsedAgent): string {\n const fm = [\n '---',\n `name: ${a.name}`,\n `description: ${a.description}`,\n `tools: ${a.tools.join(', ')}`,\n `model: ${a.model}`,\n `omd_managed: true`,\n '---',\n '',\n ].join('\\n');\n return fm + a.body;\n}\n\n/** Render a canonical agent as a Codex TOML file (declarative pointer). */\nfunction renderCodexAgent(a: ParsedAgent): string {\n const tools = claudeToolsToCodex(a.tools);\n const model = claudeModelToCodex(a.model);\n const desc = a.description.replace(/\"/g, '\\\\\"');\n return [\n `[agent]`,\n `name = \"${a.name}\"`,\n `description = \"${desc}\"`,\n `model = \"${model}\"`,\n `max_threads = 1`,\n `allowed_tools = [${tools.map((t) => `\"${t}\"`).join(', ')}]`,\n '',\n `instructions = \"\"\"`,\n `Source of truth: agents/${a.name}.md (canonical). The full role spec is`,\n `mirrored to .claude/agents/${a.name}.md when installed for Claude Code.`,\n `Follow that spec verbatim regardless of channel.`,\n '',\n `Codex notes:`,\n `- Spawn sub-agents via spawn_agent with names matching .codex/agents/<name>.toml`,\n `- Use shell to invoke CLI helpers (omd init prepare, omd remember, git apply, npx axe-core, npx lighthouse)`,\n `- All artifacts go inside .omd/runs/run-<latest>/ (or skills/omd-lab-02-design-harness/runs/<lab-version>-...)`,\n `\"\"\"`,\n '',\n ].join('\\n');\n}\n\nfunction planForTarget(projectRoot: string, target: SkillChannel): 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 // Official Codex skill discovery path is `.agents/skills/<name>/SKILL.md`\n // (developers.openai.com/codex/skills) — NOT `.codex/skills`. Folder layout\n // so multi-file skills (scripts/, references/) install + run.\n return {\n target,\n destDir: join(projectRoot, '.agents', 'skills'),\n layout: 'folder',\n };\n case 'opencode':\n // OpenCode loads `.opencode/skills/<name>/SKILL.md` (opencode.ai/docs/skills)\n // as folder skills — the old flat `.opencode/agents/<name>.md` couldn't host\n // a skill's scripts/references.\n return {\n target,\n destDir: join(projectRoot, '.opencode', 'skills'),\n layout: 'folder',\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\n// Substring shared by old (line 1) and new (after-frontmatter) marker formats.\n// Used for detection so upgrades from pre-v1.7.2 installs still refresh.\nconst MANAGED_MARKER_SUBSTR = 'omd:installed-skill';\n\n/**\n * Write the managed marker AFTER the YAML frontmatter block so the very first\n * line of the installed file is `---`. Claude Code's skill loader reads the\n * frontmatter `name`/`description` only when `---` is line 1 — a leading HTML\n * comment makes it register the comment as the description (issue #17).\n *\n * If the source has no frontmatter (shouldn't happen for SKILL.md, but be\n * defensive), fall back to prepending the marker.\n */\nfunction withManagedMarker(src: string): string {\n // \\r?\\n: a CRLF checkout (Windows core.autocrlf) must not miss the\n // frontmatter and fall back to a line-1 marker — that reintroduces #17.\n const fm = /^(---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n)([\\s\\S]*)$/.exec(src);\n if (!fm) {\n return MANAGED_HEADER + '\\n\\n' + src;\n }\n return fm[1] + MANAGED_HEADER + '\\n\\n' + fm[2];\n}\n\n/**\n * Detect an omd-managed installed-skill file. Matches both the new format\n * (marker after frontmatter) and the legacy format (marker on line 1) by\n * scanning the first ~30 lines for the marker substring. This keeps upgrades\n * working: a pre-v1.7.2 file with the marker at line 1 is still recognized as\n * managed and gets refreshed rather than skipped as user-edited drift.\n */\nfunction isManagedSkillFile(content: string): boolean {\n if (!content) return false;\n const head = content.split('\\n', 30).join('\\n');\n return head.includes(MANAGED_MARKER_SUBSTR);\n}\n\ninterface InstallResult {\n target: SkillTarget;\n skill: string;\n destPath: string;\n status: 'created' | 'updated' | 'unchanged' | 'skipped-drift' | 'skipped-incompat';\n}\n\n// Skill-tree entries that must never be installed (runtime state, caches, OS cruft).\nconst IGNORED_SKILL_ENTRIES = new Set(['.runtime', '__pycache__', '.DS_Store']);\n\n/**\n * A skill may restrict itself to specific agent channels via a frontmatter line\n * `x-omd-channels: claude-code` (comma/space separated). Returns the allowed\n * channels, or null when channel-agnostic (installs anywhere). Used by skills that\n * depend on a particular agent runtime — e.g. claude-design needs Claude Code's\n * claude-in-chrome MCP + Bash/python/node and is therefore claude-code only.\n */\nfunction parseSkillChannels(skillMd: string): SkillChannel[] | null {\n const fm = /^---\\n([\\s\\S]*?)\\n---/.exec(skillMd);\n if (!fm) return null;\n const m = /^x-omd-channels:\\s*(.+)$/m.exec(fm[1]);\n if (!m) return null;\n const valid: SkillChannel[] = ['claude-code', 'codex', 'opencode'];\n const list = m[1]\n .split(/[,\\s]+/)\n .map((s) => s.trim())\n .filter((s): s is SkillChannel => (valid as string[]).includes(s));\n return list.length > 0 ? list : null;\n}\n\n/**\n * The agent channels a skill can install into: its declared `x-omd-channels`\n * (if any), else all channels. All three channels now use folder layout\n * (.claude/skills, .agents/skills, .opencode/skills) so multi-file skills with\n * scripts/references install everywhere — the only restriction is what the skill\n * itself declares (e.g. claude-design needs a browser-driving runtime).\n */\nfunction skillSupportedChannels(packageRoot: string, skill: string): SkillChannel[] {\n return (\n parseSkillChannels(readFileSync(join(packageRoot, 'skills', skill, 'SKILL.md'), 'utf8')) ??\n (['claude-code', 'codex', 'opencode'] as SkillChannel[])\n );\n}\n\nfunction installOne(\n packageRoot: string,\n plan: InstallPlan,\n skill: string,\n force: boolean\n): InstallResult {\n const skillDir = join(packageRoot, 'skills', skill);\n const src = readFileSync(join(skillDir, 'SKILL.md'), 'utf8');\n // Marker goes AFTER frontmatter so `---` stays line 1 (issue #17).\n const managed = withManagedMarker(src);\n\n // Respect a skill's declared channel restriction (frontmatter `x-omd-channels:`).\n const channels = parseSkillChannels(src);\n if (channels && !channels.includes(plan.target)) {\n return {\n target: plan.target,\n skill,\n destPath: join(plan.destDir, skill + '.md'),\n status: 'skipped-incompat',\n };\n }\n\n // A skill is \"multi-file\" when it ships more than SKILL.md (scripts/, references/, …).\n const extras = readdirSync(skillDir).filter(\n (n) => n !== 'SKILL.md' && !IGNORED_SKILL_ENTRIES.has(n)\n );\n const isMultiFile = extras.length > 0;\n\n // Flat channels (codex/opencode) store a skill as a single <skill>.md and cannot\n // host a multi-file skill's scripts/references — such skills are claude-code only.\n if (plan.layout !== 'folder' && isMultiFile) {\n return {\n target: plan.target,\n skill,\n destPath: join(plan.destDir, skill + '.md'),\n status: 'skipped-incompat',\n };\n }\n\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 // Drift protection guards the user-editable SKILL.md. Single-file skills can\n // short-circuit on \"unchanged\"; multi-file skills always re-sync their tree.\n if (exists && existing === managed && !isMultiFile) {\n return { target: plan.target, skill, destPath, status: 'unchanged' };\n }\n // Drift = a file we didn't write. Detect the marker anywhere in the head\n // (new after-frontmatter position OR legacy line-1 position) so pre-v1.7.2\n // installs are recognized as managed and refreshed, not skipped.\n if (exists && !isManagedSkillFile(existing) && !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\n // Copy the rest of the skill tree (scripts/, references/, …) for folder layout.\n if (plan.layout === 'folder' && isMultiFile) {\n const destSkillDir = join(plan.destDir, skill);\n for (const entry of extras) {\n cpSync(join(skillDir, entry), join(destSkillDir, entry), {\n recursive: true,\n filter: (s) => !/(\\/__pycache__|\\/\\.runtime|\\.pyc$|\\.DS_Store$)/.test(s),\n });\n }\n }\n\n return {\n target: plan.target,\n skill,\n destPath,\n status: exists ? 'updated' : 'created',\n };\n}\n\n/** Install a hook script from package's .claude/hooks/ to project. */\nfunction installHookFile(\n packageRoot: string,\n projectRoot: string,\n filename: string,\n force: boolean\n): InstallResult {\n const target: SkillTarget = 'claude-code';\n const skillLabel = `hook:${filename}`;\n const srcPath = join(packageRoot, '.claude', 'hooks', filename);\n const destPath = join(projectRoot, '.claude', 'hooks', filename);\n\n if (!existsSync(srcPath)) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n const src = readFileSync(srcPath, 'utf8');\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n if (exists && existing === src) {\n return { target, skill: skillLabel, destPath, status: 'unchanged' };\n }\n if (exists && !force) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, src);\n return { target, skill: skillLabel, destPath, status: exists ? 'updated' : 'created' };\n}\n\n/**\n * Install / merge .claude/settings.json. We MERGE hooks (don't clobber user\n * customizations) by checking if the omd-managed `_doc` field is present.\n * Without --force, if a user-edited settings.json exists (no _doc field),\n * we skip with `skipped-drift`.\n */\nfunction installSettingsJson(\n packageRoot: string,\n projectRoot: string,\n force: boolean\n): InstallResult {\n const target: SkillTarget = 'claude-code';\n const skillLabel = 'settings:.claude/settings.json';\n const srcPath = join(packageRoot, '.claude', 'settings.json');\n const destPath = join(projectRoot, '.claude', 'settings.json');\n if (!existsSync(srcPath)) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n const src = readFileSync(srcPath, 'utf8');\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n if (exists && existing === src) {\n return { target, skill: skillLabel, destPath, status: 'unchanged' };\n }\n if (exists && !force) {\n // Check if it's the omd-managed version\n try {\n const parsed = JSON.parse(existing);\n if (typeof parsed._doc === 'string' && parsed._doc.includes('OmD')) {\n // managed — overwrite\n } else {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n } catch {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n }\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, src);\n return { target, skill: skillLabel, destPath, status: exists ? 'updated' : 'created' };\n}\n\n/**\n * Copy a read-only data asset (reference-fingerprints.json, vocabulary.json, …)\n * from the package's `data/` into the project's `.claude/data/` or `.codex/data/`.\n * The skill reads these at runtime — they replace the deprecated `omd init recommend` CLI.\n */\nfunction installDataFile(\n packageRoot: string,\n projectRoot: string,\n channelDir: string,\n dataFilename: string,\n force: boolean,\n // Cursor reuses the `.claude` data dir (single catalog path) — callers pass\n // an explicit target so the results table reports the real channel.\n target: SkillTarget = channelDir === '.claude' ? 'claude-code' : 'codex'\n): InstallResult {\n const skillLabel = `data:${dataFilename}`;\n\n const srcPath = join(packageRoot, 'data', dataFilename);\n const destPath = join(projectRoot, channelDir, 'data', dataFilename);\n\n if (!existsSync(srcPath)) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n\n const src = readFileSync(srcPath, 'utf8');\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n\n // Data files are pure copies — no managed header (would corrupt JSON).\n if (exists && existing === src) {\n return { target, skill: skillLabel, destPath, status: 'unchanged' };\n }\n if (exists && !force) {\n // Honor user customization unless --force\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, src, 'utf8');\n return {\n target,\n skill: skillLabel,\n destPath,\n status: exists ? 'updated' : 'created',\n };\n}\n\n/**\n * Generate a per-channel agent file from the canonical `agents/<name>.md`.\n *\n * Channel = 'claude' → emits `.claude/agents/<name>.md` (markdown w/ frontmatter)\n * Channel = 'codex' → emits `.codex/agents/<name>.toml` (TOML pointer)\n */\nfunction installAgentFile(\n packageRoot: string,\n projectRoot: string,\n channel: 'claude' | 'codex',\n filename: string,\n force: boolean\n): InstallResult {\n const target: SkillTarget = channel === 'claude' ? 'claude-code' : 'codex';\n const skillLabel = `agent:${filename}`;\n\n const parsed = parseCanonicalAgent(packageRoot, filename);\n const rendered =\n channel === 'claude' ? renderClaudeAgent(parsed) : renderCodexAgent(parsed);\n\n const destFilename =\n channel === 'claude' ? filename : filename.replace(/\\.md$/, '.toml');\n const destPath = join(\n projectRoot,\n channel === 'claude' ? '.claude' : '.codex',\n 'agents',\n destFilename\n );\n\n // For Claude Code: managed marker is encoded as `omd_managed: true` INSIDE the\n // frontmatter (rendered above) — no HTML comment can precede `---` or the\n // subagent loader rejects the file.\n // For Codex: TOML allows leading comments, so `# omd:installed-agent ...` is fine.\n const managed =\n channel === 'claude'\n ? rendered\n : '# omd:installed-agent — generated from agents/' +\n filename +\n ' by `omd install-skills`. Do not edit; rerun the command to refresh.\\n\\n' +\n rendered;\n\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n\n if (exists && existing === managed) {\n return { target, skill: skillLabel, destPath, status: 'unchanged' };\n }\n\n // Drift detection sentinels:\n // Claude — look for `omd_managed: true` line inside frontmatter\n // Codex — look for `# omd:installed-agent` comment\n const isManaged =\n channel === 'claude'\n ? /\\nomd_managed:\\s*true\\b/.test(existing)\n : existing.startsWith('# omd:installed-agent');\n\n if (exists && !isManaged && !force) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, managed, 'utf8');\n return {\n target,\n skill: skillLabel,\n destPath,\n status: exists ? 'updated' : 'created',\n };\n}\n\n/**\n * Copy the reference catalog (`web/references/<id>/DESIGN.md`) into the project's\n * `.claude/data/references/<id>/DESIGN.md` so it's reachable on clean npx installs\n * — where there is no `node_modules` and no dev `web/references` (issue #16).\n *\n * Only DESIGN.md per id is copied (not _promo.json/_research.md/screenshots) to\n * keep the install lean. Idempotent: skips ids whose DESIGN.md already matches.\n * Returns the number of catalog files written (created or updated).\n */\nfunction installReferenceCatalog(\n packageRoot: string,\n installRoot: string,\n channelDir: string,\n force: boolean\n): number {\n const srcRoot = join(packageRoot, 'web', 'references');\n if (!existsSync(srcRoot)) return 0;\n const destRoot = join(installRoot, channelDir, 'data', 'references');\n\n let written = 0;\n for (const id of readdirSync(srcRoot)) {\n const srcDesign = join(srcRoot, id, 'DESIGN.md');\n if (!existsSync(srcDesign)) continue;\n const destDesign = join(destRoot, id, 'DESIGN.md');\n const src = readFileSync(srcDesign, 'utf8');\n if (existsSync(destDesign)) {\n const existing = readFileSync(destDesign, 'utf8');\n if (existing === src) continue;\n if (!force) continue; // honor user edits unless --force\n }\n mkdirSync(dirname(destDesign), { recursive: true });\n writeFileSync(destDesign, src, 'utf8');\n written++;\n }\n return written;\n}\n\n/**\n * Cursor channel shim — Cursor has no skill/agent surface; it consumes a\n * project rule at `.cursor/rules/omd-design.mdc`. Frontmatter, body, and the\n * body-hash marker below mirror the omd:sync skill's cursor template EXACTLY\n * (skills/omd-sync/SKILL.md, \"whole\" mode: hash = sha256 of the body text,\n * 12-char hex prefix), so a later omd:sync run reads the installer-written\n * file as `clean` rather than drifted (issue #20).\n */\nconst CURSOR_RULE_BODY = [\n 'The authoritative design spec lives at `@DESIGN.md` (repo root). Open and read before generating/modifying UI.',\n '',\n 'Pending preference corrections: `@.omd/preferences.md`.',\n '',\n 'Precedence: DESIGN.md > preferences.md > framework defaults.',\n].join('\\n');\n\nfunction renderCursorRule(): string {\n const hash = createHash('sha256').update(CURSOR_RULE_BODY).digest('hex').slice(0, 12);\n return [\n '---',\n 'description: Authoritative brand & UI design system. Read DESIGN.md before UI work.',\n 'globs:',\n ' - \"**/*.tsx\"',\n ' - \"**/*.jsx\"',\n ' - \"**/*.vue\"',\n ' - \"**/*.svelte\"',\n ' - \"**/*.css\"',\n ' - \"**/*.scss\"',\n ' - \"**/tailwind.config.*\"',\n ' - \"**/components/**\"',\n ' - \"**/app/**/page.*\"',\n 'alwaysApply: false',\n '---',\n '',\n `<!-- omd:start v=1 hash=${hash} -->`,\n CURSOR_RULE_BODY,\n '<!-- omd:end -->',\n '',\n ].join('\\n');\n}\n\nfunction installCursorRule(installRoot: string, force: boolean): InstallResult {\n const target: SkillTarget = 'cursor';\n const skillLabel = 'rule:omd-design.mdc';\n const destPath = join(installRoot, '.cursor', 'rules', 'omd-design.mdc');\n const rendered = renderCursorRule();\n\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n if (exists && existing === rendered) {\n return { target, skill: skillLabel, destPath, status: 'unchanged' };\n }\n // The omd marker block doubles as the managed sentinel (matching omd:sync's\n // whole-mode rules): a file without it is user content → drift unless --force.\n if (exists && !existing.includes('<!-- omd:start') && !force) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, rendered, 'utf8');\n return { target, skill: skillLabel, destPath, status: exists ? 'updated' : 'created' };\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 'skipped-incompat': pc.yellow('skipped (claude-code only)'),\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 hosts no skills — its channel writes the .cursor/rules shim + the\n // shared .claude/data catalog (issue #20). Only when .cursor is detected;\n // the no-signal fallback below stays skill-channel-only so we never drop a\n // .cursor dir into projects that don't use Cursor.\n if (presence.cursor) targets.push('cursor');\n if (targets.length === 0) {\n // Fallback: install for all three skill channels so user gets coverage\n // even without 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 allSkills = listShippedSkills(packageRoot);\n if (allSkills.length === 0) {\n console.error(pc.red('omd install-skills: no skills found in package'));\n return 1;\n }\n const allAgents = listCanonicalAgents(packageRoot);\n\n const force = opts.force ?? false;\n const minimal = opts.skillsOnly === true;\n // Install scope: 'project' (<cwd>/.claude/…) or 'global' (~/.claude/…). --global\n // forces it; otherwise the interactive TUI asks. Global writes skills + sub-agents\n // (+ data) to the user-level dir but never touches global hooks/settings.json.\n let scope: 'project' | 'global' = opts.global ? 'global' : 'project';\n\n p.intro(\n pc.bold('omd install-skills') +\n pc.dim(` (${relative(process.cwd(), projectRoot) || '.'})`)\n );\n\n // Each dimension (scope / skills / sub-agents / channels) is resolved\n // independently: a CLI flag pins it; otherwise we prompt — but only when stdin\n // is a TTY and --all wasn't passed. This is the key fix: `--skills X` or\n // `--skills-only` no longer suppress the *channel* (where to install) prompt —\n // they only pin the dimension they name.\n const isTTY = Boolean(process.stdin.isTTY && process.stdout.isTTY);\n const interactive = isTTY && !opts.all;\n\n const detected = autoDetectTargets(projectRoot);\n // Real presence (not the all-3 fallback) — used for hint labels + prompt defaults.\n const presence = detectInstalledAgents(projectRoot);\n const actuallyDetected: SkillTarget[] = [\n presence.claudeCode ? 'claude-code' : null,\n presence.codex ? 'codex' : null,\n presence.opencode ? 'opencode' : null,\n presence.cursor ? 'cursor' : null,\n ].filter((x): x is SkillTarget => x !== null);\n\n // --- Scope (project vs global) — --global pins it, else ask / default project.\n if (!opts.global && interactive) {\n const scopeResult = await p.select({\n message: 'Install scope · 어디에 설치할까요?',\n options: [\n { value: 'project', label: 'Project', hint: `${relative(process.cwd(), projectRoot) || '.'}/.claude/skills · 이 프로젝트만` },\n { value: 'global', label: 'Global', hint: '~/.claude/skills · 모든 프로젝트 (skills + sub-agents, hooks/settings 제외)' },\n ],\n initialValue: 'project',\n });\n if (p.isCancel(scopeResult)) { p.cancel('Install cancelled.'); return 130; }\n scope = scopeResult as 'project' | 'global';\n }\n\n // --- Skills — --skills pins it, else ask / default ALL.\n let skills: string[];\n if (opts.skillsFilter) {\n skills = allSkills.filter((s) => opts.skillsFilter!.includes(s));\n } else if (interactive) {\n const skillResult = await p.multiselect({\n message: 'Skills · space = 토글 · a = 전체 · enter = 확인 (default ALL)',\n options: allSkills.map((s) => ({ value: s, label: s, hint: 'omd skill' })),\n initialValues: allSkills,\n required: true,\n });\n if (p.isCancel(skillResult)) { p.cancel('Install cancelled.'); return 130; }\n skills = skillResult as string[];\n } else {\n skills = allSkills;\n }\n\n // --- Sub-agents — dropped by --skills-only, else --agents pins, else ask / ALL.\n let canonicalAgents: string[];\n if (minimal) {\n canonicalAgents = [];\n } else if (opts.agentsFilter) {\n canonicalAgents = allAgents.filter((a) => opts.agentsFilter!.includes(a.replace(/\\.md$/, '')));\n } else if (interactive && allAgents.length > 0) {\n const agentResult = await p.multiselect({\n message: 'Sub-agents · space = 토글 · a = 전체 · enter = 확인 (default ALL)',\n options: allAgents.map((a) => ({ value: a, label: a.replace(/\\.md$/, ''), hint: 'subagent' })),\n initialValues: allAgents,\n required: false,\n });\n if (p.isCancel(agentResult)) { p.cancel('Install cancelled.'); return 130; }\n canonicalAgents = agentResult as string[];\n } else {\n canonicalAgents = allAgents;\n }\n\n // --- Channels / targets — the \"where do I install\" choice.\n // --agent pins it. Otherwise, in a TTY we ASK — limited to the channels the\n // selected skills actually support (claude-design is claude-code only, so its\n // picker shows just Claude Code). Non-TTY / --all falls back to auto-resolution.\n const supportedTargets = ((): SkillTarget[] => {\n const set = new Set<SkillTarget>(skills.flatMap((s) => skillSupportedChannels(packageRoot, s)));\n // Cursor consumes no skills — its channel install (.cursor/rules shim +\n // shared .claude/data catalog) is skill-independent, so always offer it.\n set.add('cursor');\n return (['claude-code', 'codex', 'opencode', 'cursor'] as SkillTarget[]).filter((t) => set.has(t));\n })();\n const channelLabel: Record<SkillTarget, string> = {\n 'claude-code': 'Claude Code',\n codex: 'Codex',\n opencode: 'OpenCode',\n cursor: 'Cursor',\n };\n const channelDir: Record<SkillTarget, string> = {\n 'claude-code': '.claude',\n codex: '.codex',\n opencode: '.opencode',\n cursor: '.cursor',\n };\n let targets: SkillTarget[];\n if (opts.agents) {\n targets = opts.agents;\n } else if (interactive) {\n const defaults = actuallyDetected.filter((t) => supportedTargets.includes(t));\n const targetResult = await p.multiselect({\n message: 'Agent channels · 어디에 설치할까요? · space = 토글 · enter = 확인',\n options: supportedTargets.map((t) => ({\n value: t,\n label: channelLabel[t],\n hint: actuallyDetected.includes(t) ? `${channelDir[t]}/ detected` : '',\n })) as { value: SkillTarget; label: string; hint?: string }[],\n initialValues: defaults.length > 0 ? defaults : supportedTargets,\n required: true,\n });\n if (p.isCancel(targetResult)) { p.cancel('Install cancelled.'); return 130; }\n targets = targetResult as SkillTarget[];\n } else {\n // Non-interactive (CI / piped / --all): resolve from flags + detection,\n // narrowed to channels the selected skills support.\n targets = opts.all\n ? (['claude-code', 'codex', 'opencode'] as SkillTarget[])\n : minimal\n ? (actuallyDetected.length > 0 ? actuallyDetected : (['claude-code'] as SkillTarget[]))\n : detected;\n const narrowed = targets.filter((t) => supportedTargets.includes(t));\n if (narrowed.length > 0) targets = narrowed;\n }\n\n // Global scope roots everything at the home dir, so plan dirs resolve to\n // ~/.claude/skills, ~/.claude/agents, etc. Project scope uses cwd (or --dir).\n const installRoot = scope === 'global' ? homedir() : projectRoot;\n // Cursor hosts no SKILL.md tree — it's excluded from skill plans and handled\n // below via the .cursor/rules shim + shared data copies (issue #20).\n const skillChannelTargets = targets.filter(\n (t): t is SkillChannel => t !== 'cursor'\n );\n const plans = skillChannelTargets.map((t) => planForTarget(installRoot, t));\n\n p.log.message(\n pc.bold('Scope: ') +\n pc.cyan(scope) +\n pc.dim(scope === 'global' ? ' (~/.claude)' : ` (${relative(process.cwd(), projectRoot) || '.'})`)\n );\n p.log.message(\n pc.bold(`Skills (${skills.length}): `) +\n skills.map((s) => pc.cyan(s)).join(', ')\n );\n if (minimal) {\n // --skills-only: sub-agents are intentionally skipped (minimal single-skill\n // install). Clear BEFORE the summary so we never print agents we won't write.\n canonicalAgents = [];\n p.log.message(pc.bold('Agents: ') + pc.dim('skipped (--skills-only)'));\n } else if (canonicalAgents.length > 0) {\n p.log.message(\n pc.bold(`Agents (${canonicalAgents.length}): `) +\n canonicalAgents.map((a) => pc.cyan(a.replace(/\\.md$/, ''))).join(', ')\n );\n }\n p.log.message(\n pc.bold('Targets: ') + targets.map((t) => pc.cyan(t)).join(', ')\n );\n\n const results: InstallResult[] = [];\n // Count of reference-catalog DESIGN.md files copied (issue #16) — surfaced in\n // the install summary. Declared here so the outro (outside `if (!minimal)`) sees it.\n let catalogCount = 0;\n for (const plan of plans) {\n for (const skill of skills) {\n results.push(installOne(packageRoot, plan, skill, force));\n }\n }\n\n // Generate per-channel sub-agent definitions from the canonical `agents/`.\n // This is the v2 portable source-of-truth pattern (oh-my-agent style).\n // `canonicalAgents` is already resolved above by the TUI / --agents filter.\n for (const target of targets) {\n if (target === 'claude-code') {\n for (const filename of canonicalAgents) {\n results.push(installAgentFile(packageRoot, installRoot, 'claude', filename, force));\n }\n } else if (target === 'codex') {\n for (const filename of canonicalAgents) {\n results.push(installAgentFile(packageRoot, installRoot, 'codex', filename, force));\n }\n }\n // OpenCode currently has no agent-definition channel — skills only.\n }\n\n if (!minimal) {\n // Cursor channel: write the `.cursor/rules` shim (the exact content omd:sync\n // renders for .cursor/rules/omd-design.mdc) so Cursor reads DESIGN.md before\n // UI work. No skills/agents/hooks — the shim plus the shared .claude/data\n // copies below are the whole Cursor install (issue #20).\n if (targets.includes('cursor')) {\n results.push(installCursorRule(installRoot, force));\n }\n\n // Ship the read-only data assets (reference fingerprints, controlled vocab,\n // human-readable tag matrix, opt-out corpus) so skills + hooks can run entirely\n // on the host CLI's own model — no external API keys.\n const dataFiles = [\n 'reference-fingerprints.json',\n 'reference-tags.md',\n 'vocabulary.json',\n 'synonyms.json',\n 'opt-out-corpus.json',\n ];\n for (const target of targets) {\n if (target === 'claude-code') {\n for (const dataFile of dataFiles) {\n results.push(installDataFile(packageRoot, installRoot, '.claude', dataFile, force));\n }\n } else if (target === 'codex') {\n for (const dataFile of dataFiles) {\n results.push(installDataFile(packageRoot, installRoot, '.codex', dataFile, force));\n }\n } else if (target === 'cursor' && !targets.includes('claude-code')) {\n // Cursor agents read the same `.claude/data` path — the catalog location\n // stays single (issue #20). Skip when claude-code is also selected; its\n // loop above already writes there.\n for (const dataFile of dataFiles) {\n results.push(installDataFile(packageRoot, installRoot, '.claude', dataFile, force, 'cursor'));\n }\n }\n }\n\n // Ship the reference catalog (DESIGN.md per id) into .claude/data/references\n // so omd:init can resolve a reference on clean npx installs — no node_modules,\n // no dev web/references (issue #16). Skipped under --skills-only (handled by the\n // enclosing `if (!minimal)`). Codex gets the same copy under .codex/data.\n for (const target of targets) {\n if (target === 'claude-code') {\n catalogCount += installReferenceCatalog(packageRoot, installRoot, '.claude', force);\n } else if (target === 'codex') {\n catalogCount += installReferenceCatalog(packageRoot, installRoot, '.codex', force);\n } else if (target === 'cursor' && !targets.includes('claude-code')) {\n // Same single-path rule as the data JSONs above — Cursor reads\n // .claude/data/references, never a second catalog location.\n catalogCount += installReferenceCatalog(packageRoot, installRoot, '.claude', force);\n }\n }\n\n // Copy ctx-prime.cjs (+ its companion context.cjs) into .claude/data/scripts/\n // so /omd-harness CTX-PRIME works without the package dir on npx installs\n // (issue #18 / harness OMD_DIR resolution).\n for (const target of targets) {\n const cd = target === 'claude-code' ? '.claude' : target === 'codex' ? '.codex' : null;\n if (!cd) continue;\n for (const helper of ['ctx-prime.cjs', 'context.cjs']) {\n const srcHelper = join(packageRoot, 'scripts', helper);\n if (!existsSync(srcHelper)) continue;\n const destHelper = join(installRoot, cd, 'data', 'scripts', helper);\n const srcTxt = readFileSync(srcHelper, 'utf8');\n if (existsSync(destHelper) && readFileSync(destHelper, 'utf8') === srcTxt) continue;\n mkdirSync(dirname(destHelper), { recursive: true });\n writeFileSync(destHelper, srcTxt, 'utf8');\n }\n }\n\n // Hooks + settings.json are PROJECT-SCOPED only — a global install must not\n // mutate the user's global Claude config / make hooks fire in every project.\n if (scope === 'project' && targets.includes('claude-code')) {\n for (const hookFile of [\n 'skill-activation.cjs',\n 'session-state-loader.cjs',\n 'post-edit-watch.cjs',\n 'session-end-foldin.cjs',\n // Shared module required by the fold-in / state-loader hooks. Lives in a\n // lib/ subdir; installHookFile preserves the relative path under .claude/hooks/.\n join('lib', 'preferences-parser.cjs'),\n ]) {\n results.push(installHookFile(packageRoot, installRoot, hookFile, force));\n }\n // settings.json (with merge, never clobber user)\n results.push(installSettingsJson(packageRoot, installRoot, force));\n }\n } // !minimal — skills-only skips data files, hooks, and settings.json\n\n p.log.message(pc.bold('\\nResults:'));\n for (const r of results) {\n const rel = relative(installRoot, 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 return 0;\n }\n\n // Minimal single-skill install (--skills-only): no omd onboarding, no agents/hooks.\n // Ideal for shipping a standalone skill (e.g. claude-design) to people who don't\n // want the rest of the omd toolchain.\n if (minimal) {\n for (const r of results.filter((x) => x.status === 'skipped-incompat')) {\n p.log.warn(\n `${pc.bold(r.skill)} ${pc.dim('skipped for ')}${pc.cyan(r.target)}${pc.dim(' — declares x-omd-channels (channel not supported).')}`\n );\n }\n const installed = results.filter(\n (r) => r.status === 'created' || r.status === 'updated'\n );\n if (installed.length === 0) {\n p.outro(pc.yellow('Nothing installed — no compatible skill/channel match.'));\n return 0;\n }\n p.outro(\n pc.green(\n `Done. Installed ${skills.map((s) => pc.bold(s)).join(', ')} ${scope === 'global' ? 'globally (~/.claude/skills)' : `for ${targets.join(', ')}`}.`\n ) +\n pc.dim(' → restart your agent, then use the skill (e.g. ') +\n pc.cyan('/claude-design') +\n pc.dim(').')\n );\n return 0;\n }\n\n // Friendly next-step nudge after successful install.\n // The first prompt is kept identical to the README's \"Your first 60 seconds\"\n // block so the README, the terminal, and the postinstall message all teach\n // the same activation moment. Bilingual (EN + KR) so an English reader is not\n // handed a Korean-only outro.\n const nextSteps = [\n `${pc.bold('Restart your agent, then type your first prompt:')}`,\n '',\n ` ${pc.cyan('EN')} ${pc.dim('Set up our design system — Toss-style, for a family meal-tracking app.')}`,\n ` ${pc.cyan('KR')} ${pc.dim('토스 스타일로 가족 식단 공유 앱 디자인 시스템 잡아줘')}`,\n '',\n `${pc.dim('Your agent runs omd:init and writes DESIGN.md. Then build against it:')}`,\n ` ${pc.cyan('EN')} ${pc.dim('Design the home screen.')} ${pc.cyan('KR')} ${pc.dim('홈 화면 디자인해줘')}`,\n '',\n `${pc.dim('Full walkthrough → \"Your first 60 seconds\" in the README. Routing is automatic — no slash command needed.')}`,\n `${pc.dim('Power user: ')}${pc.cyan('/omd-harness <task>')}${pc.dim(' — jump straight into the pipeline.')}`,\n '',\n `${pc.yellow('⚠ Already-running session?')} ${pc.dim('Run `/agents` to reload — or quit (Cmd+Q on macOS) and relaunch. Without reload, hooks/agents do not load.')}`,\n ].join('\\n');\n p.note(nextSteps, 'Next');\n\n // Counts derived from what was actually resolved/installed — never hardcoded,\n // so the outro can't drift from the real skill/agent/hook set (or the README).\n const hookCount = scope === 'project' && targets.includes('claude-code') ? 4 : 0;\n if (catalogCount > 0) {\n p.log.message(\n pc.bold('Reference catalog: ') +\n pc.cyan(`${catalogCount}`) +\n pc.dim(' DESIGN.md copied → .claude/data/references/<id>/DESIGN.md'),\n );\n }\n p.outro(\n pc.green(\n `Done. ${skills.length} skills · ${canonicalAgents.length} sub-agents · ${hookCount} hooks · ${catalogCount} catalog refs installed (${writtenCount} files)${scope === 'global' ? ' globally (~/.claude)' : ''}.`,\n ),\n );\n return 0;\n}\n\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport type AgentId = 'claude-code' | 'codex' | 'opencode' | 'cursor' | 'unknown';\n\nexport function detectCallingAgent(): AgentId {\n const env = process.env;\n\n if (env.CLAUDECODE === '1' || env.CLAUDE_CODE === '1' || env.CLAUDE_CODE_TASK_ID) {\n return 'claude-code';\n }\n if (env.CODEX_SESSION_ID || env.CODEX || env.OPENAI_CODEX) {\n return 'codex';\n }\n if (env.OPENCODE || env.OPENCODE_SESSION) {\n return 'opencode';\n }\n if (env.CURSOR_SESSION_ID || env.CURSOR_AGENT) {\n return 'cursor';\n }\n\n return 'unknown';\n}\n\nexport interface AgentPresence {\n claudeCode: boolean;\n codex: boolean;\n opencode: boolean;\n cursor: boolean;\n}\n\nexport function detectInstalledAgents(projectRoot: string): AgentPresence {\n return {\n claudeCode:\n existsSync(join(projectRoot, '.claude')) ||\n existsSync(join(projectRoot, 'CLAUDE.md')),\n codex:\n existsSync(join(projectRoot, '.codex')) ||\n existsSync(join(projectRoot, 'AGENTS.md')) ||\n existsSync(join(projectRoot, 'AGENTS.override.md')),\n opencode:\n existsSync(join(projectRoot, '.opencode')) ||\n existsSync(join(projectRoot, 'opencode.json')) ||\n existsSync(join(projectRoot, 'opencode.jsonc')),\n cursor:\n existsSync(join(projectRoot, '.cursor')) ||\n existsSync(join(projectRoot, '.cursorrules')),\n };\n}\n"],"mappings":"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","names":["existsSync","join","existsSync","join"]}
|