Up-frontier システム開発部のKです。
iOSやvisionOSの開発をしています。
AIは主に Claude を使っています。
AIが出力した内容をHTMLファイルに変換して視覚的に理解する、という手法を目にする機会が増えました。
Claude にも Artifacts などのツールが導入されています。
また、個人でスキルを公開している方もいらっしゃいます。
AI が大量にアウトプットしてくるので認知負荷を下げる Skill を作った(Zenn)
(以降、この記事を「参考記事」と呼びます)
参考記事のスキルは、実装計画書やPRなどのテキストを入力として受け取り、内容を「意味」としてYAMLに書き出します。そのうえで、読み手に合わせた「見せ方」を別のYAMLに書き、この2つをもとにHTMLを出力します。とても素晴らしい発想のスキルです。
私も普段からこのスキルを使わせていただいています。
この「意味と見せ方を分ける」構造にならって、私もスキルを作ってみました。
社内で展開するスキルのため公開はできないことをご了承ください。
この記事では参考記事と同じにした点・変えた点・素直に真似できなかった理由に絞って書きます。
なぜ Apple公式ドキュメントから教材を生成したかったのか
iOSやvisionOSのアプリ開発では、初めて導入するAppleのフレームワークを調査し、実装できるレベルに達するまでに、いくつかハードルがあると感じています。
ドキュメントが膨大
Appleの公式ドキュメントは膨大なので、自分が実装したい範囲まで知識を得るためにどこまで読めばよいか判断しづらいことがあります。
WWDCの動画は新機能が中心
WWDCの動画は新機能の紹介がメインなので、フレームワーク初心者は過去動画をたどる必要があり面倒です。
サンプルアプリは実装コードまでの導線が長い
公式のサンプルアプリはアプリとしての完成度が高いものの、該当フレームワークの実装コードまでの導線が長く、解読が大変です。
これらのハードルを下げるべく、本格的に調査する前に最低限の実装を体験できるハンズオン教材があればいいな、と常々思っていました。
「App Intents の入門教材を作って」と Claude に頼むと、まずまずの出来の教材を作成してくれます。しかし毎回プロンプトから生成すると、フォーマットがバラバラだったり、書かれている内容のソースが追えなかったりと不都合があります。
参考記事の設計なら、この2つの不都合をそれぞれ「見せ方」と「意味」の分離で解決できそうだと思ったのが、スキル化の発端です。
参考記事から取り入れたもの
参考記事からは主に骨格を取り入れました。
- 2段構成のスキル。 前段が「意味」をYAMLに書き出し、後段がそれをHTMLにします。私のスキルも、ドキュメントを構造化する前段と、教材を生成する後段の2本に分けました。
- 中間表現はYAML。 人が読めて差分が取れる形にしておくと、生成物がおかしいときにどの段で崩れたかを追えます。
- オフラインで自己完結したHTML。 外部CDN・通信・Cookieを使いません。
- 生成物を機械検査する。 参考記事のスキルには外部URLの混入などを検査するバリデータがあり、モデルに任せずスクリプトで検査するやり方をそのまま取り入れました。
ここまでは素直に真似できました。
違い 1: 「意味」の前に「地図」が必要だった
参考記事のスキルは、実装計画書やPRといった手元にあるテキストを入力にします。読むべきものは最初から全部揃っていて、後はそれを意味として圧縮する、という流れでした。
一方、私のスキルの入力はWebにあるApple公式ドキュメントです。App Intents だけでも数十ページあり、全部取ってから意味に圧縮するのは無理があります。かといって必要なページだけ取ろうとすると、「どのページが必要か」を先に知っていないといけません。
そこで前段のスキルは、意味を抽出するのではなく目次だけを索引として写すことにしました。ページ本文は一切取らず、各ページのタイトル・概要文・パスだけを記録します。
source:
site: "Apple Developer Documentation"
root_title: "App Intents"
root_path: "developer.apple.com/documentation/appintents"
fetched_at: "2026-08-12"
groups:
- id: getting-started-with-the-app-intents-framework
title: "Getting started with the App Intents framework"
path: "developer.apple.com/documentation/appintents/getting-started-with-the-app-intents-framework"
abstract: "Make your app's actions and content available to the rest of the system using the App Intents framework."
kind: article
role: article
これは参考記事でいう core.yaml とは役割が違います。意味そのものではなく、後段が「ユーザーの要求をどのページに振り分けるか」を決めるための地図です。
索引には「タイトルからパスを推測しない。ソースが公開しているURLをそのまま写す」という規則を置きました。後段のスキルは、この記録済みのパスだけを使ってページを取得します。
索引は一度作れば別の教材を作るときにも使い回せます。「同じ core.yaml から別のビューを派生できる」という参考記事の利点を、私のスキルでは「同じ索引から別の教材を作れる」という形で再現できました。
違い 2: 取得をWebFetchに任せられなかった
WebFetchとはWeb上にあるページを取得するClaude Codeの組み込みツールです。
参考記事のスキルは入力がテキストなので、取得という工程自体がありません。私のスキルではここが最初の壁でした。
AppleのドキュメントはAppleのドキュメント生成ツールであるDocC形式を採用していてWebサイトの内部ではVue.jsが使われています。サーバーが返すHTMLはほぼ空のシェルで、実際のコンテンツはJavaScriptが起動してから専用のAPIを叩いて描画されます。
開発者が普段見るURLは以下です。
https://developer.apple.com/documentation/appintents
これをWebFetchすると以下のメッセージが返ってきて、中身が取得できません。
This page requires JavaScript. Please turn on JavaScript in your browser and refresh the page to view its content.
そのためWebFetchをやめ、ドキュメントのJSONデータをスクリプトで取得する方式に変更しました。Appleは描画前の生データを以下のように別の場所にJSONで置いています。
/documentation/appintents
→ /tutorials/data/documentation/appintents.json
/documentation/appintents/app-entities
→ /tutorials/data/documentation/appintents/app-entities.json
App Intentsの場合は以下となります。
https://developer.apple.com/tutorials/data/documentation/appintents.json
なお、AppleはURLの末尾に.mdを付けた形で、同じページのMarkdown版も配信しています。先ほどのメッセージの下には、自動化ツール向けの案内としてこのMarkdown版へのリンクが置かれています。
https://developer.apple.com/documentation/appintents.md
こちらはWebFetchでも取得できますが、索引を作る用途ではJSONより情報が落ちるため採用しませんでした。親ページのMarkdownには、子ページが記事なのかサンプルコードなのかを示す区分がなく、別のドキュメントツリーにあるページはリンクが解決されないまま識別子の文字列で残ります。索引はこの区分と、ソースが公開しているパスを記録するためにあるので、どちらも欠かせませんでした。
DocCのJSONはフィールドが固定なので、索引YAMLへの引き当ては単純な機械作業です。「どのグループにどのページが属するか」「このページは記事なのかサンプルコードなのか」といった対応づけを、モデルに読ませて写させる理由がありません。むしろモデルを通すと要約や省略が入って、パスが微妙に変わる事故が起きます。取得から索引の組み立てまでを丸ごとスクリプトにして、モデルが判断するのは「どのルートを索引化するか」だけにしました。
今回はAppleドキュメント(DocC形式)を対象にしたので割り切ってスクリプト化しましたが、任意のドキュメントサイトに汎用化するなら、取得の手段は高性能なモデルに任せた方がよいのかもしれません。
また、このJSONエンドポイントはAppleの公開仕様ではないため、今後崩れる可能性があります。その場合は.mdを使う作りへ替える必要があります。
違い 3: 「意味」が1枚のYAMLに収まらなかった
参考記事の core.yaml は、要点を圧縮して残し、原文を長く転記しないという方針です。短い抜粋までは許容されています。読み手の認知負荷を下げることが目的なので、参考記事のスキルは原文ではなく要約文を使います。
ハンズオン教材では、これが逆になりました。要約した時点で、教材がドキュメントから生成されたものである裏付けが消えるからです。公式ドキュメントからハンズオンを作るからこそ、記述の出典にできる限り忠実でありたいと考えました。
結果として、参考記事で core.yaml 1枚だったところが、私のスキルでは性質の違う2枚に割れました。
- 教材の骨格。 何を作るか、どういう順で、各手順の根拠はどの出典か。
- 出典の記録。 取得したページから、要約せずに逐語で引用した事実の一覧。
そして骨格側のコード・要件・検証点のひとつひとつに、どこから来たかを示すoriginを付けています。
| origin | 出典の件数 | 補足の要否 | 説明 |
|---|---|---|---|
verbatim_from_doc | 1 件以上 | 不要 | ドキュメントの記述をそのまま使った |
adapted | 1 件以上 | 必須 | 公式の記述を、この教材の型名・構成に合わせて書き換えた |
synthesized | 2 件以上 | 必須 | 複数ページの記述を統合した |
authored | 0 件(空) | 必須 | ドキュメントに記述がなく、モデルが埋めた |
authoredは減らすべき値ですが、このoriginが付く項目が0件になることはありません。プロジェクト作成の手順など、どのドキュメントにも書かれていない部分は必ずあるからです。
このoriginの値ごとに「出典は何件以上」「補足は必須か」などの制約を入れていて、守られているかを機械的に検算します。当初はこの検算をモデルに判断させていましたが、こちらもスクリプト化できました。
違い 4: 「見せ方」はスクリプトで完結させる
参考記事のスキルでは、view.yaml に読み手や好みの表現形式を書き、それに従ってモデルがビューのHTMLを書きます。同じ意味からテーブル・ワークツリー(リポジトリ構造や読む順番を階層ツリーで示す形式)・初心者向けといった複数の見せ方に派生するため、HTMLをモデルが書くのは当然の設計です。
ハンズオン教材は簡単な解説文とコードだけのシンプルなものなので、読み手ごとに見せ方を派生させる必要がありませんでした。見せ方は毎回同じで構いません。
そこでHTMLのテンプレートを1つだけにし、モデルはHTMLを一切書かないことにしました。
YAMLの中身を引数としてビルドスクリプトを実行する形にしたので、レイアウトを変えたいときはテンプレートを直して再ビルドするだけで済みます。
ただ、これで終わりにはできませんでした。教材の骨格YAMLは「なぜこの手順を置いたか」「何が確認できるか」を記録するテキストでしかありません。そのまま画面に流し込むと事務的な箇条書きになり、受講者に優しい教材ではなく、ただの指示書のようなテキストになってしまいました。
そこで、受講者が読む文章だけを別ファイルに書き起こしました。
タイトルとソースコード以外の、導入・コードの説明・注意書き・出典の日本語要旨など、画面に表示されるテキストはこのファイルで管理します。書き起こしがない項目は、骨格YAMLの記録がそのまま表示されます。
{
"lead": "「ヘイ Siri、〇〇で水を記録して」——こう話しかけるだけで自分のアプリが動く。あれを作れるようになるのが、このレッスンのゴールです。\n\n仕組みを先に一言でいうと、アプリが「うちはこういう操作ができます」とシステムに差し出しておく、それだけです。差し出す単位が app intent で、それに話し言葉を添えたものが App Shortcut です。Siri はその一覧を見て、聞き取った言葉に一番近いものを実行しています。\n\n題材は、飲んだ水の量を記録するだけの小さなアプリです。画面はボタン 1 つと数字だけ。そのぶん App Intents の側に集中できます。\n\n進む順番は、アプリ本体を作る → 操作を 1 つ差し出す → ショートカットアプリで動くことを確かめる → 量を受け取れるようにする → 結果を言葉で返す → 話し言葉を登録する → Siri に頼む、の 7 段階です。Siri が出てくるのは最後の 2 手順だけで、そこに至るまでの土台がこのレッスンの大半になります。",
"goal": "iPhone に向かって「WaterLog で水を記録」と話しかけると、アプリを開かないまま記録が増え、Siri が結果を読み上げてくれる状態になります。",
"requirements_lead": "特別なツールは要りません。次の 3 つだけ確認してください。",
"requirements": [
"iOS 16.0 以降を対象にできること(App Intents が使えるのはこの世代からです)",
"struct を書いたり、プロトコルに準拠させたりする程度の Swift が読めること",
"ショートカットアプリと Siri を試せるシミュレータか実機があること(最後の 2 手順で使います)"
],
...
}
参考記事の「意味と見せ方」の2層が、私のスキルでは「骨格・出典・文章」の3層になりました。
違い 5: 「URL禁止」を緩めた
参考記事のバリデータは、HTMLにhttp://やhttps://の文字列があれば落とします。オフラインの自己完結を守るには明快な規則で、私も最初はそのまま取り入れました。
ところが教材では、追跡可能性を売りにしているのに受講者が出典に飛べないという矛盾が出ます。教材が引用するのは索引に記録した公開ドキュメントのURLだけなので、HTMLからリンクを外す処理に意味がありませんでした。
そこで禁止の対象を「URLという文字列」から「自動的に通信が発生するもの」に狭めました。外部スクリプトやスタイルシート、fetchなどは禁止のままにし、クリックして初めて開く<a href>だけは許します。ただし、モデルにURLを書かせると、引用していないページへのリンクを防げません。そこでモデルは引き続きURLを一切書かず、ビルドスクリプトが出典記録のパスにhttps://を前置してリンクを組み立てるようにしました。
これらの処理もスクリプト化しているため、教材が引用していないページへは原理的にリンクできません。
禁止を条件付き許可に変えるときは、検査も一緒に変えないと規約が形骸化します。バリデータは、URLの判定についてだけ文字列のgrepをやめ、HTMLパーサで属性単位に見るようにしました。
やらないと決めたこと
参考記事の構造を離れた分、責務も削りました。
- 受講者が作ったプロジェクトのビルドが通ることは保証しない。 教材はドキュメントから生成した整形物であり、受講者が教材どおりに実装したプロジェクトがコンパイルできるかは責務の外です。ここを保証しようとすると受講者の環境への依存が入り、「ドキュメントを取得できる」以外の前提が要らないという単純さが崩れます。
- 図版は入れない。 画像を埋め込むとHTMLが数MBになり、Appleの画像を再配布する形にもなります。SVGをモデルに書かせると「モデルはHTMLを書かない」原則に反します。画像の埋め込みもSVGの生成も採れないため、図版そのものを見送りました。図が効く題材では表現力が落ちるので、将来の再検討対象です。
- 時刻ベースのキャッシュ無効化は入れない。 取得したページはJSONデータのままキャッシュとして保存し、上書きするのは明示的に更新モードを走らせたときだけにしています。
感想
「意味と見せ方を分ける」という骨格そのものは、そのまま真似できました。真似できなかったのは中身の方で、「意味」に何を求めるかによってファイルを細分化する必要がある、というのが一番の学びです。認知負荷を下げることが目的なら、意味は圧縮してよく1枚で足ります。しかし出典を追える教材にするなら逐語の記録が別に要り、見せ方を固定するなら文章の層が別に要りました。
もう1つの学びは、機械的に処理できる範囲を増やすように意識すると、モデルに任せる余地が減るため出力が安定しやすい点です。索引の組み立て、originの検算、URLの生成、文章に混ざった未出典の固有名詞を検出する処理など、最初はどれもスキルの指示書(SKILL.md)に自然言語で書いていたものでした。これらをすべてスクリプトで処理するようにしたため、プロンプトの書き方に左右されにくい教材を出力できるようになりました。
参考記事の「最後にバリデータで検査する」という一点を、工程の各所に広げていった結果とも言えます。
参考記事ほど単純な構成にできなかった理由は、対象がテキストではなくWebドキュメントだったこと、そして生成物に追跡可能性を求めたことの2つに集約されます。どちらも参考記事のスキルにはなかった要件なので、形が変わるのは当然でした。それでも出発点があったおかげで「どこを変えたのか」を自分で説明できる状態になったのは、ゼロから作ったときには得られなかったことだと思います。
