oh-my-design-cli 1.8.6 → 1.9.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 +1 -0
- package/.claude/hooks/lib/preferences-writer.cjs +1 -0
- package/.claude/hooks/post-edit-watch.cjs +1 -0
- package/.claude/hooks/session-end-foldin.cjs +1 -0
- package/.claude/hooks/session-state-loader.cjs +1 -0
- package/.claude/hooks/skill-activation.cjs +1 -0
- package/.claude/settings.json +10 -1
- package/.github/assets/logo-bg.png +0 -0
- package/AGENTS.md +20 -1
- package/CHANGELOG.md +482 -0
- package/MIGRATION.md +103 -0
- package/README.ja.md +56 -19
- package/README.ko.md +47 -30
- package/README.md +48 -30
- package/README.zh-TW.md +59 -22
- package/agents/AGENT.md +8 -7
- package/agents/omd-codex-image.md +1 -1
- package/agents/omd-critic.md +6 -7
- package/agents/omd-designer-review.md +1 -1
- package/agents/omd-final-qa.md +1 -1
- package/agents/omd-humanizer.md +38 -0
- package/agents/omd-kr-writer.md +3 -4
- package/agents/omd-locale-adapter.md +12 -16
- package/agents/omd-master.md +24 -19
- package/agents/omd-microcopy.md +1 -1
- package/agents/omd-orchestrator.md +7 -8
- package/agents/omd-slop-auditor.md +31 -0
- package/agents/omd-ux-researcher.md +1 -1
- package/data/reference-fingerprints.json +2761 -85
- package/dist/bin/oh-my-design.js +16 -8
- package/dist/bin/oh-my-design.js.map +1 -1
- package/dist/chunk-UJIHCEYF.js +99 -0
- package/dist/chunk-UJIHCEYF.js.map +1 -0
- package/dist/doctor-LDWSFM3I.js +763 -0
- package/dist/doctor-LDWSFM3I.js.map +1 -0
- package/dist/install-skills-ZZFNJ2II.js +1120 -0
- package/dist/install-skills-ZZFNJ2II.js.map +1 -0
- package/docs/CLI_QUICKSTART.md +157 -0
- package/package.json +25 -4
- package/scripts/postinstall.cjs +5 -4
- package/skills/claude-design/SKILL.md +38 -14
- package/skills/claude-design/references/claude-design-flow.md +11 -11
- package/skills/claude-design/references/codebase-analysis.md +5 -4
- package/skills/omd-final-qa/SKILL.md +1 -1
- package/skills/omd-harness/SKILL.md +51 -50
- package/skills/omd-humanize/SKILL.md +116 -0
- package/skills/omd-humanize/agents/openai.yaml +4 -0
- package/skills/omd-humanize/references/locale-playbooks.md +102 -0
- package/skills/omd-humanize/references/sources.md +11 -0
- package/skills/omd-init/SKILL.md +18 -12
- package/skills/omd-locale-adapter/SKILL.md +58 -91
- package/skills/omd-orchestrator/SKILL.md +11 -5
- package/skills/omd-reference-capture/SKILL.md +80 -26
- package/skills/omd-reference-capture/scripts/PLAYWRIGHT-LICENSE.txt +202 -0
- package/skills/omd-reference-capture/scripts/PLAYWRIGHT-NOTICE.txt +5 -0
- package/skills/omd-reference-capture/scripts/THIRD_PARTY_NOTICES.md +3 -0
- package/skills/omd-reference-capture/scripts/capture-reference-evidence.mjs +159906 -0
- package/skills/omd-slop-audit/SKILL.md +110 -0
- package/skills/omd-slop-audit/agents/openai.yaml +4 -0
- package/skills/omd-slop-audit/references/pattern-catalog.md +96 -0
- package/skills/omd-slop-audit/references/sources.md +24 -0
- package/web/references/104/DESIGN.md +354 -0
- package/web/references/11st/DESIGN.md +197 -316
- package/web/references/29cm/DESIGN.md +297 -393
- package/web/references/42dot/DESIGN.md +390 -0
- package/web/references/8percent/DESIGN.md +438 -0
- package/web/references/ably/DESIGN.md +267 -572
- package/web/references/acer/DESIGN.md +329 -0
- package/web/references/airbnb/DESIGN.md +219 -557
- package/web/references/airbridge/DESIGN.md +451 -0
- package/web/references/alipay/DESIGN.md +179 -420
- package/web/references/apple/DESIGN.md +224 -498
- package/web/references/asleep/DESIGN.md +402 -0
- package/web/references/au/DESIGN.md +334 -0
- package/web/references/autopedia/DESIGN.md +382 -0
- package/web/references/aws-cloudscape/DESIGN.md +241 -0
- package/web/references/baemin/DESIGN.md +332 -491
- package/web/references/banksalad/DESIGN.md +269 -180
- package/web/references/barogo/DESIGN.md +416 -0
- package/web/references/beusable/DESIGN.md +464 -0
- package/web/references/bigin/DESIGN.md +454 -0
- package/web/references/bilibili/DESIGN.md +206 -374
- package/web/references/bmw/DESIGN.md +162 -276
- package/web/references/brandi/DESIGN.md +184 -325
- package/web/references/buzzvil/DESIGN.md +457 -0
- package/web/references/cafe24/DESIGN.md +472 -0
- package/web/references/catchtable/DESIGN.md +219 -264
- package/web/references/cgv/DESIGN.md +317 -0
- package/web/references/channeltalk/DESIGN.md +210 -336
- package/web/references/china-airlines/DESIGN.md +291 -0
- package/web/references/cjonstyle/DESIGN.md +453 -0
- package/web/references/class101/DESIGN.md +168 -387
- package/web/references/classum/DESIGN.md +228 -203
- package/web/references/claude/DESIGN.md +242 -479
- package/web/references/clay/DESIGN.md +179 -377
- package/web/references/clickhouse/DESIGN.md +254 -343
- package/web/references/codeit/DESIGN.md +470 -0
- package/web/references/cohere/DESIGN.md +214 -324
- package/web/references/coinone/DESIGN.md +199 -179
- package/web/references/corca/DESIGN.md +414 -0
- package/web/references/coupang/DESIGN.md +205 -475
- package/web/references/cursor/DESIGN.md +240 -381
- package/web/references/dabang/DESIGN.md +248 -213
- package/web/references/dable/DESIGN.md +431 -0
- package/web/references/datarize/DESIGN.md +338 -0
- package/web/references/dealicious/DESIGN.md +402 -0
- package/web/references/drdiary/DESIGN.md +404 -0
- package/web/references/elastic/DESIGN.md +238 -0
- package/web/references/elevenlabs/DESIGN.md +183 -321
- package/web/references/elice/DESIGN.md +445 -0
- package/web/references/eslite/DESIGN.md +257 -0
- package/web/references/evaair/DESIGN.md +338 -0
- package/web/references/expo/DESIGN.md +238 -331
- package/web/references/familymart-tw/DESIGN.md +254 -0
- package/web/references/ferrari/DESIGN.md +160 -377
- package/web/references/figma/DESIGN.md +197 -451
- package/web/references/fitpet/DESIGN.md +445 -0
- package/web/references/flex/DESIGN.md +170 -253
- package/web/references/framer/DESIGN.md +197 -340
- package/web/references/freee/DESIGN.md +211 -533
- package/web/references/friendliai/DESIGN.md +431 -0
- package/web/references/frip/DESIGN.md +440 -0
- package/web/references/furiosaai/DESIGN.md +450 -0
- package/web/references/gangnamunni/DESIGN.md +154 -591
- package/web/references/gaudiolab/DESIGN.md +414 -0
- package/web/references/google/DESIGN.md +290 -398
- package/web/references/goorm/DESIGN.md +470 -0
- package/web/references/greencar/DESIGN.md +420 -0
- package/web/references/hackle/DESIGN.md +472 -0
- package/web/references/hashicorp/DESIGN.md +273 -318
- package/web/references/heydealer/DESIGN.md +429 -0
- package/web/references/humanscape/DESIGN.md +433 -0
- package/web/references/hwahae/DESIGN.md +453 -0
- package/web/references/hyundai/DESIGN.md +158 -368
- package/web/references/hyundaicard/DESIGN.md +171 -121
- package/web/references/ibm/DESIGN.md +227 -378
- package/web/references/idus/DESIGN.md +450 -0
- package/web/references/igaworks/DESIGN.md +437 -0
- package/web/references/inflearn/DESIGN.md +221 -366
- package/web/references/intuit/DESIGN.md +244 -0
- package/web/references/jandi/DESIGN.md +191 -291
- package/web/references/jobplanet/DESIGN.md +415 -0
- package/web/references/kakao/DESIGN.md +287 -475
- package/web/references/kakaobank/DESIGN.md +220 -521
- package/web/references/kakaogames/DESIGN.md +220 -0
- package/web/references/kakaopay/DESIGN.md +207 -526
- package/web/references/karrot/DESIGN.md +227 -251
- package/web/references/kb-kookmin/DESIGN.md +308 -0
- package/web/references/kbank/DESIGN.md +220 -175
- package/web/references/kcd/DESIGN.md +432 -0
- package/web/references/kmong/DESIGN.md +281 -356
- package/web/references/krds/DESIGN.md +245 -19
- package/web/references/kream/DESIGN.md +224 -359
- package/web/references/kurly/DESIGN.md +211 -533
- package/web/references/kyobobook/DESIGN.md +445 -0
- package/web/references/lablup/DESIGN.md +474 -0
- package/web/references/lamborghini/DESIGN.md +198 -360
- package/web/references/lemonbase/DESIGN.md +452 -0
- package/web/references/lguplus/DESIGN.md +259 -0
- package/web/references/likelion/DESIGN.md +283 -0
- package/web/references/line/DESIGN.md +396 -355
- package/web/references/linear.app/DESIGN.md +209 -562
- package/web/references/lotteon/DESIGN.md +386 -0
- package/web/references/lovable/DESIGN.md +255 -349
- package/web/references/makinarocks/DESIGN.md +242 -0
- package/web/references/maum-ai/DESIGN.md +423 -0
- package/web/references/medibloc/DESIGN.md +425 -0
- package/web/references/megabox/DESIGN.md +232 -0
- package/web/references/mercari/DESIGN.md +217 -434
- package/web/references/microsoft/DESIGN.md +234 -388
- package/web/references/mildang/DESIGN.md +411 -0
- package/web/references/millie/DESIGN.md +183 -504
- package/web/references/minimax/DESIGN.md +264 -322
- package/web/references/miricanvas/DESIGN.md +182 -355
- package/web/references/miro/DESIGN.md +234 -232
- package/web/references/mistral.ai/DESIGN.md +231 -314
- package/web/references/mixi/DESIGN.md +234 -0
- package/web/references/moin/DESIGN.md +445 -0
- package/web/references/mongodb/DESIGN.md +219 -316
- package/web/references/moreh/DESIGN.md +437 -0
- package/web/references/musinsa/DESIGN.md +184 -510
- package/web/references/mynavi/DESIGN.md +242 -0
- package/web/references/myrealtrip/DESIGN.md +169 -406
- package/web/references/naver/DESIGN.md +328 -499
- package/web/references/naverwebtoon/DESIGN.md +172 -395
- package/web/references/neosapience/DESIGN.md +441 -0
- package/web/references/nhn/DESIGN.md +169 -346
- package/web/references/nhncloud/DESIGN.md +185 -123
- package/web/references/nota/DESIGN.md +451 -0
- package/web/references/notion/DESIGN.md +271 -475
- package/web/references/nrise/DESIGN.md +417 -0
- package/web/references/ohouse/DESIGN.md +210 -549
- package/web/references/oliveyoung/DESIGN.md +217 -326
- package/web/references/ollama/DESIGN.md +241 -308
- package/web/references/onestore/DESIGN.md +248 -0
- package/web/references/palantir/DESIGN.md +256 -0
- package/web/references/panasonic/DESIGN.md +276 -0
- package/web/references/patternfly/DESIGN.md +259 -0
- package/web/references/payhere/DESIGN.md +438 -0
- package/web/references/pega/DESIGN.md +262 -0
- package/web/references/peoplefund/DESIGN.md +431 -0
- package/web/references/petfriends/DESIGN.md +428 -0
- package/web/references/pinkfong/DESIGN.md +199 -367
- package/web/references/pinterest/DESIGN.md +258 -308
- package/web/references/portone/DESIGN.md +446 -0
- package/web/references/poya/DESIGN.md +286 -0
- package/web/references/pozalabs/DESIGN.md +438 -0
- package/web/references/protopie/DESIGN.md +419 -0
- package/web/references/qraft/DESIGN.md +448 -0
- package/web/references/queenit/DESIGN.md +432 -0
- package/web/references/quotabook/DESIGN.md +449 -0
- package/web/references/rebellions/DESIGN.md +449 -0
- package/web/references/recruit/DESIGN.md +247 -0
- package/web/references/remember/DESIGN.md +202 -396
- package/web/references/resend/DESIGN.md +274 -348
- package/web/references/returnzero/DESIGN.md +460 -0
- package/web/references/sakura-internet/DESIGN.md +269 -0
- package/web/references/samsung/DESIGN.md +157 -359
- package/web/references/sandoll/DESIGN.md +445 -0
- package/web/references/sanity/DESIGN.md +241 -375
- package/web/references/saramin/DESIGN.md +465 -0
- package/web/references/scatterlab/DESIGN.md +412 -0
- package/web/references/selectstar/DESIGN.md +439 -0
- package/web/references/servicenow/DESIGN.md +289 -0
- package/web/references/shiftee/DESIGN.md +468 -0
- package/web/references/shinhanbank/DESIGN.md +161 -341
- package/web/references/shinhancard/DESIGN.md +208 -319
- package/web/references/sionic/DESIGN.md +454 -0
- package/web/references/sktelecom/DESIGN.md +310 -0
- package/web/references/socar/DESIGN.md +125 -32
- package/web/references/softbank/DESIGN.md +231 -0
- package/web/references/solapi/DESIGN.md +483 -0
- package/web/references/sony/DESIGN.md +234 -0
- package/web/references/soop/DESIGN.md +261 -0
- package/web/references/spacex/DESIGN.md +184 -323
- package/web/references/spotify/DESIGN.md +253 -394
- package/web/references/squeezebits/DESIGN.md +441 -0
- package/web/references/stayfolio/DESIGN.md +422 -0
- package/web/references/stripe/DESIGN.md +212 -491
- package/web/references/supabase/DESIGN.md +244 -316
- package/web/references/supertone/DESIGN.md +413 -0
- package/web/references/tabling/DESIGN.md +415 -0
- package/web/references/taishinbank/DESIGN.md +243 -0
- package/web/references/teamlab/DESIGN.md +240 -0
- package/web/references/teamsparta/DESIGN.md +472 -0
- package/web/references/tellingme/DESIGN.md +392 -0
- package/web/references/tesla/DESIGN.md +244 -427
- package/web/references/thsr/DESIGN.md +284 -0
- package/web/references/thumbtack/DESIGN.md +302 -0
- package/web/references/together.ai/DESIGN.md +257 -304
- package/web/references/toss/DESIGN.md +251 -593
- package/web/references/toss-securities/DESIGN.md +255 -178
- package/web/references/tossbank/DESIGN.md +195 -491
- package/web/references/toyota/DESIGN.md +238 -0
- package/web/references/tving/DESIGN.md +197 -245
- package/web/references/typed/DESIGN.md +413 -0
- package/web/references/uber/DESIGN.md +256 -333
- package/web/references/udn/DESIGN.md +250 -0
- package/web/references/upbit/DESIGN.md +145 -256
- package/web/references/upstage/DESIGN.md +211 -204
- package/web/references/uswds/DESIGN.md +287 -0
- package/web/references/vercel/DESIGN.md +236 -440
- package/web/references/vuno/DESIGN.md +413 -0
- package/web/references/wanted/DESIGN.md +215 -487
- package/web/references/weverse/DESIGN.md +437 -0
- package/web/references/wise/DESIGN.md +223 -231
- package/web/references/wisetracker/DESIGN.md +462 -0
- package/web/references/wooribank/DESIGN.md +245 -0
- package/web/references/yanolja/DESIGN.md +250 -419
- package/web/references/yeogiotte/DESIGN.md +235 -418
- package/web/references/zapier/DESIGN.md +256 -377
- package/web/references/zendesk/DESIGN.md +288 -0
- package/web/references/zigzag/DESIGN.md +200 -621
- package/dist/install-skills-7UUDOLG2.js +0 -734
- package/dist/install-skills-7UUDOLG2.js.map +0 -1
|
@@ -0,0 +1,763 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CLAUDE_HOOK_PATHS,
|
|
4
|
+
isCurrentManagedHook,
|
|
5
|
+
unsafeManagedPath
|
|
6
|
+
} from "./chunk-UJIHCEYF.js";
|
|
7
|
+
|
|
8
|
+
// src/cli/doctor.ts
|
|
9
|
+
import * as p from "@clack/prompts";
|
|
10
|
+
import pc from "picocolors";
|
|
11
|
+
import {
|
|
12
|
+
existsSync,
|
|
13
|
+
readFileSync,
|
|
14
|
+
readdirSync
|
|
15
|
+
} from "fs";
|
|
16
|
+
import { homedir } from "os";
|
|
17
|
+
import { dirname, join, relative } from "path";
|
|
18
|
+
import { fileURLToPath } from "url";
|
|
19
|
+
function shellQuote(value) {
|
|
20
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
21
|
+
}
|
|
22
|
+
var REQUIRED_PRODUCT_SKILLS = [
|
|
23
|
+
"claude-design",
|
|
24
|
+
"omd-apply",
|
|
25
|
+
"omd-asset-fetch",
|
|
26
|
+
"omd-codex-image",
|
|
27
|
+
"omd-designer-review",
|
|
28
|
+
"omd-experiment-gallery",
|
|
29
|
+
"omd-feel",
|
|
30
|
+
"omd-final-qa",
|
|
31
|
+
"omd-harness",
|
|
32
|
+
"omd-humanize",
|
|
33
|
+
"omd-init",
|
|
34
|
+
"omd-kr-writer",
|
|
35
|
+
"omd-learn",
|
|
36
|
+
"omd-locale-adapter",
|
|
37
|
+
"omd-orchestrator",
|
|
38
|
+
"omd-reference-capture",
|
|
39
|
+
"omd-remember",
|
|
40
|
+
"omd-slop-audit",
|
|
41
|
+
"omd-sync",
|
|
42
|
+
"omd-taste"
|
|
43
|
+
];
|
|
44
|
+
var REQUIRED_AGENT_IDS = [
|
|
45
|
+
"omd-a11y-auditor",
|
|
46
|
+
"omd-asset-curator",
|
|
47
|
+
"omd-codex-image",
|
|
48
|
+
"omd-critic",
|
|
49
|
+
"omd-designer-review",
|
|
50
|
+
"omd-final-qa",
|
|
51
|
+
"omd-humanizer",
|
|
52
|
+
"omd-kr-writer",
|
|
53
|
+
"omd-locale-adapter",
|
|
54
|
+
"omd-master",
|
|
55
|
+
"omd-microcopy",
|
|
56
|
+
"omd-orchestrator",
|
|
57
|
+
"omd-persona-tester",
|
|
58
|
+
"omd-slop-auditor",
|
|
59
|
+
"omd-ui-junior",
|
|
60
|
+
"omd-ux-engineer",
|
|
61
|
+
"omd-ux-researcher",
|
|
62
|
+
"omd-ux-writer"
|
|
63
|
+
];
|
|
64
|
+
var REQUIRED_DATA_FILES = [
|
|
65
|
+
"reference-fingerprints.json",
|
|
66
|
+
"reference-tags.md",
|
|
67
|
+
"vocabulary.json"
|
|
68
|
+
];
|
|
69
|
+
var REQUIRED_CLAUDE_HOOKS = [
|
|
70
|
+
"skill-activation.cjs",
|
|
71
|
+
"session-state-loader.cjs",
|
|
72
|
+
"post-edit-watch.cjs",
|
|
73
|
+
"session-end-foldin.cjs"
|
|
74
|
+
];
|
|
75
|
+
function missingProductSkills(skillsRoot) {
|
|
76
|
+
return REQUIRED_PRODUCT_SKILLS.filter(
|
|
77
|
+
(skill) => !existsSync(join(skillsRoot, skill, "SKILL.md"))
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
function missingDataFiles(dataRoot) {
|
|
81
|
+
return REQUIRED_DATA_FILES.filter((file) => !existsSync(join(dataRoot, file)));
|
|
82
|
+
}
|
|
83
|
+
function expectedSkillName(skill, channel) {
|
|
84
|
+
if (channel === "opencode" || skill === "claude-design") return skill;
|
|
85
|
+
return skill.replace(/^omd-/, "omd:");
|
|
86
|
+
}
|
|
87
|
+
function skillContractIssues(installRoot, skillsRoot, channel) {
|
|
88
|
+
const malformed = [];
|
|
89
|
+
const unsafe = [];
|
|
90
|
+
for (const skill of REQUIRED_PRODUCT_SKILLS) {
|
|
91
|
+
const path = join(skillsRoot, skill, "SKILL.md");
|
|
92
|
+
if (!existsSync(path)) continue;
|
|
93
|
+
if (unsafeManagedPath(installRoot, path)) {
|
|
94
|
+
unsafe.push(skill);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const content = readFileSync(path, "utf8");
|
|
98
|
+
const parsed = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]+)$/.exec(content);
|
|
99
|
+
const rawName = parsed ? /^name:\s*([^\r\n]+)\s*$/m.exec(parsed[1])?.[1]?.trim().replace(/^['"]|['"]$/g, "") : void 0;
|
|
100
|
+
const hasDescription = parsed ? /^description:\s*(?:\|[+-]?|>[-+]?|\S.+)\s*$/m.test(parsed[1]) : false;
|
|
101
|
+
if (!parsed || rawName !== expectedSkillName(skill, channel) || channel === "opencode" && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(rawName ?? "") || !hasDescription || !parsed[2].trim() || !content.includes("omd:installed-skill")) {
|
|
102
|
+
malformed.push(skill);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return [
|
|
106
|
+
...unsafe.length > 0 ? [`unsafe symlinked ${channel} skill paths: ${unsafe.join(", ")}`] : [],
|
|
107
|
+
...malformed.length > 0 ? [`invalid ${channel} skill definitions: ${malformed.join(", ")}`] : []
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
function coreIssues(installRoot, skillsRoot, dataRoot, referenceIds, channel) {
|
|
111
|
+
const issues = [];
|
|
112
|
+
const unsafeDataPaths = [
|
|
113
|
+
...REQUIRED_DATA_FILES.map((file) => join(dataRoot, file)),
|
|
114
|
+
join(dataRoot, "references")
|
|
115
|
+
].filter((path) => existsSync(path) && unsafeManagedPath(installRoot, path));
|
|
116
|
+
if (unsafeDataPaths.length > 0) {
|
|
117
|
+
issues.push(
|
|
118
|
+
`unsafe symlinked ${channel} data paths: ${unsafeDataPaths.map((path) => relative(installRoot, path)).join(", ")}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
const missingSkills = missingProductSkills(skillsRoot);
|
|
122
|
+
const missingData = missingDataFiles(dataRoot);
|
|
123
|
+
if (missingSkills.length > 0) {
|
|
124
|
+
issues.push(`missing product skills: ${missingSkills.join(", ")}`);
|
|
125
|
+
}
|
|
126
|
+
issues.push(...skillContractIssues(installRoot, skillsRoot, channel));
|
|
127
|
+
if (missingData.length > 0) issues.push(`missing catalog data: ${missingData.join(", ")}`);
|
|
128
|
+
if (referenceIds.size === 0) issues.push("reference catalog is empty");
|
|
129
|
+
const fingerprintsPath = join(dataRoot, "reference-fingerprints.json");
|
|
130
|
+
if (existsSync(fingerprintsPath)) {
|
|
131
|
+
try {
|
|
132
|
+
const fingerprints = JSON.parse(readFileSync(fingerprintsPath, "utf8"));
|
|
133
|
+
if (typeof fingerprints !== "object" || fingerprints === null || typeof fingerprints.count !== "number") {
|
|
134
|
+
issues.push("reference-fingerprints.json has no numeric count");
|
|
135
|
+
} else {
|
|
136
|
+
const { count, items } = fingerprints;
|
|
137
|
+
if (!Array.isArray(items)) {
|
|
138
|
+
issues.push("reference-fingerprints.json has no items array");
|
|
139
|
+
} else {
|
|
140
|
+
const invalidItems = items.filter(
|
|
141
|
+
(item) => typeof item !== "object" || item === null || typeof item.id !== "string" || item.id.length === 0
|
|
142
|
+
);
|
|
143
|
+
const fingerprintIds = items.filter((item) => !invalidItems.includes(item)).map((item) => item.id);
|
|
144
|
+
const uniqueFingerprintIds = new Set(fingerprintIds);
|
|
145
|
+
if (invalidItems.length > 0) {
|
|
146
|
+
issues.push(`reference-fingerprints.json has ${invalidItems.length} item(s) without an id`);
|
|
147
|
+
}
|
|
148
|
+
if (count !== items.length) {
|
|
149
|
+
issues.push(
|
|
150
|
+
`catalog mismatch: declared ${count} fingerprints but items contains ${items.length}`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
if (uniqueFingerprintIds.size !== fingerprintIds.length) {
|
|
154
|
+
issues.push("reference-fingerprints.json contains duplicate ids");
|
|
155
|
+
}
|
|
156
|
+
const missingReferences = [...uniqueFingerprintIds].filter(
|
|
157
|
+
(id) => !referenceIds.has(id)
|
|
158
|
+
);
|
|
159
|
+
if (missingReferences.length > 0) {
|
|
160
|
+
const preview = missingReferences.slice(0, 5).join(", ");
|
|
161
|
+
const remainder = missingReferences.length > 5 ? ` (+${missingReferences.length - 5} more)` : "";
|
|
162
|
+
issues.push(`missing catalog references: ${preview}${remainder}`);
|
|
163
|
+
}
|
|
164
|
+
const managedReferenceIds = managedReferenceIdsAt(dataRoot);
|
|
165
|
+
if (managedReferenceIds) {
|
|
166
|
+
const missingFingerprints = [...managedReferenceIds].filter(
|
|
167
|
+
(id) => !uniqueFingerprintIds.has(id)
|
|
168
|
+
);
|
|
169
|
+
if (missingFingerprints.length > 0) {
|
|
170
|
+
const preview = missingFingerprints.slice(0, 5).join(", ");
|
|
171
|
+
const remainder = missingFingerprints.length > 5 ? ` (+${missingFingerprints.length - 5} more)` : "";
|
|
172
|
+
issues.push(`catalog fingerprints missing for managed references: ${preview}${remainder}`);
|
|
173
|
+
}
|
|
174
|
+
} else if (referenceIds.size > 0 && uniqueFingerprintIds.size === 0) {
|
|
175
|
+
issues.push("reference-fingerprints.json is empty while references are installed");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (count < 0 || !Number.isInteger(count)) {
|
|
179
|
+
issues.push(
|
|
180
|
+
"reference-fingerprints.json count must be a non-negative integer"
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} catch {
|
|
185
|
+
issues.push("reference-fingerprints.json is not valid JSON");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return issues;
|
|
189
|
+
}
|
|
190
|
+
function managedReferenceIdsAt(dataRoot) {
|
|
191
|
+
const manifestPath = join(dataRoot, "references", ".omd-managed.json");
|
|
192
|
+
if (!existsSync(manifestPath)) return null;
|
|
193
|
+
try {
|
|
194
|
+
const parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
195
|
+
if (typeof parsed === "object" && parsed !== null && Array.isArray(parsed.managedIds) && parsed.managedIds.every(
|
|
196
|
+
(id) => typeof id === "string" && id.length > 0
|
|
197
|
+
)) {
|
|
198
|
+
return new Set(parsed.managedIds);
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
function referenceIdsAt(dataRoot) {
|
|
205
|
+
const root = join(dataRoot, "references");
|
|
206
|
+
if (!existsSync(root)) return /* @__PURE__ */ new Set();
|
|
207
|
+
return new Set(
|
|
208
|
+
readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync(join(root, entry.name, "DESIGN.md"))).map((entry) => entry.name)
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
function countRequiredSkills(skillsRoot) {
|
|
212
|
+
return REQUIRED_PRODUCT_SKILLS.filter(
|
|
213
|
+
(skill) => existsSync(join(skillsRoot, skill, "SKILL.md"))
|
|
214
|
+
).length;
|
|
215
|
+
}
|
|
216
|
+
function missingAgentIds(root, extension) {
|
|
217
|
+
return REQUIRED_AGENT_IDS.filter(
|
|
218
|
+
(agent) => !existsSync(join(root, `${agent}${extension}`))
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
function countRequiredAgents(root, extension) {
|
|
222
|
+
return REQUIRED_AGENT_IDS.length - missingAgentIds(root, extension).length;
|
|
223
|
+
}
|
|
224
|
+
function claudeAgentIssues(root) {
|
|
225
|
+
const agentsRoot = join(root, ".claude", "agents");
|
|
226
|
+
const missingAgents = missingAgentIds(agentsRoot, ".md");
|
|
227
|
+
const issues = [];
|
|
228
|
+
if (missingAgents.length > 0) {
|
|
229
|
+
issues.push(`missing Claude sub-agents: ${missingAgents.join(", ")}`);
|
|
230
|
+
}
|
|
231
|
+
const malformedAgents = [];
|
|
232
|
+
const unsafeAgents = [];
|
|
233
|
+
for (const agent of REQUIRED_AGENT_IDS) {
|
|
234
|
+
const path = join(agentsRoot, `${agent}.md`);
|
|
235
|
+
if (!existsSync(path)) continue;
|
|
236
|
+
if (unsafeManagedPath(root, path)) {
|
|
237
|
+
unsafeAgents.push(agent);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const content = readFileSync(path, "utf8");
|
|
241
|
+
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]+)$/.exec(content);
|
|
242
|
+
if (!frontmatter) {
|
|
243
|
+
malformedAgents.push(agent);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const name = /^name:\s*("(?:\\.|[^"\\])*")\s*$/m.exec(frontmatter[1])?.[1];
|
|
247
|
+
const description = /^description:\s*("(?:\\.|[^"\\])*")\s*$/m.exec(frontmatter[1])?.[1];
|
|
248
|
+
const tools = /^tools:\s*(\[[^\n]*\])\s*$/m.exec(frontmatter[1])?.[1];
|
|
249
|
+
const model = /^model:\s*("(?:\\.|[^"\\])*")\s*$/m.exec(frontmatter[1])?.[1];
|
|
250
|
+
try {
|
|
251
|
+
if (!name || JSON.parse(name) !== agent || !description || typeof JSON.parse(description) !== "string" || !JSON.parse(description) || !tools || !Array.isArray(JSON.parse(tools)) || !model || typeof JSON.parse(model) !== "string" || !/^omd_managed:\s*true\s*$/m.test(frontmatter[1]) || !frontmatter[2].trim()) {
|
|
252
|
+
malformedAgents.push(agent);
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
malformedAgents.push(agent);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (malformedAgents.length > 0) {
|
|
259
|
+
issues.push(`invalid Claude sub-agent frontmatter: ${malformedAgents.join(", ")}`);
|
|
260
|
+
}
|
|
261
|
+
if (unsafeAgents.length > 0) {
|
|
262
|
+
issues.push(`unsafe symlinked Claude sub-agent paths: ${unsafeAgents.join(", ")}`);
|
|
263
|
+
}
|
|
264
|
+
return issues;
|
|
265
|
+
}
|
|
266
|
+
function isJsonObject(value) {
|
|
267
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
268
|
+
}
|
|
269
|
+
var CLAUDE_HOOK_EVENTS = {
|
|
270
|
+
"skill-activation.cjs": "UserPromptSubmit",
|
|
271
|
+
"session-state-loader.cjs": "SessionStart",
|
|
272
|
+
"post-edit-watch.cjs": "PostToolUse",
|
|
273
|
+
"session-end-foldin.cjs": "Stop"
|
|
274
|
+
};
|
|
275
|
+
function readClaudeSettings(root) {
|
|
276
|
+
const settingsPath = join(root, ".claude", "settings.json");
|
|
277
|
+
if (!existsSync(settingsPath)) return null;
|
|
278
|
+
try {
|
|
279
|
+
const parsed = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
280
|
+
return isJsonObject(parsed) ? parsed : null;
|
|
281
|
+
} catch {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function hasExactClaudeHook(settings, event, hook) {
|
|
286
|
+
if (!isJsonObject(settings.hooks)) return false;
|
|
287
|
+
const groups = settings.hooks[event];
|
|
288
|
+
if (!Array.isArray(groups)) return false;
|
|
289
|
+
const expected = `node \${CLAUDE_PROJECT_DIR}/.claude/hooks/${hook}`;
|
|
290
|
+
return groups.some(
|
|
291
|
+
(group) => isJsonObject(group) && Array.isArray(group.hooks) && group.hooks.some(
|
|
292
|
+
(entry) => isJsonObject(entry) && entry.type === "command" && entry.command === expected
|
|
293
|
+
)
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
function hasClaudeOmdActivation(root) {
|
|
297
|
+
const settings = readClaudeSettings(root);
|
|
298
|
+
return Boolean(
|
|
299
|
+
settings && REQUIRED_CLAUDE_HOOKS.some(
|
|
300
|
+
(hook) => hasExactClaudeHook(settings, CLAUDE_HOOK_EVENTS[hook], hook)
|
|
301
|
+
)
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
function findBundleRoot() {
|
|
305
|
+
let current = dirname(fileURLToPath(import.meta.url));
|
|
306
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
307
|
+
const packageJson = join(current, "package.json");
|
|
308
|
+
try {
|
|
309
|
+
const parsed = JSON.parse(readFileSync(packageJson, "utf8"));
|
|
310
|
+
if (isJsonObject(parsed) && parsed.name === "oh-my-design-cli" && existsSync(join(current, "skills")) && existsSync(join(current, ".claude", "hooks"))) {
|
|
311
|
+
return current;
|
|
312
|
+
}
|
|
313
|
+
} catch {
|
|
314
|
+
}
|
|
315
|
+
const parent = dirname(current);
|
|
316
|
+
if (parent === current) break;
|
|
317
|
+
current = parent;
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
function bundledClaudeHookSource(hook) {
|
|
322
|
+
const bundleRoot = findBundleRoot();
|
|
323
|
+
if (!bundleRoot) return null;
|
|
324
|
+
const candidate = join(bundleRoot, ".claude", "hooks", hook);
|
|
325
|
+
return existsSync(candidate) ? readFileSync(candidate, "utf8") : null;
|
|
326
|
+
}
|
|
327
|
+
function claudeActivationIssues(root) {
|
|
328
|
+
const issues = [];
|
|
329
|
+
const settingsPath = join(root, ".claude", "settings.json");
|
|
330
|
+
if (!existsSync(settingsPath)) return ["Claude hook settings are missing"];
|
|
331
|
+
const settings = readClaudeSettings(root);
|
|
332
|
+
if (!settings) {
|
|
333
|
+
return ["Claude hook settings are not valid JSON"];
|
|
334
|
+
}
|
|
335
|
+
for (const hook of CLAUDE_HOOK_PATHS) {
|
|
336
|
+
const installedPath = join(root, ".claude", "hooks", hook);
|
|
337
|
+
if (!existsSync(installedPath)) {
|
|
338
|
+
issues.push(`missing Claude hook file: ${hook}`);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const unsafe = unsafeManagedPath(root, installedPath);
|
|
342
|
+
if (unsafe) {
|
|
343
|
+
issues.push(`unsafe Claude hook path: ${hook} (${unsafe})`);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
const source = bundledClaudeHookSource(hook);
|
|
347
|
+
if (!source) {
|
|
348
|
+
issues.push(`bundled Claude hook source is missing: ${hook}`);
|
|
349
|
+
} else if (!isCurrentManagedHook(readFileSync(installedPath, "utf8"), source)) {
|
|
350
|
+
issues.push(`stale or modified Claude hook file: ${hook}`);
|
|
351
|
+
}
|
|
352
|
+
if (!hook.startsWith("lib/") && !hasExactClaudeHook(
|
|
353
|
+
settings,
|
|
354
|
+
CLAUDE_HOOK_EVENTS[hook],
|
|
355
|
+
hook
|
|
356
|
+
)) {
|
|
357
|
+
issues.push(`Claude hook is not activated in settings: ${hook}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const settingsText = readFileSync(settingsPath, "utf8");
|
|
361
|
+
if (settingsText.includes("scripts/context_restore.sh") && !existsSync(join(root, "scripts", "context_restore.sh"))) {
|
|
362
|
+
issues.push("settings still invoke the unshipped scripts/context_restore.sh hook");
|
|
363
|
+
}
|
|
364
|
+
return issues;
|
|
365
|
+
}
|
|
366
|
+
function codexAgentIssues(root) {
|
|
367
|
+
const agentsRoot = join(root, ".codex", "agents");
|
|
368
|
+
const missingAgents = missingAgentIds(agentsRoot, ".toml");
|
|
369
|
+
const issues = [];
|
|
370
|
+
if (missingAgents.length > 0) {
|
|
371
|
+
issues.push(`missing Codex sub-agents: ${missingAgents.join(", ")}`);
|
|
372
|
+
}
|
|
373
|
+
const malformedAgents = [];
|
|
374
|
+
const foreignSkillPathAgents = [];
|
|
375
|
+
const missingRuntimeDependencyAgents = [];
|
|
376
|
+
const unsafeAgents = [];
|
|
377
|
+
for (const agent of REQUIRED_AGENT_IDS) {
|
|
378
|
+
const path = join(agentsRoot, `${agent}.toml`);
|
|
379
|
+
if (!existsSync(path)) continue;
|
|
380
|
+
if (unsafeManagedPath(root, path)) {
|
|
381
|
+
unsafeAgents.push(agent);
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const content = readFileSync(path, "utf8");
|
|
385
|
+
const name = /^name\s*=\s*"([^"\n]+)"\s*$/m.exec(content)?.[1]?.trim();
|
|
386
|
+
const description = /^description\s*=\s*"([^"\n]+)"\s*$/m.exec(content)?.[1]?.trim();
|
|
387
|
+
const instructionsMatch = /^developer_instructions\s*=\s*(?:'''([\s\S]*?)'''|"""([\s\S]*?)""")/m.exec(content);
|
|
388
|
+
const developerInstructions = (instructionsMatch?.[1] ?? instructionsMatch?.[2])?.trim();
|
|
389
|
+
if (/^\s*\[agent\]\s*$/m.test(content) || name !== agent || !description || !developerInstructions) {
|
|
390
|
+
malformedAgents.push(agent);
|
|
391
|
+
}
|
|
392
|
+
if (/\.(?:claude|opencode)\/skills\//.test(content)) {
|
|
393
|
+
foreignSkillPathAgents.push(agent);
|
|
394
|
+
}
|
|
395
|
+
if (/\bdata\/research\//.test(content)) {
|
|
396
|
+
missingRuntimeDependencyAgents.push(agent);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (malformedAgents.length > 0) {
|
|
400
|
+
issues.push(
|
|
401
|
+
`invalid Codex sub-agent definitions (expected top-level name, description, developer_instructions): ${malformedAgents.join(", ")}`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
if (foreignSkillPathAgents.length > 0) {
|
|
405
|
+
issues.push(
|
|
406
|
+
`Codex sub-agents reference another channel's skill root: ${foreignSkillPathAgents.join(", ")}`
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
if (missingRuntimeDependencyAgents.length > 0) {
|
|
410
|
+
issues.push(
|
|
411
|
+
`Codex sub-agents require unshipped development research files: ${missingRuntimeDependencyAgents.join(", ")}`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
if (unsafeAgents.length > 0) {
|
|
415
|
+
issues.push(`unsafe symlinked Codex sub-agent paths: ${unsafeAgents.join(", ")}`);
|
|
416
|
+
}
|
|
417
|
+
const masterPath = join(agentsRoot, "omd-master.toml");
|
|
418
|
+
if (!existsSync(masterPath)) return issues;
|
|
419
|
+
const master = readFileSync(masterPath, "utf8");
|
|
420
|
+
if (!master.includes("# omd-master \u2014 Conversational Design Partner")) {
|
|
421
|
+
issues.push("Codex omd-master does not embed the canonical role body");
|
|
422
|
+
}
|
|
423
|
+
if (/^model\s*=/m.test(master)) {
|
|
424
|
+
issues.push("Codex omd-master pins a stale model instead of inheriting the session model");
|
|
425
|
+
}
|
|
426
|
+
if (/\bomd (?:init prepare|remember)\b/.test(master)) {
|
|
427
|
+
issues.push("Codex omd-master still invokes removed CLI helpers");
|
|
428
|
+
}
|
|
429
|
+
const legacySkillsRoot = join(root, ".codex", "skills");
|
|
430
|
+
if (existsSync(legacySkillsRoot)) {
|
|
431
|
+
const legacySkills = readdirSync(legacySkillsRoot, { withFileTypes: true }).filter(
|
|
432
|
+
(entry) => entry.isDirectory() && existsSync(join(legacySkillsRoot, entry.name, "SKILL.md"))
|
|
433
|
+
).map((entry) => entry.name).sort();
|
|
434
|
+
const unmanagedLegacySkills = legacySkills.filter((skill) => {
|
|
435
|
+
const content = readFileSync(join(legacySkillsRoot, skill, "SKILL.md"), "utf8");
|
|
436
|
+
return !content.includes("omd:installed-skill");
|
|
437
|
+
});
|
|
438
|
+
const managedLegacySkills = legacySkills.filter(
|
|
439
|
+
(skill) => !unmanagedLegacySkills.includes(skill)
|
|
440
|
+
);
|
|
441
|
+
if (managedLegacySkills.length > 0) {
|
|
442
|
+
issues.push(`legacy Codex skill path must be migrated from .codex/skills: ${managedLegacySkills.join(", ")}`);
|
|
443
|
+
}
|
|
444
|
+
if (unmanagedLegacySkills.length > 0) {
|
|
445
|
+
issues.push(`unmanaged legacy Codex skill paths require manual review: ${unmanagedLegacySkills.join(", ")}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return issues;
|
|
449
|
+
}
|
|
450
|
+
function openCodeAgentIssues(root, agentsRoot) {
|
|
451
|
+
const missingAgents = missingAgentIds(agentsRoot, ".md");
|
|
452
|
+
const issues = [];
|
|
453
|
+
if (missingAgents.length > 0) {
|
|
454
|
+
issues.push(`missing OpenCode sub-agents: ${missingAgents.join(", ")}`);
|
|
455
|
+
}
|
|
456
|
+
const malformed = [];
|
|
457
|
+
const foreignSkillPathAgents = [];
|
|
458
|
+
const missingRuntimeDependencyAgents = [];
|
|
459
|
+
const colonSkillReferenceAgents = [];
|
|
460
|
+
const unsafeAgents = [];
|
|
461
|
+
for (const agent of REQUIRED_AGENT_IDS) {
|
|
462
|
+
const path = join(agentsRoot, `${agent}.md`);
|
|
463
|
+
if (!existsSync(path)) continue;
|
|
464
|
+
if (unsafeManagedPath(root, path)) {
|
|
465
|
+
unsafeAgents.push(agent);
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const content = readFileSync(path, "utf8");
|
|
469
|
+
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]+)$/.exec(content);
|
|
470
|
+
const description = frontmatter ? /^description:\s*("(?:\\.|[^"\\])*")\s*$/m.exec(frontmatter[1])?.[1] : void 0;
|
|
471
|
+
try {
|
|
472
|
+
if (!frontmatter || !description || !JSON.parse(description) || !/^mode:\s*subagent\s*$/m.test(frontmatter[1]) || !frontmatter[2].trim() || !content.includes("omd:installed-agent")) {
|
|
473
|
+
malformed.push(agent);
|
|
474
|
+
}
|
|
475
|
+
} catch {
|
|
476
|
+
malformed.push(agent);
|
|
477
|
+
}
|
|
478
|
+
if (/\.(?:claude|agents)\/skills\//.test(content)) {
|
|
479
|
+
foreignSkillPathAgents.push(agent);
|
|
480
|
+
}
|
|
481
|
+
if (/\bdata\/research\//.test(content)) {
|
|
482
|
+
missingRuntimeDependencyAgents.push(agent);
|
|
483
|
+
}
|
|
484
|
+
if (REQUIRED_PRODUCT_SKILLS.some(
|
|
485
|
+
(skill) => skill.startsWith("omd-") && content.includes(`omd:${skill.slice(4)}`)
|
|
486
|
+
)) {
|
|
487
|
+
colonSkillReferenceAgents.push(agent);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (malformed.length > 0) {
|
|
491
|
+
issues.push(`invalid OpenCode sub-agent definitions: ${malformed.join(", ")}`);
|
|
492
|
+
}
|
|
493
|
+
if (foreignSkillPathAgents.length > 0) {
|
|
494
|
+
issues.push(
|
|
495
|
+
`OpenCode sub-agents reference another channel's skill root: ${foreignSkillPathAgents.join(", ")}`
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
if (missingRuntimeDependencyAgents.length > 0) {
|
|
499
|
+
issues.push(
|
|
500
|
+
`OpenCode sub-agents require unshipped development research files: ${missingRuntimeDependencyAgents.join(", ")}`
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
if (colonSkillReferenceAgents.length > 0) {
|
|
504
|
+
issues.push(
|
|
505
|
+
`OpenCode sub-agents reference non-native colon-form skills: ${colonSkillReferenceAgents.join(", ")}`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
if (unsafeAgents.length > 0) {
|
|
509
|
+
issues.push(`unsafe symlinked OpenCode sub-agent paths: ${unsafeAgents.join(", ")}`);
|
|
510
|
+
}
|
|
511
|
+
return issues;
|
|
512
|
+
}
|
|
513
|
+
function cursorRuleIssues(root) {
|
|
514
|
+
const path = join(root, ".cursor", "rules", "omd-design.mdc");
|
|
515
|
+
if (!existsSync(path)) return [];
|
|
516
|
+
const unsafe = unsafeManagedPath(root, path);
|
|
517
|
+
if (unsafe) return [`unsafe Cursor rule path (${unsafe})`];
|
|
518
|
+
const content = readFileSync(path, "utf8");
|
|
519
|
+
if (!content.startsWith("---\n") || !content.includes("description: Authoritative brand & UI design system.") || !/<!-- omd:start v=1 hash=[a-f0-9]{12} -->/.test(content) || !content.includes("The authoritative design spec lives at `@DESIGN.md` (repo root). Open and read before generating/modifying UI.") || !content.includes("<!-- omd:end -->")) {
|
|
520
|
+
return ["Cursor rule is not an OmD-managed design-system shim"];
|
|
521
|
+
}
|
|
522
|
+
return [];
|
|
523
|
+
}
|
|
524
|
+
function collectDoctorReport(opts = {}) {
|
|
525
|
+
const projectRoot = opts.dir ?? process.cwd();
|
|
526
|
+
const root = opts.global ? homedir() : projectRoot;
|
|
527
|
+
const claudeSkillsRoot = join(root, ".claude", "skills");
|
|
528
|
+
const codexSkillsRoot = join(root, ".agents", "skills");
|
|
529
|
+
const opencodeRoot = opts.global ? join(root, ".config", "opencode") : join(root, ".opencode");
|
|
530
|
+
const opencodeSkillsRoot = join(opencodeRoot, "skills");
|
|
531
|
+
const claudeAgentsRoot = join(root, ".claude", "agents");
|
|
532
|
+
const codexAgentsRoot = join(root, ".codex", "agents");
|
|
533
|
+
const opencodeAgentsRoot = join(opencodeRoot, "agents");
|
|
534
|
+
const claudeDataRoot = join(root, ".claude", "data");
|
|
535
|
+
const codexDataRoot = join(root, ".codex", "data");
|
|
536
|
+
const opencodeDataRoot = join(opencodeRoot, "data");
|
|
537
|
+
const claudeSkills = countRequiredSkills(claudeSkillsRoot);
|
|
538
|
+
const codexSkills = countRequiredSkills(codexSkillsRoot);
|
|
539
|
+
const opencodeSkills = countRequiredSkills(opencodeSkillsRoot);
|
|
540
|
+
const claudeAgents = countRequiredAgents(claudeAgentsRoot, ".md");
|
|
541
|
+
const codexAgents = countRequiredAgents(codexAgentsRoot, ".toml");
|
|
542
|
+
const opencodeAgents = countRequiredAgents(opencodeAgentsRoot, ".md");
|
|
543
|
+
const sourceRepository = (() => {
|
|
544
|
+
try {
|
|
545
|
+
const parsed = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
546
|
+
return isJsonObject(parsed) && parsed.name === "oh-my-design-cli" && existsSync(join(root, "web", "references"));
|
|
547
|
+
} catch {
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
})();
|
|
551
|
+
const installedClaudeReferenceIds = referenceIdsAt(claudeDataRoot);
|
|
552
|
+
const claudeReferenceIds = sourceRepository && installedClaudeReferenceIds.size === 0 ? referenceIdsAt(join(root, "web")) : installedClaudeReferenceIds;
|
|
553
|
+
const codexReferenceIds = referenceIdsAt(codexDataRoot);
|
|
554
|
+
const opencodeReferenceIds = referenceIdsAt(opencodeDataRoot);
|
|
555
|
+
const claudeReferences = claudeReferenceIds.size;
|
|
556
|
+
const codexReferences = codexReferenceIds.size;
|
|
557
|
+
const opencodeReferences = opencodeReferenceIds.size;
|
|
558
|
+
const claudeInstalled = claudeSkills > 0 || claudeAgents > 0 || hasClaudeOmdActivation(root);
|
|
559
|
+
const codexInstalled = codexSkills > 0 || codexAgents > 0 || existsSync(join(codexDataRoot, "reference-fingerprints.json"));
|
|
560
|
+
const opencodeInstalled = opencodeSkills > 0 || opencodeAgents > 0 || existsSync(join(opencodeDataRoot, "reference-fingerprints.json"));
|
|
561
|
+
const cursorInstalled = !opts.global && existsSync(join(root, ".cursor", "rules", "omd-design.mdc"));
|
|
562
|
+
const claudeIssues = claudeInstalled ? [
|
|
563
|
+
...coreIssues(
|
|
564
|
+
root,
|
|
565
|
+
claudeSkillsRoot,
|
|
566
|
+
claudeDataRoot,
|
|
567
|
+
claudeReferenceIds,
|
|
568
|
+
"claude-code"
|
|
569
|
+
),
|
|
570
|
+
...claudeAgentIssues(root),
|
|
571
|
+
...opts.global ? [] : claudeActivationIssues(root)
|
|
572
|
+
] : [];
|
|
573
|
+
const codexIssues = codexInstalled ? [
|
|
574
|
+
...coreIssues(
|
|
575
|
+
root,
|
|
576
|
+
codexSkillsRoot,
|
|
577
|
+
codexDataRoot,
|
|
578
|
+
codexReferenceIds,
|
|
579
|
+
"codex"
|
|
580
|
+
),
|
|
581
|
+
...codexAgentIssues(root)
|
|
582
|
+
] : [];
|
|
583
|
+
const opencodeIssues = opencodeInstalled ? [
|
|
584
|
+
...coreIssues(
|
|
585
|
+
root,
|
|
586
|
+
opencodeSkillsRoot,
|
|
587
|
+
opencodeDataRoot,
|
|
588
|
+
opencodeReferenceIds,
|
|
589
|
+
"opencode"
|
|
590
|
+
),
|
|
591
|
+
...openCodeAgentIssues(root, opencodeAgentsRoot)
|
|
592
|
+
] : [];
|
|
593
|
+
const cursorIssues = cursorInstalled ? coreIssues(
|
|
594
|
+
root,
|
|
595
|
+
join(root, ".cursor", "__no_skills__"),
|
|
596
|
+
claudeDataRoot,
|
|
597
|
+
claudeReferenceIds,
|
|
598
|
+
"claude-code"
|
|
599
|
+
).filter((issue) => !issue.startsWith("missing product skills:")).filter((issue) => !issue.startsWith("invalid claude-code skill definitions:")).concat(cursorRuleIssues(root)) : [];
|
|
600
|
+
const channels = [
|
|
601
|
+
{
|
|
602
|
+
id: "claude-code",
|
|
603
|
+
installed: claudeInstalled,
|
|
604
|
+
ready: claudeInstalled && claudeIssues.length === 0,
|
|
605
|
+
skills: claudeSkills,
|
|
606
|
+
agents: claudeAgents,
|
|
607
|
+
references: claudeReferences,
|
|
608
|
+
issues: claudeIssues
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
id: "codex",
|
|
612
|
+
installed: codexInstalled,
|
|
613
|
+
ready: codexInstalled && codexIssues.length === 0,
|
|
614
|
+
skills: codexSkills,
|
|
615
|
+
agents: codexAgents,
|
|
616
|
+
references: codexReferences,
|
|
617
|
+
issues: codexIssues
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
id: "opencode",
|
|
621
|
+
installed: opencodeInstalled,
|
|
622
|
+
ready: opencodeInstalled && opencodeIssues.length === 0,
|
|
623
|
+
skills: opencodeSkills,
|
|
624
|
+
agents: opencodeAgents,
|
|
625
|
+
references: opencodeReferences,
|
|
626
|
+
issues: opencodeIssues
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
id: "cursor",
|
|
630
|
+
installed: cursorInstalled,
|
|
631
|
+
ready: cursorInstalled && cursorIssues.length === 0,
|
|
632
|
+
skills: 0,
|
|
633
|
+
agents: 0,
|
|
634
|
+
references: claudeReferences,
|
|
635
|
+
issues: cursorIssues
|
|
636
|
+
}
|
|
637
|
+
];
|
|
638
|
+
const installedChannels = channels.filter((channel) => channel.installed);
|
|
639
|
+
const cursorOnly = installedChannels.length === 1 && installedChannels[0].id === "cursor";
|
|
640
|
+
const hasInstall = installedChannels.length > 0;
|
|
641
|
+
const hasIncompleteChannel = installedChannels.some((channel) => !channel.ready);
|
|
642
|
+
const designMd = !opts.global && existsSync(join(projectRoot, "DESIGN.md"));
|
|
643
|
+
const state = !hasInstall ? "not-installed" : hasIncompleteChannel ? "incomplete" : designMd || opts.global ? "ready" : "needs-design-md";
|
|
644
|
+
const nextPrompt = state === "ready" && designMd ? cursorOnly ? "Improve the home screen using our DESIGN.md. Preserve behavior, then verify the real route and accessibility." : "Improve the home screen using our DESIGN.md. Audit the result with omd:feel." : state === "needs-design-md" || state === "ready" && opts.global ? "Set up our design system \u2014 Toss-style, for a family meal-tracking app." : "";
|
|
645
|
+
const repairTargets = installedChannels.filter((channel) => !channel.ready).map((channel) => channel.id).join(" ");
|
|
646
|
+
const dirSuffix = opts.dir && !opts.global ? ` --dir ${shellQuote(projectRoot)}` : "";
|
|
647
|
+
const needsHookRepair = installedChannels.some(
|
|
648
|
+
(channel) => channel.id === "claude-code" && channel.issues.some(
|
|
649
|
+
(issue) => issue.startsWith("stale or modified Claude hook file:")
|
|
650
|
+
)
|
|
651
|
+
);
|
|
652
|
+
const allIssues = installedChannels.flatMap((channel) => channel.issues);
|
|
653
|
+
const needsForceRepair = allIssues.some(
|
|
654
|
+
(issue) => issue.startsWith("invalid ") || issue === "Claude hook settings are not valid JSON" || issue === "Cursor rule is not an OmD-managed design-system shim"
|
|
655
|
+
);
|
|
656
|
+
const needsManualReview = allIssues.some(
|
|
657
|
+
(issue) => issue.startsWith("unsafe ") || issue.startsWith("unmanaged legacy Codex skill paths require manual review:") || issue.startsWith("bundled Claude hook source is missing:")
|
|
658
|
+
);
|
|
659
|
+
const repairCommand = `npx oh-my-design-cli@latest install-skills --agent ${repairTargets} --all${needsHookRepair ? " --repair-hooks" : ""}${needsForceRepair ? " --force" : ""}${opts.global ? " --global" : ""}${dirSuffix}`;
|
|
660
|
+
return {
|
|
661
|
+
root,
|
|
662
|
+
scope: opts.global ? "global" : "project",
|
|
663
|
+
state,
|
|
664
|
+
designMd,
|
|
665
|
+
channels,
|
|
666
|
+
nextCommand: state === "not-installed" ? dirSuffix ? `npx oh-my-design-cli@latest install-skills --all${dirSuffix}` : "npx oh-my-design-cli@latest" : state === "incomplete" ? needsManualReview ? "" : repairCommand : "",
|
|
667
|
+
nextPrompt,
|
|
668
|
+
manualAction: needsManualReview ? allIssues.some((issue) => issue.startsWith("bundled Claude hook source is missing:")) ? "Reinstall the latest oh-my-design package; its bundled Claude hook sources are incomplete, so automatic repair is disabled." : "Replace symlinked OmD paths with real project-local files and review unmanaged legacy Codex entries, then run doctor again." : ""
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
var CHANNEL_LABELS = {
|
|
672
|
+
"claude-code": "Claude Code",
|
|
673
|
+
codex: "Codex",
|
|
674
|
+
opencode: "OpenCode",
|
|
675
|
+
cursor: "Cursor"
|
|
676
|
+
};
|
|
677
|
+
async function runDoctor(opts = {}) {
|
|
678
|
+
const report = collectDoctorReport(opts);
|
|
679
|
+
if (opts.json) {
|
|
680
|
+
console.log(JSON.stringify(report, null, 2));
|
|
681
|
+
return report.state === "not-installed" || report.state === "incomplete" ? 1 : 0;
|
|
682
|
+
}
|
|
683
|
+
p.intro(
|
|
684
|
+
pc.bold("omd doctor") + pc.dim(` (${relative(process.cwd(), report.root) || "."})`)
|
|
685
|
+
);
|
|
686
|
+
const installed = report.channels.filter((channel) => channel.installed);
|
|
687
|
+
if (installed.length === 0) {
|
|
688
|
+
p.log.error("No oh-my-design agent channel found in this scope.");
|
|
689
|
+
p.note(
|
|
690
|
+
`${pc.bold("Install the bundle:")}
|
|
691
|
+
|
|
692
|
+
${pc.cyan(report.nextCommand)}`,
|
|
693
|
+
"Next"
|
|
694
|
+
);
|
|
695
|
+
p.outro(pc.red("Action required."));
|
|
696
|
+
return 1;
|
|
697
|
+
}
|
|
698
|
+
for (const channel of installed) {
|
|
699
|
+
const details = [
|
|
700
|
+
channel.skills > 0 ? `${channel.skills} skills` : null,
|
|
701
|
+
channel.agents > 0 ? `${channel.agents} sub-agents` : null,
|
|
702
|
+
channel.references > 0 ? `${channel.references} references` : null
|
|
703
|
+
].filter(Boolean);
|
|
704
|
+
if (channel.ready) {
|
|
705
|
+
p.log.success(`${CHANNEL_LABELS[channel.id]} ${pc.dim(details.join(" \xB7 "))}`);
|
|
706
|
+
} else {
|
|
707
|
+
p.log.warn(`${CHANNEL_LABELS[channel.id]} ${pc.dim(details.join(" \xB7 "))}`);
|
|
708
|
+
for (const issue of channel.issues) p.log.message(` ${pc.yellow("\u2013")} ${issue}`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (report.state === "incomplete") {
|
|
712
|
+
const repairResetsHooks = report.nextCommand.includes("--repair-hooks");
|
|
713
|
+
const repairForcesReservedPaths = report.nextCommand.includes("--force");
|
|
714
|
+
const preservationNote = repairResetsHooks || repairForcesReservedPaths ? [
|
|
715
|
+
repairResetsHooks ? "The six OmD Claude hook paths will be reset to the packaged versions." : null,
|
|
716
|
+
repairForcesReservedPaths ? "Conflicting files at OmD-reserved paths will be replaced; unrelated user files are preserved." : null
|
|
717
|
+
].filter(Boolean).join(" ") : "User-authored files outside OmD ownership are preserved by the installer.";
|
|
718
|
+
p.note(
|
|
719
|
+
report.nextCommand ? `${pc.bold("Repair the managed bundle:")}
|
|
720
|
+
|
|
721
|
+
${pc.cyan(report.nextCommand)}
|
|
722
|
+
|
|
723
|
+
` + pc.dim(`Run npx oh-my-design-cli@latest doctor again afterward. ${preservationNote}`) : `${pc.bold("Manual safety review required:")}
|
|
724
|
+
|
|
725
|
+
${pc.yellow(report.manualAction)}
|
|
726
|
+
|
|
727
|
+
` + pc.dim("The installer will not follow symlinks or remove unmanaged legacy entries."),
|
|
728
|
+
"Next"
|
|
729
|
+
);
|
|
730
|
+
p.outro(pc.red("Install found, but required runtime assets are incomplete."));
|
|
731
|
+
return 1;
|
|
732
|
+
}
|
|
733
|
+
if (report.scope === "global") {
|
|
734
|
+
p.log.success("Global bundle is discoverable from every project.");
|
|
735
|
+
} else if (report.designMd) {
|
|
736
|
+
p.log.success("DESIGN.md found \u2014 brand context is active.");
|
|
737
|
+
} else {
|
|
738
|
+
p.log.warn("Bundle installed, but this project has no DESIGN.md yet.");
|
|
739
|
+
}
|
|
740
|
+
const note2 = [
|
|
741
|
+
report.designMd ? pc.bold("Build with the system:") : pc.bold("Activate this project:"),
|
|
742
|
+
"",
|
|
743
|
+
` ${pc.cyan("EN")} ${pc.dim(report.nextPrompt)}`,
|
|
744
|
+
report.designMd ? ` ${pc.cyan("KR")} ${pc.dim(
|
|
745
|
+
report.channels.filter((channel) => channel.installed).length === 1 && report.channels.find((channel) => channel.installed)?.id === "cursor" ? "DESIGN.md\uB97C \uC801\uC6A9\uD574 \uD648 \uD654\uBA74\uC744 \uAC1C\uC120\uD558\uACE0 \uC2E4\uC81C \uACBD\uB85C\uC640 \uC811\uADFC\uC131\uC744 \uAC80\uC99D\uD574\uC918" : "DESIGN.md\uB97C \uC801\uC6A9\uD574 \uD648 \uD654\uBA74\uC744 \uAC1C\uC120\uD558\uACE0 omd:feel\uB85C \uAC80\uC218\uD574\uC918"
|
|
746
|
+
)}` : ` ${pc.cyan("KR")} ${pc.dim("\uD1A0\uC2A4 \uC2A4\uD0C0\uC77C\uB85C \uAC00\uC871 \uC2DD\uB2E8 \uACF5\uC720 \uC571 \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C \uC7A1\uC544\uC918")}`,
|
|
747
|
+
"",
|
|
748
|
+
pc.dim("Start a new agent session after install or upgrade so skills are reloaded."),
|
|
749
|
+
...report.channels.some((channel) => channel.id === "codex" && channel.installed) ? [pc.dim("Codex also requires the project to be trusted before project-local roles are loaded.")] : []
|
|
750
|
+
].join("\n");
|
|
751
|
+
p.note(note2, "Next");
|
|
752
|
+
p.outro(
|
|
753
|
+
report.state === "ready" ? pc.green("Ready. Your next prompt is the product surface.") : pc.yellow("One prompt away from activation.")
|
|
754
|
+
);
|
|
755
|
+
return 0;
|
|
756
|
+
}
|
|
757
|
+
export {
|
|
758
|
+
REQUIRED_AGENT_IDS,
|
|
759
|
+
REQUIRED_PRODUCT_SKILLS,
|
|
760
|
+
collectDoctorReport,
|
|
761
|
+
runDoctor
|
|
762
|
+
};
|
|
763
|
+
//# sourceMappingURL=doctor-LDWSFM3I.js.map
|