cmx-sdk 0.2.7 → 0.2.9
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/dist/add-studio-YUDYE2OH.js +0 -0
- package/dist/chunk-7TDMLYBI.js +0 -0
- package/dist/chunk-EDXXR5BE.js +0 -0
- package/dist/{chunk-S3TFTN6H.js → chunk-EZMBZWH7.js} +57 -0
- package/dist/chunk-FPQYL5GE.js +0 -0
- package/dist/chunk-IIQLQIDP.js +0 -0
- package/dist/chunk-NZQ6SBFS.js +0 -0
- package/dist/cli.js +567 -121
- package/dist/index.d.ts +343 -2
- package/dist/index.js +79 -31
- package/dist/index.js.map +1 -1
- package/dist/init-FLRQXJX4.js +0 -0
- package/dist/{interactive-menu-QKE6FMPN.js → interactive-menu-FYVOQSTL.js} +1 -1
- package/dist/studio-HAS6DYLO.js +0 -0
- package/dist/{update-sdk-ZDFOSMN4.js → update-sdk-KJZ6VB4M.js} +1 -1
- package/dist/update-studio-TWCYSYIS.js +0 -0
- package/package.json +16 -14
- package/templates/AGENTS.md +173 -0
- package/templates/CLAUDE.md +28 -0
- package/templates/claude/commands/check.md +64 -0
- package/templates/claude/commands/next-action.md +66 -0
- package/templates/claude/skills/cmx-cache/SKILL.md +50 -0
- package/templates/claude/skills/cmx-cache/references/cache-patterns.md +153 -0
- package/templates/claude/skills/cmx-component/SKILL.md +108 -0
- package/templates/claude/skills/cmx-component/references/component-schema.md +123 -0
- package/templates/claude/skills/cmx-content/SKILL.md +158 -0
- package/templates/claude/skills/cmx-content/references/migration-patterns.md +120 -0
- package/templates/claude/skills/cmx-content/references/seed-patterns.md +146 -0
- package/templates/claude/skills/cmx-dev/SKILL.md +266 -0
- package/templates/claude/skills/cmx-dev/references/api-patterns.md +220 -0
- package/templates/claude/skills/cmx-dev/references/cli-reference.md +54 -0
- package/templates/claude/skills/cmx-form/SKILL.md +103 -0
- package/templates/claude/skills/cmx-form/references/form-template.md +152 -0
- package/templates/claude/skills/cmx-migrate/SKILL.md +501 -0
- package/templates/claude/skills/cmx-migrate/references/analysis-guide.md +127 -0
- package/templates/claude/skills/cmx-migrate/references/html-to-mdx.md +99 -0
- package/templates/claude/skills/cmx-migrate/references/intermediate-format.md +196 -0
- package/templates/claude/skills/cmx-migrate/references/tool-setup.md +150 -0
- package/templates/claude/skills/cmx-schema/SKILL.md +159 -0
- package/templates/claude/skills/cmx-schema/references/field-types.md +164 -0
- package/templates/claude/skills/cmx-schema/references/migration-scenarios.md +44 -0
- package/templates/claude/skills/cmx-seo/SKILL.md +54 -0
- package/templates/claude/skills/cmx-seo/references/seo-patterns.md +216 -0
- package/templates/claude/skills/cmx-style/SKILL.md +48 -0
- package/templates/claude/skills/cmx-style/references/style-patterns.md +114 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# フォームテンプレート
|
|
2
|
+
|
|
3
|
+
## 基本構造
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
// src/app/{path}/contact-form.tsx
|
|
7
|
+
"use client"
|
|
8
|
+
|
|
9
|
+
import { useState } from "react"
|
|
10
|
+
import { z } from "zod"
|
|
11
|
+
import { Button } from "@/components/ui/button"
|
|
12
|
+
import { Input } from "@/components/ui/input"
|
|
13
|
+
import { Textarea } from "@/components/ui/textarea"
|
|
14
|
+
import { Label } from "@/components/ui/label"
|
|
15
|
+
|
|
16
|
+
const formSchema = z.object({
|
|
17
|
+
name: z.string().min(1, "お名前を入力してください"),
|
|
18
|
+
email: z.string().email("正しいメールアドレスを入力してください"),
|
|
19
|
+
message: z.string().min(1, "メッセージを入力してください"),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
type FormData = z.infer<typeof formSchema>
|
|
23
|
+
|
|
24
|
+
export function ContactForm() {
|
|
25
|
+
const [formData, setFormData] = useState<FormData>({ name: "", email: "", message: "" })
|
|
26
|
+
const [honeypot, setHoneypot] = useState("")
|
|
27
|
+
const [errors, setErrors] = useState<Partial<Record<keyof FormData, string>>>({})
|
|
28
|
+
const [status, setStatus] = useState<"idle" | "submitting" | "success" | "error">("idle")
|
|
29
|
+
const [errorMessage, setErrorMessage] = useState("")
|
|
30
|
+
|
|
31
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
32
|
+
e.preventDefault()
|
|
33
|
+
setErrors({})
|
|
34
|
+
setErrorMessage("")
|
|
35
|
+
|
|
36
|
+
// バリデーション
|
|
37
|
+
const result = formSchema.safeParse(formData)
|
|
38
|
+
if (!result.success) {
|
|
39
|
+
const fieldErrors: Partial<Record<keyof FormData, string>> = {}
|
|
40
|
+
for (const issue of result.error.issues) {
|
|
41
|
+
const field = issue.path[0] as keyof FormData
|
|
42
|
+
fieldErrors[field] = issue.message
|
|
43
|
+
}
|
|
44
|
+
setErrors(fieldErrors)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setStatus("submitting")
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const response = await fetch(
|
|
52
|
+
`${process.env.NEXT_PUBLIC_CMX_API_URL}/api/v1/sdk/submissions/contact`,
|
|
53
|
+
{
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: { "Content-Type": "application/json" },
|
|
56
|
+
body: JSON.stringify({ ...result.data, _hp: honeypot }),
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error("送信に失敗しました")
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
setStatus("success")
|
|
65
|
+
setFormData({ name: "", email: "", message: "" })
|
|
66
|
+
} catch {
|
|
67
|
+
setStatus("error")
|
|
68
|
+
setErrorMessage("送信に失敗しました。しばらく経ってから再度お試しください。")
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (status === "success") {
|
|
73
|
+
return (
|
|
74
|
+
<div className="rounded-lg border bg-card p-8 text-center">
|
|
75
|
+
<h3 className="text-lg font-semibold mb-2">送信完了</h3>
|
|
76
|
+
<p className="text-muted-foreground">お問い合わせありがとうございます。</p>
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<form onSubmit={handleSubmit} className="space-y-6">
|
|
83
|
+
{/* ハニーポット(非表示) */}
|
|
84
|
+
<div className="hidden" aria-hidden="true">
|
|
85
|
+
<input
|
|
86
|
+
type="text"
|
|
87
|
+
name="_hp"
|
|
88
|
+
tabIndex={-1}
|
|
89
|
+
autoComplete="off"
|
|
90
|
+
value={honeypot}
|
|
91
|
+
onChange={(e) => setHoneypot(e.target.value)}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<div className="space-y-2">
|
|
96
|
+
<Label htmlFor="name">お名前 *</Label>
|
|
97
|
+
<Input
|
|
98
|
+
id="name"
|
|
99
|
+
value={formData.name}
|
|
100
|
+
onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}
|
|
101
|
+
aria-invalid={!!errors.name}
|
|
102
|
+
aria-describedby={errors.name ? "name-error" : undefined}
|
|
103
|
+
/>
|
|
104
|
+
{errors.name && <p id="name-error" className="text-sm text-destructive">{errors.name}</p>}
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div className="space-y-2">
|
|
108
|
+
<Label htmlFor="email">メールアドレス *</Label>
|
|
109
|
+
<Input
|
|
110
|
+
id="email"
|
|
111
|
+
type="email"
|
|
112
|
+
value={formData.email}
|
|
113
|
+
onChange={(e) => setFormData((prev) => ({ ...prev, email: e.target.value }))}
|
|
114
|
+
aria-invalid={!!errors.email}
|
|
115
|
+
aria-describedby={errors.email ? "email-error" : undefined}
|
|
116
|
+
/>
|
|
117
|
+
{errors.email && <p id="email-error" className="text-sm text-destructive">{errors.email}</p>}
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div className="space-y-2">
|
|
121
|
+
<Label htmlFor="message">メッセージ *</Label>
|
|
122
|
+
<Textarea
|
|
123
|
+
id="message"
|
|
124
|
+
rows={5}
|
|
125
|
+
value={formData.message}
|
|
126
|
+
onChange={(e) => setFormData((prev) => ({ ...prev, message: e.target.value }))}
|
|
127
|
+
aria-invalid={!!errors.message}
|
|
128
|
+
aria-describedby={errors.message ? "message-error" : undefined}
|
|
129
|
+
/>
|
|
130
|
+
{errors.message && <p id="message-error" className="text-sm text-destructive">{errors.message}</p>}
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{errorMessage && (
|
|
134
|
+
<div className="rounded-lg border border-destructive bg-destructive/10 p-4 text-sm text-destructive">
|
|
135
|
+
{errorMessage}
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
|
|
139
|
+
<Button type="submit" disabled={status === "submitting"}>
|
|
140
|
+
{status === "submitting" ? "送信中..." : "送信する"}
|
|
141
|
+
</Button>
|
|
142
|
+
</form>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## カスタマイズポイント
|
|
148
|
+
|
|
149
|
+
- `formSchema`: フィールドに合わせて Zod スキーマを変更
|
|
150
|
+
- `formSlug`: Admin で定義したフォームのスラッグに合わせる
|
|
151
|
+
- スタイリング: site-config.md のデザイン方針に沿って調整
|
|
152
|
+
- 送信後の挙動: サンクスメッセージ or リダイレクト
|
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cmx-migrate
|
|
3
|
+
description: |
|
|
4
|
+
既存サイトをCMXに移行するためのスキル。Firecrawl CLI と agent-browser を使ったサイト解析、
|
|
5
|
+
スキーマ自動提案、コンテンツ一括移行、スタイル移行を支援する。
|
|
6
|
+
トリガー: 「サイトを移行」「既存HPをCMXに」「サイトを解析」「ホームページを移行」
|
|
7
|
+
「移行」「マイグレーション」「既存サイトをインポート」「サイト構造を分析」
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# CMX サイト移行スキル
|
|
11
|
+
|
|
12
|
+
既存サイトのURLひとつから、CMXサイトとして再構築するための5ステップ移行フローを提供する。
|
|
13
|
+
|
|
14
|
+
## ツール構成
|
|
15
|
+
|
|
16
|
+
| ツール | 用途 | インストール |
|
|
17
|
+
|-------|------|------------|
|
|
18
|
+
| Firecrawl CLI | URL発見・クロール・構造化抽出 | `npm i -g firecrawl-cli` |
|
|
19
|
+
| agent-browser | スクショ撮影・スタイル情報抽出 | スキルとして利用可能 |
|
|
20
|
+
| cmx-sdk CLI | スキーマ登録・コード生成・コンテンツ投入 | プロジェクト内蔵 |
|
|
21
|
+
|
|
22
|
+
## エージェント設計原則
|
|
23
|
+
|
|
24
|
+
### 親エージェント(メインコンテキスト)が担当
|
|
25
|
+
|
|
26
|
+
- サイト構造の把握・判断
|
|
27
|
+
- ユーザーとの対話・確認
|
|
28
|
+
- スキーマ設計・マッピング
|
|
29
|
+
- 子エージェントの起動と結果集約
|
|
30
|
+
|
|
31
|
+
### 子エージェント(Task tool で起動)に委譲
|
|
32
|
+
|
|
33
|
+
**以下のタスクは必ず子エージェントに委譲する。親のコンテキストウィンドウを節約するため。**
|
|
34
|
+
|
|
35
|
+
1. **コンテンツ変換・投入**(Step 4)
|
|
36
|
+
- crawl-result.json の各ページを MDX に変換
|
|
37
|
+
- `npx cmx-sdk create-content` で投入
|
|
38
|
+
- 5-10件/バッチで処理
|
|
39
|
+
2. **設定ファイル生成**(Step 3 の一部)
|
|
40
|
+
- site-config.md, style-guide.md の生成
|
|
41
|
+
3. **コンポーネント作成**(Step 3 の一部)
|
|
42
|
+
- componentCandidates に基づくコンポーネント実装
|
|
43
|
+
4. **スクショ比較**(Step 5)
|
|
44
|
+
- 移行元と移行先のビジュアル比較
|
|
45
|
+
|
|
46
|
+
### 子エージェントへの指示ルール
|
|
47
|
+
|
|
48
|
+
子に渡すもの:
|
|
49
|
+
- 対象データ(URLリスト or ページデータ)
|
|
50
|
+
- 変換ルール([references/html-to-mdx.md](references/html-to-mdx.md) の内容を簡潔に)
|
|
51
|
+
- 使用コマンド
|
|
52
|
+
- 期待する出力フォーマット(JSON)
|
|
53
|
+
|
|
54
|
+
子に渡さないもの:
|
|
55
|
+
- site-config.md 全文
|
|
56
|
+
- 他のページのデータ
|
|
57
|
+
- スキーマ設計の経緯
|
|
58
|
+
- migration-plan.json 全体(該当部分のみ抜粋)
|
|
59
|
+
|
|
60
|
+
### 子エージェントの出力フォーマット
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"batch_id": "batch-001",
|
|
65
|
+
"results": [
|
|
66
|
+
{ "url": "/blog/post-1", "status": "success", "contentId": "uuid", "slug": "post-1", "title": "記事タイトル" },
|
|
67
|
+
{ "url": "/blog/post-2", "status": "error", "error": "変換エラー: テーブル構造が複雑" }
|
|
68
|
+
],
|
|
69
|
+
"summary": { "total": 10, "success": 9, "error": 1 }
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Step 1: 解析
|
|
76
|
+
|
|
77
|
+
### 1-1. URL発見
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
firecrawl map {url} --limit 1000 -o cmx/migration/urls.json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`urls.json` からURL一覧を読み取り、以下を分析:
|
|
84
|
+
|
|
85
|
+
- URLパターンのグルーピング(`/blog/*`, `/news/*`, `/about` 等)
|
|
86
|
+
- 各グループのページ数
|
|
87
|
+
- 一覧ページ vs 詳細ページの推定
|
|
88
|
+
|
|
89
|
+
### 1-2. 全ページクロール
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
firecrawl crawl {url} --limit 200 --max-depth 3 --wait --progress -o cmx/migration/crawl-result.json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
全ページの Markdown + メタデータ(title, description, OGP)が取得される。
|
|
96
|
+
|
|
97
|
+
### 1-3. 構造化データ抽出
|
|
98
|
+
|
|
99
|
+
各ページグループの代表的な一覧ページに対して Firecrawl の scrape + JSON フォーマットで構造を抽出する。
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
firecrawl scrape {list_page_url} --format json --json-prompt "Extract the list structure: what fields does each item have (title, date, category, thumbnail, author, description)? What categories or tags exist?" -o cmx/migration/structure/{group_slug}.json
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
抽出結果から:
|
|
106
|
+
- コレクションのフィールド候補を推定
|
|
107
|
+
- カテゴリ/タグの既存値を収集
|
|
108
|
+
- データタイプのフィールド構造を推定
|
|
109
|
+
|
|
110
|
+
### 1-4. ビジュアル解析(agent-browser)
|
|
111
|
+
|
|
112
|
+
代表ページ(3-5ページ)に対して agent-browser で:
|
|
113
|
+
|
|
114
|
+
1. **スクリーンショット撮影**
|
|
115
|
+
```
|
|
116
|
+
open {url}
|
|
117
|
+
screenshot cmx/migration/screenshots/home.png
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
2. **スタイル情報抽出**(JS 実行)
|
|
121
|
+
```
|
|
122
|
+
eval "JSON.stringify({
|
|
123
|
+
bodyBg: getComputedStyle(document.body).backgroundColor,
|
|
124
|
+
bodyColor: getComputedStyle(document.body).color,
|
|
125
|
+
bodyFont: getComputedStyle(document.body).fontFamily,
|
|
126
|
+
h1Font: document.querySelector('h1') ? getComputedStyle(document.querySelector('h1')).fontFamily : null,
|
|
127
|
+
h1Size: document.querySelector('h1') ? getComputedStyle(document.querySelector('h1')).fontSize : null,
|
|
128
|
+
linkColor: document.querySelector('a') ? getComputedStyle(document.querySelector('a')).color : null,
|
|
129
|
+
navBg: document.querySelector('nav') ? getComputedStyle(document.querySelector('nav')).backgroundColor : null
|
|
130
|
+
})"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
3. **ナビゲーション構造の取得**
|
|
134
|
+
```
|
|
135
|
+
snapshot
|
|
136
|
+
```
|
|
137
|
+
→ セマンティック構造から nav, header, footer, main を特定
|
|
138
|
+
|
|
139
|
+
### 1-5. 成果物の生成
|
|
140
|
+
|
|
141
|
+
上記の情報を統合して以下を生成:
|
|
142
|
+
|
|
143
|
+
**`cmx/migration/site-analysis.md`** — 人間が読む解析レポート
|
|
144
|
+
|
|
145
|
+
```markdown
|
|
146
|
+
# サイト解析レポート
|
|
147
|
+
|
|
148
|
+
## 基本情報
|
|
149
|
+
- 移行元URL: {url}
|
|
150
|
+
- 解析日: {date}
|
|
151
|
+
- 発見URL数: {n}件
|
|
152
|
+
|
|
153
|
+
## ページ構成
|
|
154
|
+
### コレクション候補
|
|
155
|
+
- /blog/* (25件) → post型コレクション「ブログ」
|
|
156
|
+
- /news/* (15件) → news型コレクション「お知らせ」
|
|
157
|
+
|
|
158
|
+
### 固定ページ
|
|
159
|
+
- /about → 会社概要
|
|
160
|
+
- /contact → お問い合わせ(フォームあり)
|
|
161
|
+
|
|
162
|
+
### データタイプ候補
|
|
163
|
+
- スタッフ一覧(/about 内、5名)
|
|
164
|
+
- FAQ(/faq 内、12件)
|
|
165
|
+
|
|
166
|
+
## スタイル情報
|
|
167
|
+
- メインカラー: {color}
|
|
168
|
+
- フォント: {font}
|
|
169
|
+
- トーン: {tone}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**`cmx/migration/site-map.json`** — 構造化データ(フォーマット詳細は [references/intermediate-format.md](references/intermediate-format.md) を参照)
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Step 2: 計画
|
|
177
|
+
|
|
178
|
+
`site-map.json` を読み込み、ユーザーと対話的に移行計画を策定する。
|
|
179
|
+
|
|
180
|
+
### 2-1. コレクション定義
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
site-map.json の pageGroups から type: "collection" を抽出
|
|
184
|
+
|
|
185
|
+
提案例:
|
|
186
|
+
/blog/* → コレクション "blog"(type: post)
|
|
187
|
+
付属データタイプ: categories, tags
|
|
188
|
+
/news/* → コレクション "news"(type: news)
|
|
189
|
+
付属データタイプ: categories
|
|
190
|
+
|
|
191
|
+
ユーザーに確認 → 確定
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
参考: cmx-schema スキルの [references/migration-scenarios.md](../cmx-schema/references/migration-scenarios.md)
|
|
195
|
+
|
|
196
|
+
### 2-2. データタイプ定義
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
site-map.json の dataTypeCandidates を提案
|
|
200
|
+
|
|
201
|
+
提案例:
|
|
202
|
+
staff — name(text), position(text), photo(image)
|
|
203
|
+
faq — question(text), answer(richtext), category(select)
|
|
204
|
+
|
|
205
|
+
ユーザーに確認 → フィールドの追加・削除・型変更
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
参考: cmx-schema スキルの [references/field-types.md](../cmx-schema/references/field-types.md)
|
|
209
|
+
|
|
210
|
+
### 2-3. コンポーネント候補
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
site-map.json の componentCandidates を提案
|
|
214
|
+
|
|
215
|
+
提案例:
|
|
216
|
+
FeatureCard — icon, title, description(/about, / で使用)
|
|
217
|
+
FAQ — question, answer(/faq で使用)
|
|
218
|
+
|
|
219
|
+
ユーザーに確認 → 必要なものだけ選択
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### 2-4. フォーム定義
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
site-map.json の type: "form" ページを確認
|
|
226
|
+
|
|
227
|
+
提案例:
|
|
228
|
+
contact — name(text), email(email), message(textarea)
|
|
229
|
+
|
|
230
|
+
ユーザーに確認
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### 2-5. ルーティングマップ
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
移行元 → CMX
|
|
237
|
+
/ → src/app/page.tsx(トップページ)
|
|
238
|
+
/blog → src/app/blog/page.tsx(一覧)
|
|
239
|
+
/blog/* → src/app/blog/[slug]/page.tsx(詳細)
|
|
240
|
+
/news → src/app/news/page.tsx(一覧)
|
|
241
|
+
/about → src/app/about/page.tsx(固定ページ)
|
|
242
|
+
/contact → src/app/contact/page.tsx(フォーム)
|
|
243
|
+
|
|
244
|
+
ユーザーに確認
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### 2-6. 成果物
|
|
248
|
+
|
|
249
|
+
**`cmx/migration/migration-plan.json`**
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"sourceUrl": "https://example.com",
|
|
254
|
+
"collections": [
|
|
255
|
+
{
|
|
256
|
+
"slug": "blog",
|
|
257
|
+
"name": "ブログ",
|
|
258
|
+
"type": "post",
|
|
259
|
+
"dataTypes": ["categories", "tags"],
|
|
260
|
+
"sourcePattern": "/blog/*",
|
|
261
|
+
"pageCount": 25
|
|
262
|
+
}
|
|
263
|
+
],
|
|
264
|
+
"dataTypes": [
|
|
265
|
+
{
|
|
266
|
+
"slug": "staff",
|
|
267
|
+
"name": "スタッフ",
|
|
268
|
+
"fields": [
|
|
269
|
+
{ "key": "name", "label": "名前", "type": "text", "required": true },
|
|
270
|
+
{ "key": "position", "label": "役職", "type": "text" },
|
|
271
|
+
{ "key": "photo", "label": "写真", "type": "image" }
|
|
272
|
+
],
|
|
273
|
+
"source": "/about#team",
|
|
274
|
+
"entryCount": 5
|
|
275
|
+
}
|
|
276
|
+
],
|
|
277
|
+
"components": [
|
|
278
|
+
{
|
|
279
|
+
"name": "FeatureCard",
|
|
280
|
+
"props": ["icon", "title", "description"],
|
|
281
|
+
"foundAt": ["/about", "/"]
|
|
282
|
+
}
|
|
283
|
+
],
|
|
284
|
+
"forms": [
|
|
285
|
+
{
|
|
286
|
+
"slug": "contact",
|
|
287
|
+
"name": "お問い合わせ",
|
|
288
|
+
"fields": [
|
|
289
|
+
{ "key": "name", "type": "text", "required": true },
|
|
290
|
+
{ "key": "email", "type": "email", "required": true },
|
|
291
|
+
{ "key": "message", "type": "textarea", "required": true }
|
|
292
|
+
]
|
|
293
|
+
}
|
|
294
|
+
],
|
|
295
|
+
"routes": [
|
|
296
|
+
{ "source": "/", "target": "src/app/page.tsx", "type": "static" },
|
|
297
|
+
{ "source": "/blog", "target": "src/app/blog/page.tsx", "type": "collection-list" },
|
|
298
|
+
{ "source": "/blog/*", "target": "src/app/blog/[slug]/page.tsx", "type": "collection-detail" },
|
|
299
|
+
{ "source": "/about", "target": "src/app/about/page.tsx", "type": "static" },
|
|
300
|
+
{ "source": "/contact", "target": "src/app/contact/page.tsx", "type": "form" }
|
|
301
|
+
],
|
|
302
|
+
"style": {
|
|
303
|
+
"colors": { "primary": "#2563eb", "text": "#1e293b", "background": "#ffffff" },
|
|
304
|
+
"fonts": { "heading": "Noto Sans JP", "body": "Noto Sans JP" },
|
|
305
|
+
"tone": "プロフェッショナル・クリーン"
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Step 3: 構築
|
|
313
|
+
|
|
314
|
+
### 3-1. スキーマ登録(親エージェント)
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
# コレクション登録
|
|
318
|
+
npx cmx-sdk create-collection --json '{"type":"post","slug":"blog","name":"ブログ","dataTypes":["categories","tags"]}'
|
|
319
|
+
|
|
320
|
+
# グローバルデータタイプ登録
|
|
321
|
+
npx cmx-sdk create-data-type --json '{"slug":"staff","name":"スタッフ","fields":[...]}'
|
|
322
|
+
|
|
323
|
+
# 既存カテゴリ・タグのエントリ作成
|
|
324
|
+
npx cmx-sdk create-data-entry --type-slug blog-categories --json '{"name":"技術"}'
|
|
325
|
+
npx cmx-sdk create-data-entry --type-slug blog-tags --json '{"name":"Next.js"}'
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### 3-2. コード生成(親エージェント)
|
|
329
|
+
|
|
330
|
+
<!-- cmx-sync:start id="skill-cmx-migrate-codegen-block" -->
|
|
331
|
+
```bash
|
|
332
|
+
npx cmx-sdk codegen types
|
|
333
|
+
npx cmx-sdk codegen pages --template layered
|
|
334
|
+
```
|
|
335
|
+
<!-- cmx-sync:end -->
|
|
336
|
+
|
|
337
|
+
### 3-3. 設定ファイル生成(子エージェントに委譲)
|
|
338
|
+
|
|
339
|
+
子エージェントに以下を渡す:
|
|
340
|
+
- `migration-plan.json` の `style` セクション
|
|
341
|
+
- site-analysis.md のスタイル情報
|
|
342
|
+
- スクリーンショット
|
|
343
|
+
|
|
344
|
+
子が `cmx/site-config.md` と `workflows/style-guide.md` を生成。
|
|
345
|
+
|
|
346
|
+
### 3-4. コンポーネント作成(子エージェントに委譲)
|
|
347
|
+
|
|
348
|
+
子エージェントに以下を渡す:
|
|
349
|
+
- `migration-plan.json` の `components` セクション
|
|
350
|
+
- cmx-component スキルの参照情報
|
|
351
|
+
|
|
352
|
+
子が各コンポーネントの JSON定義 + TSX実装 + index.ts 更新を実施。
|
|
353
|
+
|
|
354
|
+
### 3-5. 成果物
|
|
355
|
+
|
|
356
|
+
**`cmx/migration/build-result.json`**
|
|
357
|
+
|
|
358
|
+
```json
|
|
359
|
+
{
|
|
360
|
+
"collections": [
|
|
361
|
+
{ "slug": "blog", "id": "uuid", "dataTypes": ["blog-categories", "blog-tags"] }
|
|
362
|
+
],
|
|
363
|
+
"dataTypes": [
|
|
364
|
+
{ "slug": "staff", "id": "uuid" }
|
|
365
|
+
],
|
|
366
|
+
"dataEntries": {
|
|
367
|
+
"blog-categories": [
|
|
368
|
+
{ "name": "技術", "id": "uuid" },
|
|
369
|
+
{ "name": "デザイン", "id": "uuid" }
|
|
370
|
+
],
|
|
371
|
+
"blog-tags": [
|
|
372
|
+
{ "name": "Next.js", "id": "uuid" }
|
|
373
|
+
]
|
|
374
|
+
},
|
|
375
|
+
"components": ["FeatureCard", "FAQ"],
|
|
376
|
+
"generatedPages": ["src/app/blog/page.tsx", "src/app/blog/[slug]/page.tsx"]
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## Step 4: コンテンツ投入
|
|
383
|
+
|
|
384
|
+
### 4-1. バッチ分割(親エージェント)
|
|
385
|
+
|
|
386
|
+
```
|
|
387
|
+
crawl-result.json のページ一覧を読み込み
|
|
388
|
+
migration-plan.json のコレクションマッピングで振り分け
|
|
389
|
+
|
|
390
|
+
batch-001: blog の post-1 〜 post-10
|
|
391
|
+
batch-002: blog の post-11 〜 post-20
|
|
392
|
+
batch-003: news の news-1 〜 news-10
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### 4-2. 子エージェントに委譲
|
|
396
|
+
|
|
397
|
+
各バッチごとに子エージェントを起動(Task tool)。
|
|
398
|
+
|
|
399
|
+
**子への指示テンプレート:**
|
|
400
|
+
|
|
401
|
+
```
|
|
402
|
+
以下の {n} 件のページをMDXに変換し、CMXに投入してください。
|
|
403
|
+
|
|
404
|
+
コレクション: {collection_slug}
|
|
405
|
+
変換ルール:
|
|
406
|
+
- HTML→MDX変換テーブル(references/html-to-mdx.md の内容を簡潔に記載)
|
|
407
|
+
- script, style, iframe は除去
|
|
408
|
+
- 相対URLは絶対URLに変換
|
|
409
|
+
- 画像は外部URLのまま維持
|
|
410
|
+
|
|
411
|
+
使用コマンド:
|
|
412
|
+
npx cmx-sdk create-content --collection {slug} --json '{"title":"...","slug":"...","description":"...","mdx":"..."}'
|
|
413
|
+
|
|
414
|
+
ページデータ:
|
|
415
|
+
{crawl-result.json から該当ページのデータを抜粋}
|
|
416
|
+
|
|
417
|
+
結果を以下のJSON形式で返してください:
|
|
418
|
+
{ "batch_id": "...", "results": [...], "summary": { "total": n, "success": n, "error": n } }
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### 4-3. 参照設定(親エージェント)
|
|
422
|
+
|
|
423
|
+
コンテンツ投入後、カテゴリ・タグの参照を設定:
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
npx cmx-sdk set-content-references --id {contentId} --json '{
|
|
427
|
+
"references": [
|
|
428
|
+
{ "fieldSlug": "categories", "dataEntryIds": ["{categoryId}"] },
|
|
429
|
+
{ "fieldSlug": "tags", "dataEntryIds": ["{tagId1}", "{tagId2}"] }
|
|
430
|
+
]
|
|
431
|
+
}'
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### 4-4. 成果物
|
|
435
|
+
|
|
436
|
+
**`cmx/migration/content-result.json`**
|
|
437
|
+
|
|
438
|
+
```json
|
|
439
|
+
{
|
|
440
|
+
"total": 40,
|
|
441
|
+
"success": 38,
|
|
442
|
+
"error": 2,
|
|
443
|
+
"collections": {
|
|
444
|
+
"blog": { "total": 25, "success": 24, "error": 1 },
|
|
445
|
+
"news": { "total": 15, "success": 14, "error": 1 }
|
|
446
|
+
},
|
|
447
|
+
"errors": [
|
|
448
|
+
{ "url": "/blog/complex-table-post", "error": "テーブル変換エラー" },
|
|
449
|
+
{ "url": "/news/special-format", "error": "特殊なHTML構造" }
|
|
450
|
+
]
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## Step 5: スタイル調整
|
|
457
|
+
|
|
458
|
+
### 5-1. スクショ比較
|
|
459
|
+
|
|
460
|
+
agent-browser で移行後のページのスクショを撮影し、Step 1 で取得した移行元のスクショと並べて比較。
|
|
461
|
+
|
|
462
|
+
**子エージェントに委譲:**
|
|
463
|
+
- 各ページのスクショ撮影
|
|
464
|
+
- 移行元スクショとの差分ポイント列挙
|
|
465
|
+
|
|
466
|
+
### 5-2. 差分報告
|
|
467
|
+
|
|
468
|
+
```
|
|
469
|
+
移行元 vs 移行後の主な差分:
|
|
470
|
+
|
|
471
|
+
1. ヘッダーの背景色が異なる(元: #1a1a2e → 現: デフォルト白)
|
|
472
|
+
2. フォントサイズが全体的に小さい
|
|
473
|
+
3. カードのシャドウが効いていない
|
|
474
|
+
4. フッターのレイアウトが2カラムだったが1カラムになっている
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### 5-3. 調整実施
|
|
478
|
+
|
|
479
|
+
ユーザーと確認しながら、cmx-style スキルを使ってTailwind CSSで調整。
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
## 中間ファイル一覧
|
|
484
|
+
|
|
485
|
+
```
|
|
486
|
+
cmx/migration/
|
|
487
|
+
├── urls.json ← Step 1: firecrawl map の結果
|
|
488
|
+
├── crawl-result.json ← Step 1: firecrawl crawl の結果
|
|
489
|
+
├── site-analysis.md ← Step 1: 人間向け解析レポート
|
|
490
|
+
├── site-map.json ← Step 1: 構造化データ
|
|
491
|
+
├── screenshots/
|
|
492
|
+
│ ├── original/ ← Step 1: 移行元スクリーンショット
|
|
493
|
+
│ └── migrated/ ← Step 5: 移行後スクリーンショット
|
|
494
|
+
├── structure/ ← Step 1: 構造化抽出結果
|
|
495
|
+
│ └── {group}.json
|
|
496
|
+
├── migration-plan.json ← Step 2: 確定した移行計画
|
|
497
|
+
├── build-result.json ← Step 3: 構築結果(登録済みID)
|
|
498
|
+
└── content-result.json ← Step 4: 投入結果
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
これらのファイルはセッション間の引き継ぎに使用する。`.gitignore` に `cmx/migration/` を追加することを推奨(crawl-result.json が大きいため)。
|