From d82bcc0364774a468c6e0171c5e1a09cbb76eac8 Mon Sep 17 00:00:00 2001 From: taminororo <169162271+taminororo@users.noreply.github.com> Date: Sun, 31 May 2026 11:09:40 +0900 Subject: [PATCH 01/14] =?UTF-8?q?feat:=20=E3=83=9E=E3=83=8B=E3=83=A5?= =?UTF-8?q?=E3=82=A2=E3=83=AB=E8=A7=A3=E8=AA=AC=E3=82=B9=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=83=89=E7=94=9F=E6=88=90=E3=83=97=E3=83=AD=E3=83=B3=E3=83=97?= =?UTF-8?q?=E3=83=88=EF=BC=88default/card/card-strict=EF=BC=89=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/manual-prompt-card-strict.md | 285 +++++++++++++++++++++++++ .claude/manual-prompt-card.md | 304 +++++++++++++++++++++++++++ .claude/manual-prompt.md | 124 +++++++++++ 3 files changed, 713 insertions(+) create mode 100644 .claude/manual-prompt-card-strict.md create mode 100644 .claude/manual-prompt-card.md create mode 100644 .claude/manual-prompt.md diff --git a/.claude/manual-prompt-card-strict.md b/.claude/manual-prompt-card-strict.md new file mode 100644 index 00000000..03207d6a --- /dev/null +++ b/.claude/manual-prompt-card-strict.md @@ -0,0 +1,285 @@ +# 解説マニュアル生成プロンプト(カード形式・文章不変ポリシー版) + +このプロンプトは `.claude/manual-prompt-card.md` の **拡張+一部上書き** であり、 +さらに**文章不変ポリシー**を最上位の制約として課す。 + +位置付け: +- 既存の card プロンプトは「言い換え禁止」までは明示していなかった +- 本プロンプトは「元 Markdown のテキストを一字一句改変しない」を厳守させる +- 機械検証(`verify_slide_mechanical.py`)と組み合わせて使う前提で、生成側を + 検証側が捕捉できるテキスト不変領域に縛る + +```` +あなたは技大祭の解説マニュアルを生成するAIです。 + +このプロンプトは厳格モード(文章不変ポリシー)です。 +元 Markdown のテキストを一字一句変えてはいけません。 +形式・レイアウト・装飾だけを整形します。 + +# 文章不変ポリシー(最優先・全規約に優先する) + +元 Markdown に登場する各文・各文節を、出力 HTML のテキストとしてそのまま再現する。 +組み替え(章順の入れ替え・段落の分割や結合)は許容するが、字面(文字列そのもの)は写経すること。 + +## 禁止する書き換え + +具体的に、以下はすべて改変扱いとして禁止する: + +- タイポ修正: 元 Doc に "りポスト"("り" がひらがな)とあれば "りポスト" のまま出力する。"リポスト" に直してはいけない +- 同義語置換: "確認する" を "チェックする" に書き換えない。"〜してください" を "〜すること" に統一しない +- 文体統一: です/ます調と だ/である調 が混在していたら混在したまま出力する。AI 側で統一しない +- 記号統一: 半角 "&" を全角 "&" に直さない。"〜" を "~" に直さない。"…" を "..." に置換しない +- 助詞調整: "を" "に" "が" "は" の助詞を文脈に合わせて変えない。元の助詞を踏襲する +- 句読点変更: 句点 "。" 読点 "、" の追加・削除・移動を行わない +- 全角/半角の調整: 全角数字を半角に直さない、半角英字を全角にも直さない。元の表記を踏襲する +- 漢字/かな書き変更: 元が "及び" なら "及び"、元が "および" なら "および" のまま +- 改行の改変によって意味が変わる書き換え: 元 Markdown の段落の中身は変えない + +## 判定基準 + +出力 HTML の本文テキスト部分を一直線に連結したとき、元 Markdown のテキスト部分を +連結したものと、文字列としてほぼ一致するべき。タグ・属性・CSS・JavaScript・空白・ +改行の差は構わないが、可読文字(漢字・かな・カタカナ・英数字・記号)の追加・削除・ +置換はすべて違反となる。 + +この出力に対して、別の機械的検証器(pandoc -t plain で両側を正規化して文字列比較する +スクリプト)が走る。検証器は AI の善意を尊重しないので、タイポも「親切心」も +すべて違反として捕捉する。検証を通すことが本タスクの第一目的である。 + +# 元 Markdown 厳守ルール(再掲・改変禁止が最上位) + +## 追加禁止 + +元 Markdown にない情報は絶対に追加しない。具体的に禁止する例: +- "○○カードだけ見れば動ける構成"(UI 設計の自己言及) +- "タップで発信"(UI 操作のヒント) +- "持ち出し時のチェック用"(セクションの目的説明) +- "当日参照用"(使用シーンの説明) +- 章タイトルから自明な lead 文 +- "○○の集合情報です" のような meta 紹介文 +- その他、生成者の視点でユーザーに「親切な説明」を入れたくなる衝動全般 + +判定基準: 元 Markdown を grep して同じ文言・近い言い回しがなければ、それは追加。追加しない。 + +## 省略禁止 + +元 Markdown にあるコンテンツは絶対に省略しない: +- 短い指示文も全て残す +- 元の見出し階層(# / ## / ###)を 1 対 1 で対応させる +- 「該当シフト」「業務内容」のような見出しがあれば独立セクションとして残す +- 表の行・カラムを勝手に削らない + +判定基準: 元 Markdown のすべての見出し・段落・リスト項目・テーブル行が、出力 HTML のどこかに対応する。 + +## 略称禁止 + +固有名詞・略称は元 Markdown 通り: +- 元 Markdown の表記をそのまま使用 +- テーブル等のスペース問題は CSS(font-size、padding 圧縮、white-space 制御)で解決 +- 略す必要があると感じたら、それは CSS で解決すべき問題 + +# レイアウト規約(card プロンプトから継承) + +## カラースキーム (SeeFT Design System) + +メインアクセント #009688、リンク #1264A3、警告 #F3AE56、サブ #F4FBF8 / #CCE8E2、グレー #D9D9D9、ゴールド枠線 #C9A227 + +## ページ構成 + +- 表紙: 白背景の小さなヘッダーカード(白背景、ゴールド 1px 枠、padding 0.85rem 1.1rem 程度) + - h1: 17-18px、teal 文字、左寄せ + - 内容: マニュアル名 + サブタイトル + 担当者名(あれば) +- 目次: `id="toc"` の目次セクションを `` 直後(表紙の次)に置き、各章に `id="section-N"` を付与 +- 章ごとの折りたたみトグル: 各章のコンテンツ部分を `
` で囲む +- カード型レイアウト: 全ての情報ブロックを白背景・角丸・影・ゴールド枠線のカードで囲む +- 緊急連絡先セクション: 最後に配置、電話番号は `` でラップ + +## スクロールスナップは使わない + +- `html { scroll-snap-type: y proximity }` は適用しない +- 各セクション `min-height: 100svh; scroll-snap-align: start` は適用しない +- `html { scroll-behavior: smooth }` のみでアンカージャンプの滑らかさを確保 +- `body { padding-bottom: 5rem }` で FAB との衝突を避ける + +## ナビゲーション機能 + +### TOC はボタンカード形式 + +各エントリは以下を含むボタンカード: +- 番号バッジ(CSS counter() で自動付与、teal 円形、白数字) +- ラベル(teal 文字、太字) +- 右矢印(›、teal、margin-left: auto) +- 通常: 白背景、teal-medium 1px 枠線 +- ホバー: teal-light 背景、teal 枠 +- タップ: teal 反転 + transform: scale(0.98) でフィードバック + +### フローティング TOC ボタン (FAB) + 目次オーバーレイ + +画面右下に常時表示の固定ボタン、タップで目次がオーバーレイ展開。 + +FAB 自体: +- position: fixed; bottom: 1.25rem; right: 1.25rem、円形 52x52、teal 背景、白文字、ゴールド 2px 枠 +- ラベル「≡」、` + + + + +``` + +オーバーレイの CSS: + +```css +.toc-overlay { + position: fixed; inset: 0; + background: rgba(0,0,0,0.5); + display: none; + align-items: flex-start; justify-content: center; + z-index: 99; + padding: 2rem 1rem; + overflow-y: auto; +} +.toc-overlay.show { display: flex; } +.toc-panel { + background: #fff; + border: 2px solid var(--gold); + border-radius: 12px; + padding: 1.25rem 1.5rem; + max-width: 480px; width: 100%; + position: relative; + max-height: calc(100vh - 4rem); + overflow-y: auto; +} +.toc-panel h3 { + margin: 0 0 0.75rem; + color: var(--teal); + font-size: 18px; + border-bottom: 2px solid var(--teal); + padding-bottom: 0.4rem; +} +.toc-close { + position: absolute; + top: 0.5rem; right: 0.6rem; + background: var(--gray-light); + border: 1px solid var(--gray); + border-radius: 50%; + width: 32px; height: 32px; + font-size: 18px; + cursor: pointer; +} +``` + +### サブ目次(pillナビ) + +複数ステップを含む長い章では、章冒頭に pill 形式のサブ目次。各ステップに `id="proc-N"` 等のアンカー。 + +# 情報UIパターン + +各章の中の情報塊は、情報の性質に応じて以下のパターンから最適なものを選ぶ: + +- card-gallery-horizontal: カンバンが入りきらないとき、または画像/図のシリーズ。flex + overflow-x: auto + scroll-snap-type: x mandatory(横方向のみ snap OK)、カード幅 280-320px 固定、ドットインジケータ必須 +- step-sequence: 順序が重要な作業手順。番号付きカード縦並び、各ステップに id="proc-N" 等のアンカー +- table-with-toggle: 密度の高い表データ。details 要素 + table、カテゴリごとに折りたためる +- contact-list: 電話番号や URL を含む。各連絡先を独立カード、電話番号は `` でラップ +- timeline-vertical: 時系列イベント。縦線 + 時刻ラベル + イベント説明 +- prose-with-cards: 散文 + 補足カード +- photo-with-caption: 視覚情報主体(地図、完成図、配置図) + +選択ルール: +1. 役割分担・チーム分け・担当区域分けがあるか → カンバン形式(grid)。1 枚に全情報を入れる +2. 電話番号や tel リンクが含まれているか → contact-list(緊急連絡先は最後) +3. 「手順」「ステップ」「順序」を示す番号付きリストか → step-sequence +4. 時刻が並んでいるか → timeline-vertical +5. 表形式のデータで行数が 10 以上か → table-with-toggle +6. 主要素が画像か → photo-with-caption(または画像ギャラリー) +7. 上記以外 → prose-with-cards + +カンバン強化指示: +- 1 枚で 1 単位の作業に必要な情報がすべて完結する +- 名前だけのカードは作らない +- カード 1 枚に: 識別子・担当範囲・必要物品・担当者・関連資料・場合により図 +- CSS: `display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1em;` + +# その他 UI ルール + +- 1 ファイル内で複数の UI パターンを混在させて OK +- 電話番号は必ず `` でラップ +- URL は必ず `` でラップ。Google Docs リダイレクト URL(`https://www.google.com/url?q=...`)は q パラメータの実 URL を抽出 +- 画像は `` でライトボックス対応 +- 横スワイプ UI には下にドットインジケータ必須 +- スマホ縦持ち(380px 幅)を最優先 +- 本文は最小 15px、補助情報(small、label、キャプション)は 12px 以上で可 +- 画像: `{{ファイル名}}` プレースホルダーで参照、後処理で base64 置換 +- 除外: 目次ページ(ページ番号羅列)、注釈 [a][b][c] 等 +- カード枠線はゴールド系(例: #C9A227)。警告色 #F3AE56 とは別物として扱う +- スクロールスナップは横スワイプ card-gallery 内除き使わない +- min-height: 100svh は表紙含めて使わない +- 表紙は控えめに(白背景の小ヘッダーカード) +- 章末ナビ(「↑ 目次」「次: ○○ →」)は不要(FAB オーバーレイで代替) + +# 出力 + +```html + +... + +``` + +の 1 ファイル完結 HTML を返す。コードブロック開始の "```html" と終了の "```" で必ず囲む。 +```` + +## USER_PROMPT テンプレート + +```` +以下は Google Document からエクスポートされたマニュアルの HTML を pandoc で +Markdown 化したものです。この内容を読み取り、解説 HTML を生成してください。 + +【最重要・繰り返し】 +元 Markdown のテキストは一字一句変えてはいけません。タイポも直しません。 +形式・レイアウト・装飾だけを整形します。 +出力に対して機械的な文字列比較検証が走ります。 + +画像ファイルは以下が利用可能です: +{image_list} + +元 Markdown: +{html_content} +```` diff --git a/.claude/manual-prompt-card.md b/.claude/manual-prompt-card.md new file mode 100644 index 00000000..6ec2997b --- /dev/null +++ b/.claude/manual-prompt-card.md @@ -0,0 +1,304 @@ + + + +```` +あなたは技大祭の解説マニュアルを生成するAIです。 + +このプロンプトは `.claude/manual-prompt.md` の **拡張+一部上書き** であり、 +置き換えではない。manual-prompt.md の全要件を継承した上で、 +カード形式特有の修正・追加要件を以下に定める。 + +## オーバーライド(manual-prompt.md からの一部上書き) + +### スクロールスナップは使わない +- ❌ `html { scroll-snap-type: y proximity }` は適用しない +- ❌ 各セクション `min-height: 100svh; scroll-snap-align: start` は適用しない(**表紙含めて全部**) +- 理由: 短いセクションで余白が過剰、スマホで意図しない吸い付き挙動が出る +- ✅ 各セクションは content fit。`html { scroll-behavior: smooth }` のみでアンカージャンプの滑らかさを確保 +- ✅ `body { padding-bottom: 5rem }` で FAB との衝突を避ける + +### 表紙は控えめに +- 旧版の「グラデーション全画面 + 白文字 + h1 30px」のような強調は不要 +- 白背景の小さなヘッダーカード程度に留める(白背景、ゴールド 1px 枠、padding 0.85rem 1.1rem 程度) +- h1: 17-18px、teal 文字、左寄せ +- 解説マニュアルは技大祭運営マニュアル群の中の1項目という位置付け +- 内容: マニュアル名 + サブタイトル + 担当者名(あれば) + +### カンバン grid の minmax +- manual-prompt.md は `minmax(160px, 1fr)` だが、5要素入りカードでは窮屈 +- カード形式では `minmax(220px, 1fr)` を推奨 + +## 継承する manual-prompt.md の要件(修正後) + +- **カラースキーム** (SeeFT Design System): メインアクセント #009688、リンク #1264A3、警告 #F3AE56、サブ #F4FBF8 / #CCE8E2、グレー #D9D9D9 +- **目次**: `id="toc"` の目次セクションを `` 直後(表紙の次)に置き、各章に `id="section-N"` を付与してアンカーリンクで飛べるようにする +- **章ごとの折りたたみトグル**: 各章のコンテンツ部分を `
` で囲み、`` に章タイトルを置く +- **画像のライトボックス**: 純粋 CSS+最小限の JS で、`` の画像タップ時に全画面拡大表示 +- **カード型レイアウト**: 全ての情報ブロックを**白背景・角丸・影・ゴールド系の枠線(例: #C9A227)**のカードで囲む +- **緊急連絡先セクション**: 最後に配置、電話番号は `` でラップ +- **設営/運営/片付けの切り分け**: 該当する場合、独立したセクション群として切り分け +- **画像**: `{{ファイル名}}` プレースホルダーで参照、後処理で base64 置換 +- **除外**: 目次ページ(ページ番号羅列)、注釈 [a][b][c] 等 +- **スマホ最適化**: `clamp()` で本文最小15px、padding は vw 単位、補助情報は12px以上で可 + +## カンバンの強化指示(最重要) + +役割分担・チーム分け・担当区域分けのカンバンカードは、**1枚で1単位の作業に必要な情報がすべて完結する**ように設計する。 +「Aチームの担当者は、Aチームのカードだけ見れば動ける」状態がゴール。 +**名前だけのカード(識別子+担当者名のみ)はカード化の意味が薄いので作らない**。 + +カード1枚に含めるべき情報の例: +- 識別子(チームレター、役割名、バッジ) +- 担当範囲(配線エリア、配線箇所、担当区域) +- 必要物品(数量・置き場所) +- 担当者(指揮者、サブリーダー) +- 関連資料(PDFリンク、参照図) +- 場合によりその区域専用の図(例: 雨天時の屋内配線図) + +CSS: `display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1em;` + +## ナビゲーション機能(必須) + +情報アクセスの速度を最大化するため、以下4種のナビ機能を必ず実装する。 + +### TOC はボタンカード形式 +通常のリンクテキストでは見た目で「クリック可能」が伝わらない(特にスマホ)。 +各エントリは以下を含むボタンカードとして表示: +- **番号バッジ**(CSS `counter()` で自動付与、teal 円形、白数字) +- **ラベル**(teal 文字、太字) +- **右矢印**(›、teal、`margin-left: auto`) +- 通常: 白背景、teal-medium 1px 枠線 +- ホバー: teal-light 背景、teal 枠 +- タップ: teal 反転 + `transform: scale(0.98)` でフィードバック + +### フローティング TOC ボタン (FAB) + 目次オーバーレイ +画面右下に常時表示の固定ボタンを置き、タップで **目次がその場でオーバーレイ展開** する方式。スクロール位置を保ったまま目次を確認・選択できる。 + +**FAB 自体:** +- `position: fixed; bottom: 1.25rem; right: 1.25rem`、円形 52x52、teal 背景、白文字、ゴールド 2px 枠 +- ラベル「≡」、` + + + + + + +``` + +**オーバーレイの CSS スケルトン:** + +```css +.toc-overlay { + position: fixed; inset: 0; + background: rgba(0,0,0,0.5); + display: none; + align-items: flex-start; justify-content: center; + z-index: 99; + padding: 2rem 1rem; + overflow-y: auto; +} +.toc-overlay.show { display: flex; } +.toc-panel { + background: #fff; + border: 2px solid var(--gold); + border-radius: 12px; + padding: 1.25rem 1.5rem; + max-width: 480px; width: 100%; + position: relative; + max-height: calc(100vh - 4rem); + overflow-y: auto; +} +.toc-panel h3 { + margin: 0 0 0.75rem; + color: var(--teal); + font-size: 18px; + border-bottom: 2px solid var(--teal); + padding-bottom: 0.4rem; +} +.toc-close { + position: absolute; + top: 0.5rem; right: 0.6rem; + background: var(--gray-light); + border: 1px solid var(--gray); + border-radius: 50%; + width: 32px; height: 32px; + font-size: 18px; + cursor: pointer; + color: var(--text-secondary); +} +``` + +### サブ目次(pillナビ) +複数ステップを含む長い章(配線手順等)では、章冒頭に pill 形式のサブ目次: +- 各ステップに `id="proc-N"` 等のアンカーを付与 +- サブ目次は flex + flex-wrap で pill を並べる +- 各 pill: 白背景、teal 文字、teal-medium 1px 枠、padding 4px 10px、border-radius 12px +- 章タイトル + サブ目次 + ステップ本文の3層で情報粒度を上げる + +## 情報UIパターン一覧(章内の情報塊に適用) + +各章の中の情報塊は、情報の性質に応じて以下のパターンから最適なものを選ぶ。複数パターンを同一章内で混在させてよい。 + +### card-gallery-horizontal(横スワイプカード) +- いつ使う: カンバン形式(grid)が物理的に入りきらないとき、または画像/図のシリーズ +- 実装: flex + overflow-x: auto + scroll-snap-type: x mandatory(**横方向のみ snap OK**) +- カード幅: 280〜320px固定、下にドットインジケータ必須 + +### step-sequence(縦ステップ) +- いつ使う: 順序が重要な作業手順 +- 実装: 番号付きカード縦並び、各ステップに見出し+説明+画像 +- 各ステップに `id="proc-N"` 等のアンカーを付与し、章冒頭サブ目次から飛べるように + +### table-with-toggle(折りたたみテーブル) +- いつ使う: 密度の高い表データ、要素数が多い、検索性が必要 +- 実装: details要素 + table、カテゴリごとに折りたためる + +### contact-list(アクション可能カード) +- いつ使う: 電話番号やURLを含む、即アクションが要る情報 +- 実装: 各連絡先を独立カード、電話番号は で発信可能 + +### timeline-vertical(縦タイムライン) +- いつ使う: 時系列で並ぶイベント +- 実装: 縦線 + 時刻ラベル + イベント説明 + +### prose-with-cards(散文+補足カード) +- いつ使う: 説明文がメインで、補足情報をハイライトしたい +- 実装: 通常の段落 + 重要情報を枠付きカードで強調 + +### photo-with-caption(写真+キャプション) +- いつ使う: 視覚情報が主体(地図、完成図、配置図) +- 実装: 画像 + ライトボックス + キャプション + +## 選択ルール + +各セクションの中の情報塊に対して、以下の順で判断する: + +1. 役割分担・チーム分け・担当区域分けがあるか? → **カンバン形式(grid)**。1枚に全情報を入れる +2. 電話番号や tel リンクが含まれているか? → contact-list(緊急連絡先は最後) +3. 「手順」「ステップ」「順序」を示す番号付きリストか? → step-sequence(各ステップに id 付与) +4. 時刻が並んでいるか? → timeline-vertical +5. 表形式のデータで行数が10以上か? → table-with-toggle +6. 主要素が画像(マップ、写真)か? → photo-with-caption(または画像ギャラリー) +7. 上記以外 → prose-with-cards + +## 厳守事項 + +### 元 Markdown 厳守ルール(最重要) + +#### 追加禁止 +**元 Markdown にない情報は絶対に追加しない**。具体的に禁止する例: +- ❌ "○○カードだけ見れば動ける構成"(UI 設計の自己言及) +- ❌ "タップで発信"(UI 操作のヒント) +- ❌ "持ち出し時のチェック用"(セクションの目的説明) +- ❌ "当日参照用"(使用シーンの説明) +- ❌ 章タイトルから自明な lead 文(例: 配線手順章で「プラグの差し込みからケーブル固定、特殊箇所まで」) +- ❌ "○○の集合情報です" のような meta 紹介文 +- その他、生成者の視点でユーザーに「親切な説明」を入れたくなる衝動全般 + +判定基準: 元 Markdown を grep して同じ文言・近い言い回しがなければ、それは追加。追加しない。 + +#### 省略禁止 +**元 Markdown にあるコンテンツは絶対に省略しない**: +- 短い指示文(例: 「この資料を参考に業務にあたること」)も全て残す +- 元の見出し階層(# / ## / ###)を1対1で対応させる +- 「該当シフト」「業務内容」のような見出しがあれば独立セクションとして残す +- 表の行・カラムを勝手に削らない + +判定基準: 元 Markdown のすべての見出し・段落・リスト項目・テーブル行が、出力 HTML のどこかに対応する。 + +#### 略称禁止 +**固有名詞・略称は元 Markdown 通り**: +- ❌ "BTタップ"("ブレーカータップ" を勝手に略す) +- ❌ "BT"("ブレーカー" を勝手に略す) +- ❌ "PC" / "DB" 等の英略(元 Markdown が日本語フルネームなら日本語のまま) +- ✅ 元 Markdown の表記をそのまま使用 +- テーブル等のスペース問題は CSS(font-size 12-13px、padding 圧縮、`white-space` 制御)で解決すること +- 略す必要があると感じたら、それは CSS で解決すべき問題 + +### UI ルール + +- 1ファイル内で複数のUIパターンを混在させてOK(推奨) +- 電話番号は必ず `` でラップ +- URL は必ず `` でラップ。Google Docs リダイレクト URL(`https://www.google.com/url?q=...`)は q パラメータの実 URL を抽出 +- 画像は `` でライトボックス対応 +- 横スワイプUIには下にドットインジケータ必須 +- スマホ縦持ち(380px幅)を最優先 +- 本文は最小15px、補助情報(small、label、キャプション)は12px以上で可 +- カード枠線はゴールド系(例: #C9A227)。警告色 #F3AE56 とは別物として扱う +- カンバンカードは「名前だけ」を避け、1枚で1単位の作業情報を完結させる +- スクロールスナップ(`scroll-snap-type` / `scroll-snap-align`)は**使わない**(横スワイプ card-gallery 内除く) +- `min-height: 100svh` は表紙含めて**使わない** +- 表紙は控えめに(白背景の小ヘッダーカード) +- TOC エントリはボタンカード形式(番号バッジ + 右矢印) +- フローティング TOC ボタン (FAB) を画面右下に常時表示、タップで目次オーバーレイを展開(飛ばさない) +- 複数ステップを含む章は、章冒頭にサブ目次(pillナビ)を置き、各ステップに id を付与 +- 章末ナビ(「↑ 目次」「次: ○○ →」リンク)は**不要**(FAB オーバーレイで代替できるため) +```` + +## 完了条件 + +- [ ] `SeeFT/.claude/manual-prompt-card.md` が作成されている +- [ ] `generate_manual_slide.py` に `--prompt` 引数が追加され、新旧切替できる +- [ ] 既存の使い方(引数なし)で生成すると、これまでと同じ出力になる +- [ ] `compare_manual_versions.sh` で同じ入力から両方を生成できる +- [ ] 既存の `manual-prompt.md` は変更されていない + +## USER_PROMPT テンプレート + +```` +以下はGoogle Documentからエクスポートされたマニュアルの HTML です。 +この内容を読み取り、解説スライドHTMLを生成してください。 + +画像ファイルは以下が利用可能です: +{image_list} + +元HTML: +{html_content} +```` diff --git a/.claude/manual-prompt.md b/.claude/manual-prompt.md new file mode 100644 index 00000000..93c25e37 --- /dev/null +++ b/.claude/manual-prompt.md @@ -0,0 +1,124 @@ +# マニュアル解説スライド生成プロンプト + +## 設計思想 + +Layer 1: ハードルール(機械的・再現性100%) +- カラースキーム、CSS変数、フォント +- 除外ルール(注釈[a][b][c]) +- 出力形式(自己完結HTML、Base64画像、scroll-snap) +- 1セクション = 1画面(min-height: 100svh) + +Layer 2: ソフトガイドライン(判断の方向性だけ示す) +- 「元の文書を読んで、読み手が当日迷わないように情報を再構成して」 +- 「テーブルが大きすぎる場合はカテゴリに分割して」 +- 「画像は関連する説明の直後に配置して」 +- 「1画面の情報密度が薄すぎず、はみ出しもしないように」 + +SKILL.mdが失敗したのは、Layer 2をLayer 1のように書いてしまったから。 +「HH:MMがあればタイムライン」「表があればパターンE」みたいな機械的分類にした結果、 +内容を理解せずに形式だけ変換してしまった。 + +「何を守るか」と「何を任せるか」の境界が明確なプロンプトが、再現性と品質を両立できるラインだと思う。 + +--- + +## SYSTEM_PROMPT(実際にAPIに渡しているもの) + +``` +あなたはマニュアルデザイナーです。大学の学園祭の運営マニュアルを、当日スマホで見る実行委員向けの解説スライドHTMLに再構成します。 + +## 出力形式(厳守) + +- 自己完結したHTML( から まで)のみを出力。説明文やコードブロック記法は不要。 +- 画像は `{{ファイル名}}` のプレースホルダーで参照する(例: ``)。後処理でBase64 data URIに置換される。 +- CSSはHTML内の + + +
+

{cover_title}

+ {cover_meta} +
+ + + +{sections_html} + + +
+
+ +

目次

+
    + {toc_items_overlay} +
+
+
+ + + + + + +""" + + +def render_full_document(cover: dict, sections: list) -> str: + cover_meta = "" + if cover["meta"]: + cover_meta = '
' + "\n".join(cover["meta"]) + "
" + + toc_items = "\n".join( + f'
  • {html.escape(s["title"])}
  • ' + for s in sections + ) + toc_items_overlay = "\n".join( + f'
  • {html.escape(s["title"])}
  • ' + for s in sections + ) + sections_html = "\n".join( + f'
    \n' + f' {s["title_html"]}\n' + f'
    {s["html"]}
    \n' + f"
    " + for s in sections + ) + return TEMPLATE.format( + title=html.escape(cover["title"] or "マニュアル"), + cover_title=html.escape(cover["title"] or "マニュアル"), + cover_meta=cover_meta, + toc_items=toc_items, + toc_items_overlay=toc_items_overlay, + sections_html=sections_html, + ) + + +# --------------------------------------------------------------------------- +# main +# --------------------------------------------------------------------------- + +def main() -> int: + parser = argparse.ArgumentParser( + description="決定的 Google Docs HTML → SeeFT card HTML 変換器 (AI なし)", + ) + parser.add_argument("manual_dir", help="マニュアルディレクトリ") + parser.add_argument( + "--output-name", default=OUTPUT_NAME, + help=f"出力ファイル名 (default: {OUTPUT_NAME})", + ) + args = parser.parse_args() + + manual_dir = os.path.abspath(args.manual_dir) + output_path = os.path.join(manual_dir, args.output_name) + + print(f"=== 決定的 SeeFT HTML 生成 (AI なし) ===") + print(f" Source: {manual_dir}") + print(f" Output: {output_path}") + + src_html = find_source_html(manual_dir) + print(f" Source HTML: {src_html}") + + ast = load_ast(src_html) + print(f" AST blocks: {len(ast['blocks'])}") + + flat = flatten_blocks(ast["blocks"]) + print(f" Flattened blocks: {len(flat)}") + + images = load_images_base64(manual_dir) + print(f" Images: {len(images)} files") + + cover, sections = split_into_sections(flat, images) + print(f" Cover title: {cover['title']!r}") + print(f" Sections: {len(sections)}") + for s in sections: + print(f" - {s['id']}: {s['title']}") + + output_html = render_full_document(cover, sections) + with open(output_path, "w", encoding="utf-8") as f: + f.write(output_html) + + print(f" Output: {output_path} ({os.path.getsize(output_path)//1024}KB)") + print("=== 完了 ===") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/deterministic-slide/pyproject.toml b/scripts/deterministic-slide/pyproject.toml new file mode 100644 index 00000000..cde03e4d --- /dev/null +++ b/scripts/deterministic-slide/pyproject.toml @@ -0,0 +1,6 @@ +[project] +name = "deterministic-slide" +version = "0.1.0" +description = "Deterministic Google Docs HTML → SeeFT card HTML converter via pandoc -t json AST. No generative AI." +requires-python = ">=3.10" +dependencies = [] diff --git a/scripts/deterministic-slide/uv.lock b/scripts/deterministic-slide/uv.lock new file mode 100644 index 00000000..f0b0f454 --- /dev/null +++ b/scripts/deterministic-slide/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" + +[[package]] +name = "deterministic-slide" +version = "0.1.0" +source = { virtual = "." } From c3af26608b570b6d5fe576b7b072596df7269500 Mon Sep 17 00:00:00 2001 From: taminororo <169162271+taminororo@users.noreply.github.com> Date: Sun, 31 May 2026 11:09:40 +0900 Subject: [PATCH 09/14] =?UTF-8?q?docs:=20=E3=83=91=E3=82=A4=E3=83=97?= =?UTF-8?q?=E3=83=A9=E3=82=A4=E3=83=B3=E6=8A=80=E8=A1=93=E3=83=AA=E3=83=95?= =?UTF-8?q?=E3=82=A1=E3=83=AC=E3=83=B3=E3=82=B9=E3=83=BBAgent=20SDK?= =?UTF-8?q?=E9=81=8B=E7=94=A8=E3=83=BB=E5=AE=9F=E8=A3=85=E8=A8=AD=E8=A8=88?= =?UTF-8?q?=E3=83=BB=E6=A4=9C=E8=A8=BC=E6=94=B9=E5=96=84=E3=81=BE=E3=81=A8?= =?UTF-8?q?=E3=82=81=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/proposals/agent-sdk-usage.md | 258 +++++++++++++ .../automation-design.md | 310 +++++++++++++++ docs/proposals/manual-slide-pipeline.md | 181 +++++++++ .../verify-mechanical-improvements.html | 354 ++++++++++++++++++ 4 files changed, 1103 insertions(+) create mode 100644 docs/proposals/agent-sdk-usage.md create mode 100644 docs/proposals/manual-proposal-v4-slides/automation-design.md create mode 100644 docs/proposals/manual-slide-pipeline.md create mode 100644 docs/proposals/verify-mechanical-improvements.html diff --git a/docs/proposals/agent-sdk-usage.md b/docs/proposals/agent-sdk-usage.md new file mode 100644 index 00000000..0cefa8d7 --- /dev/null +++ b/docs/proposals/agent-sdk-usage.md @@ -0,0 +1,258 @@ +# Claude Agent SDK の使い方(このプロジェクトでの運用パターン) + +ステータス: 運用中 +最終更新: 2026-05-21 +担当: 上林(PM) +対象読者: 新 PM 引き継ぎ、`scripts/claude-slide/` 系の修正・拡張をする人 +位置付け: `scripts/claude-slide/generate_slide.py` と `scripts/claude-slide/verify_slide.py` が裏で使っている Claude Agent SDK の使い方リファレンス。新規実装ではなく、既存コードを読む・直す・拡張するための知識 + +## 1. Anthropic SDK との違い + +``` +Anthropic SDK (anthropic パッケージ) + └─ Claude モデルを API キーで叩く。普通の SDK。Anthropic にトークン課金 + +Claude Agent SDK (claude_agent_sdk パッケージ) + └─ Claude Code CLI のサブスク認証を流用する SDK + ・OAuth トークンで認証 (API キー不要) + ・「Claude Code が裏で持っている対話セッション」を + スクリプトから呼べるイメージ + ・Agent としての構造化 (system_prompt, tools, max_turns) を持つ +``` + +このプロジェクトで Agent SDK を選んでいる理由は、`project/manual_slide_pipeline.md` で確定した「Max プラン共用」運用と整合するため。API キー方式だと API 課金が発生するが、Agent SDK は `claude login` 済の Max プラン容量を消費する。 + +代償として、**Claude Code CLI がローカルでバックエンドとして必要**。Mac の引き継ぎで `claude login` をやり直す手間や、CI からは動かしにくい等の運用制約が付く。 + +## 2. インポートする 5 つのもの + +Agent SDK は API 表面積が小さく作られていて、ほぼこれだけ覚えれば良い: + +```python +from claude_agent_sdk import ( + AssistantMessage, # アシスタント (Claude) からのメッセージ + ClaudeAgentOptions, # 呼び出しオプション + ResultMessage, # 終了時の集計メッセージ (usage, duration 等) + TextBlock, # メッセージ内のテキストブロック + query, # 唯一のエントリポイント関数 (async generator) +) +``` + +## 3. 基本パターン (generate_slide.py から抜粋) + +### 3-1. オプション組み立て + +```python +DISALLOWED_TOOLS = [ + "Read", "Write", "Edit", "Bash", + "Task", "WebFetch", "WebSearch", + "Grep", "Glob", "TodoWrite", + "NotebookEdit", +] + +options = ClaudeAgentOptions( + system_prompt=system_prompt, # .md ファイルから読んだプロンプト + max_turns=20, # 内部ループの上限 + disallowed_tools=DISALLOWED_TOOLS, + model="claude-opus-4-7", # 省略可。省略すると Claude Code デフォルト +) +``` + +#### `disallowed_tools` で「テキストだけ吐かせる」のがコツ + +Claude Code は本来「Read/Write/Bash 等のツールを使ってコードを書く」エージェント。だが本スクリプトでは「マニュアル HTML を 1 個返してくれればいい」のでツール不要。全ツール禁止することで「ツール試行で容量を浪費する」のを防いでいる。 + +#### `max_turns` の落とし穴 + +Claude Code は内部で「ツールを使って試行錯誤」する設計。テキスト返答 1 回でも、内部で複数 turn (思考 → ツール試行 → 結果見て次の手) を経ることがある。disallowed_tools で全部ブロックしてても、Claude が「Read を使おうとして拒否される」を何回も繰り返すと turn を消費する。 + +generate_slide.py には経験則として以下のコメントがある: + +```python +# max_turns を 20 に: 大きい入力(55KB+ markdown)で Claude が tool 試行する場合に +# disallowed_tools 拒否で turn が消費されるため、余裕大きめ。 +# お化け屋敷で max_turns=10 では flaky に失敗する事象を確認したため。 +``` + +つまり `max_turns` は **「思考の上限」ではなく「ツール試行も含む内部ループの上限」**。大きい入力で flaky に失敗するようなら、まず `max_turns` を上げる。 + +### 3-2. query() を async で叩いて結果を吸う + +```python +result_text = "" +usage: dict = {} + +async for message in query(prompt=user_text, options=options): + if isinstance(message, AssistantMessage): + for block in message.content: + if isinstance(block, TextBlock): + result_text += block.text + elif isinstance(message, ResultMessage): + usage = { + "duration_ms": getattr(message, "duration_ms", None), + "num_turns": getattr(message, "num_turns", None), + "total_cost_usd": getattr(message, "total_cost_usd", None), + "is_error": getattr(message, "is_error", None), + } + +return result_text, usage +``` + +ポイント: + +- `query()` は **async generator**。`await` で 1 個ずつ取得ではなく、`async for` でストリーミング取得 +- 1 回のリクエストで複数 message が流れてくる: + - `AssistantMessage` → Claude のテキスト出力 (1 回または複数回) + - `ResultMessage` → 最後に必ず 1 回、集計情報 (duration, num_turns, cost) +- `message.content` は **ブロックのリスト**。`TextBlock` 以外 (将来的に思考ブロックなど) が混入する可能性があるので、明示的に `isinstance(block, TextBlock)` で絞る +- `result_text += block.text` と**累積加算**しているのは、1 回の応答が長文だと SDK が部分的に小分けして送ってくることがあるため + +呼び出し側 (main) では: + +```python +import anyio + +response_text, usage = anyio.run( + call_claude_sdk, + system_prompt, user_prompt_template, md_content, image_files, args.model, +) +``` + +`anyio.run()` で async 関数を同期的に起動。`asyncio.run` でも同じことができるが、`anyio` を使ってるのは Agent SDK が anyio ベースだから合わせている。 + +### 3-3. ResultMessage の使いどころ + +`total_cost_usd` は Anthropic API 換算の推定値。Max プラン経由でも、参考値として API キー使用時のコスト相当が出る。**「Max プラン共用で API 課金相当をどれくらい節約できているか」を測れる**。 + +`is_error` フラグは現状コードで参照されていない。本番運用に乗せる前に下記を足したい: + +```python +if usage.get("is_error"): + raise RuntimeError(f"Agent SDK error: {usage}") +``` + +## 4. プロンプトを `.md` ファイルから読む + +プロンプトは Python 文字列リテラルではなく `.claude/manual-prompt-card.md` のような Markdown ファイルから読む。`_load_prompt_from_md()` がその実装: + +```python +def _load_prompt_from_md(path: str) -> tuple[str, str]: + with open(path, "r", encoding="utf-8") as f: + content = f.read() + # Prefer 4-backtick outer fences (allows nested 3-backtick blocks inside). + blocks = re.findall(r"````\s*\n(.*?)\n````", content, re.DOTALL) + if len(blocks) < 2: + blocks = re.findall(r"```\n(.*?)```", content, re.DOTALL) + if len(blocks) < 2: + raise ValueError(f"Expected 2 code blocks in {path}, found {len(blocks)}") + return blocks[0].strip(), blocks[1].strip() +``` + +- 1 つ目の 4-backtick ブロック → system_prompt +- 2 つ目の 4-backtick ブロック → user_prompt_template +- 4-backtick で外側を囲うのは、中に普通のコードブロック (3-backtick) を書きたいから + +### なぜこのパターンが良いか + +- プロンプトを git で diff 可能に管理できる +- Markdown のシンタックスハイライトが効くのでエディタの編集体験が良い +- プロンプトには `## オーバーライド` `## 厳守事項` 等の**解説セクション**を書け、コードブロック内だけが実際に AI に渡る +- 「ドキュメントとしてのプロンプト」と「実行されるプロンプト」を同じファイルで管理できる + +### プロンプトファイルの実例 + +- `.claude/manual-prompt.md` — デフォルト (スライド形式) +- `.claude/manual-prompt-card.md` — カード形式 +- `.claude/manual-prompt-card-strict.md` — 文章不変ポリシー版 +- `.claude/manual-verify-prompt.md` — 検証用 + +`generate_slide.py` の `PROMPT_VARIANTS` 辞書で対応関係を管理している。新しいプロンプトを追加するには: + +1. `.claude/.md` を作る (4-backtick ブロック 2 つを含む) +2. `PROMPT_VARIANTS` に `"": ".md"` を追加 +3. `--prompt ` で呼び出せるようになる + +## 5. レスポンスから HTML を取り出す + +Claude が `` ```html ... ``` `` で囲んで返してくる前提のパーサ: + +```python +def extract_html(response: str) -> str: + match = re.search(r"```html\s*\n(.*?)```", response, re.DOTALL) + if match: + return match.group(1).strip() + match = re.search(r"(.*?)", response, re.DOTALL | re.IGNORECASE) + if match: + return match.group(1).strip() + return response.strip() +``` + +fallback として `...` 直接抽出も。AI 応答のフォーマットを 100% 信用しない健全な防衛コード。 + +## 6. 認証 (コードに出てこないが必須) + +Agent SDK が動くための前提条件: + +```bash +claude login +# ブラウザが開く → Anthropic アカウントでログイン +# Mac のキーチェーン or ~/.claude/ 配下に OAuth トークン保存 +# 以降、claude_agent_sdk.query() は自動でそのトークンを読む +``` + +運用ルール (`project/manual_slide_pipeline.md` から): + +- 1 Mac = 1 アカウント運用、同時複数 Mac 使用を避ける +- ベトナム期間中は他デバイスでログアウト +- 引き継ぎ時に新 PM の Mac で 1 回だけ `claude login` +- 月 1 で Anthropic 利用状況を確認 + +コード側は何もしない。シェル環境 (= claude CLI の状態) を信頼するモデル。 + +## 7. このパターンの強みと弱み + +### 強み + +- **API キー課金ゼロ** (Max プラン容量で吸収) +- **API 表面積が小さく覚えやすい** (`query` と 4 つの型だけ) +- **プロンプトを `.md` で管理** → git diff・PR レビューが効く +- **disallowed_tools パターン** → Claude Code の Agent 特性を逆手に取って「テキスト返答に専念させる」 +- **生成と検証で同じ構造** → 学習コストが運用全体で 1 つで済む + +### 弱み + +- **Mac が必要**: Claude Code CLI が動くマシン依存。CI や VPS では動かしにくい +- **`max_turns` チューニング**: ツール拒否で turn を消費する特性が直感に反する。たまに `flaky` 失敗が出る +- **`is_error` 未活用**: 現コードはエラー時の挙動が緩い。本番化前に強化したい +- **Anthropic TOS グレー領域**: 個人サブスクをチームで共用する運用は厳密には「単一ユーザー前提」と擦れる (詳細: `manual_slide_pipeline.md`) + +## 8. 別バックエンド (API キー方式) への移行コスト + +万一サブスク共用が運用できなくなった場合、`scripts/generate_manual_slide.py` (Anthropic API キー版) に切り替える。移行で書き換える部分: + +| 部分 | Agent SDK 版 | API キー版 (移行後) | +| --- | --- | --- | +| インポート | `from claude_agent_sdk import ...` | `import anthropic` | +| クライアント生成 | (暗黙、`query()` が自動認証) | `client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])` | +| リクエスト送信 | `async for m in query(...)` | `client.messages.create(...)` | +| メッセージ解析 | `AssistantMessage` の `TextBlock` を集める | `response.content` の `TextBlock` を集める | +| ツール禁止 | `disallowed_tools=[...]` | tools 引数を渡さない (デフォルトでツールなし) | +| `max_turns` | あり | 概念なし。`max_tokens` で出力長を制御 | +| 認証 | `claude login` | 環境変数 `ANTHROPIC_API_KEY` | + +`call_claude_sdk()` 関数を `call_anthropic_api()` に書き換える形で、他のロジック (prompt 読み込み・画像 base64 化・HTML 抽出) は再利用可能。 + +## 9. 参考: コード上の出現箇所 + +- `scripts/claude-slide/generate_slide.py:1-322` — 生成本体、`call_claude_sdk()` がメイン +- `scripts/claude-slide/verify_slide.py:1-253` — 検証本体、ほぼ同じパターン +- `scripts/claude-slide/pyproject.toml` — 依存宣言 (`claude-agent-sdk>=0.1.80`, `anyio>=4.0`) +- `.claude/manual-prompt-card.md` — プロンプトの実例 (4-backtick パターン) +- `.claude/manual-verify-prompt.md` — 検証用プロンプトの実例 + +## 10. 関連ドキュメント + +- `docs/proposals/manual-proposal-v4-slides/automation-design.md` — 解説マニュアル生成パイプライン全体の実装設計 (本ドキュメントの親) +- `docs/proposals/manual-slide-pipeline.md` — 既存パイプラインの技術リファレンス +- `docs/proposals/manual-slide-pipeline-qa.md` — Q&A 形式の運用解説 +- `project/manual_slide_pipeline.md` (memory) — バックエンド方針 (Max 共用 / API キー / Sakura 不採用) diff --git a/docs/proposals/manual-proposal-v4-slides/automation-design.md b/docs/proposals/manual-proposal-v4-slides/automation-design.md new file mode 100644 index 00000000..bcc77cfb --- /dev/null +++ b/docs/proposals/manual-proposal-v4-slides/automation-design.md @@ -0,0 +1,310 @@ +# 解説マニュアル生成パイプライン — 自動化実装設計 + +ステータス: Phase 1.5 完了、Phase 2 実装中 +最終更新: 2026-05-13 +担当: 上林(PM) +位置付け: **実装者視点の単一正本**。新 PM への引き継ぎはまずこの MD を読む + +このドキュメントは「解説マニュアル生成パイプライン」の自動化部分について、**コードと同期して育てる**実装設計メモ。執行部視点 / インフラ視点 / 機能視点の他 MD(後述)に対して、本 MD は「**実装する人**」が手を動かす際の正本として機能する。 + +## 1. 全体ビジョン: 「スプシを軸」にする + +PM が触るのは Google Sheets だけ。コード(generate / verify / アップロード)は全てスプシのデータを読み書きする側に回る。 + +``` +[PM] + │ Google Sheets でステータス列を「再生成」に変える + ▼ +[watcher.py(ローカル常駐 or cron)] + │ Sheets API でステータス変更を検知 + ▼ +[process_one.py(1 本のマニュアル処理)] + │ ① スプシから行を読む(Doc URL / 個別生成指示 / 解説HTML生成 等) + │ ② Drive API で Doc を HTML として取得 + │ ③ pandoc → generate_slide.py で解説 HTML 生成 + │ ④ verify_slide.py で AI 検証 + │ ⑤ ホスティング先にアップロード(インフラ部門と決定後) + │ ⑥ Sheets API で結果を書き戻し + ▼ +[Google Sheets] + ステータス「検証済(OK/NG)」、生成HTML URL、件数、最終生成日時 が反映される +``` + +## 2. Phase の進捗 + +| Phase | 内容 | 状態 | 関連物 | +| --- | --- | --- | --- | +| **Phase 1** | スプシ可視化(手動運用、CSV で状態管理) | 完了 | `docs/spread_sheets/45th_マニュアル生成ステータス.csv` | +| **Phase 1.5** | 個別生成指示の手動同期(PM が CSV 経由でプロンプトに追加指示を流せる) | 完了 | `generate_slide.py` の CSV ルックアップ機能、19 列に拡張済み | +| **Phase 2** | スプシ軸の完全自動化(Sheets API + Drive API + watcher) | **実装中** | 本 MD の Step 1-6 を参照 | +| **Phase 3(任意)** | GAS onEdit + webhook で polling を webhook に置換 | 未着手 | qa.md TODO 参照 | + +## 3. 構成部品 + +### 既存(Phase 1.5 まで) + +| 部品 | 役割 | 場所 | +| --- | --- | --- | +| `generate_slide.py` | Claude Agent SDK + サブスク認証で解説 HTML 生成。CSV から個別生成指示も注入 | `scripts/claude-slide/generate_slide.py` | +| `verify_slide.py` | 元 Doc と生成 HTML の AI 検証、VERDICT: OK/NG + 件数を返す | `scripts/claude-slide/verify_slide.py` | +| `.claude/manual-prompt-card.md` | カード形式生成用の共通プロンプト | `.claude/manual-prompt-card.md` | +| `.claude/manual-verify-prompt.md` | AI 検証用プロンプト | `.claude/manual-verify-prompt.md` | +| ステータス CSV | 19 列の状態管理 | `docs/spread_sheets/45th_マニュアル生成ステータス.csv` | +| `build_status_csv.py` | xlsx + verify レポートから CSV を再構築するユーティリティ | `/tmp/build_status_csv.py`(将来 `scripts/` に昇格予定) | + +### 新規(Phase 2 で作る) + +| 部品 | 役割 | 工数目安 | 依存 | +| --- | --- | --- | --- | +| `scripts/automation/sheets_client.py` | Google Sheets API ラッパー、行の読み書き、enum 値の検証 | 2-3h | OAuth 認証必須 | +| `scripts/automation/drive_client.py` | Google Drive API で Doc を HTML エクスポート | 1-2h | OAuth 認証必須 | +| `scripts/automation/process_one.py` | マニュアル 1 本の生成 → 検証 → スプシ書き戻し(メインエンジン) | 1-2h | sheets_client + 既存 generate/verify | +| `scripts/automation/watcher.py` | スプシ polling でステータス変更検知、`process_one.py` を起動 | 1-2h | sheets_client | +| `scripts/automation/uploader.py` | 生成 HTML を配信先にアップロード(インターフェースだけ先に切る、実装はインフラ部門の決定後) | 30 分(スタブ)+ TBD(本実装) | ホスティング先依存 | + +### Phase 2 のディレクトリ構成(提案) + +``` +scripts/ +├── claude-slide/ # 既存 (Phase 1.5 まで) +│ ├── generate_slide.py +│ ├── verify_slide.py +│ ├── pyproject.toml # claude_agent_sdk dep +│ └── ... +└── automation/ # 新規 (Phase 2) + ├── sheets_client.py + ├── drive_client.py + ├── process_one.py + ├── watcher.py + ├── uploader.py + └── pyproject.toml # google-api-python-client 系 dep +``` + +`claude-slide` と `automation` を分けるのは依存が違うため(LLM 系 vs Google API 系)。お互いを subprocess で呼び合う / 共有モジュールを参照する形にする。 + +## 4. データフロー詳細 + +### 4-1. 起動からスプシ書き戻しまで + +``` +[watcher.py] スプシを 30-60 秒ごとに polling + ↓ 「再生成」または「未生成」のステータスを検出 +[process_one.py manual_name] + ↓ +sheets_client.read_row(manual_name) + → 行データを取得 (マニュアル名 / Doc URL / 解説HTML生成 / 個別生成指示 等) + ↓ +[分岐 1] 解説HTML生成 == "生成しない": + ├ Doc URL をそのまま manual_url としてマーク (このサイクル終了) + └ パイプライン状態 = "Doc 直配信" にしてスプシに書き戻し + +[分岐 2] 解説HTML生成 == "生成する": + ├ drive_client.export_doc_as_html(doc_url, dest=docs/manuals/{name}/) + │ → docs/manuals/{name}/source.html を作成 + ├ pandoc で source.html → markdown 化(既存ロジック) + ├ generate_slide.py を呼ぶ + │ → docs/manuals/{name}/slide_claude.card.html を生成 + ├ verify_slide.py を呼ぶ + │ → docs/manuals/{name}/verify_claude.card.txt を保存 + │ → VERDICT (OK/NG) と件数 (追加/欠落/改変) を取得 + ├ uploader.upload(slide_html, name) → URL を取得 (Phase 2 後半) + └ sheets_client.write_row(manual_name, { + "生成HTML URL": url, + "パイプライン状態": "検証済(OK)" or "検証済(NG)", + "最終生成日時": now, + "検証VERDICT": "OK" or "NG", + "追加件数": N, "欠落件数": M, "改変件数": K, + "検証レポート": "docs/manuals/{name}/verify_claude.card.txt", + "確認結果": "未確認" if NG else "", + }) +``` + +### 4-2. SeeFT モバイル側との接続 + +`mobile/lib/widgets/manual_viewer.dart:8-27` の既存ロジックがそのまま動く: + +- `manual_url` が `docs.google.com` → Doc プレビュー iframe(PR #258) +- `manual_url` が `.pdf` → pdf.js viewer +- `manual_url` がその他 → 普通の iframe(生成 HTML はこの分岐) + +→ **モバイル側は無改修**。詳細は `selective-html-generation.md` 参照。 + +## 5. 認証 (OAuth セットアップ) + +### 5-1. 必要なもの + +- Google Cloud Console プロジェクト(PM の Google アカウントで作る) +- 有効化する API: **Google Sheets API**、**Google Drive API** +- OAuth 同意画面: User Type = 外部、テストユーザーに PM の Gmail を追加(未公開のままで OK) +- OAuth クライアント ID: アプリケーションの種類 = **デスクトップアプリ** +- ダウンロードした `credentials.json` + +### 5-2. ファイル配置 + +``` +~/.config/seeft-pipeline/ +├── credentials.json # GCP からダウンロード、リポジトリには絶対に置かない +└── token.json # 初回スクリプト実行時に自動生成、以降は再利用 +``` + +リポジトリ外(`~/.config/`)に置く理由: + +- git の事故防止(誤コミットで認証情報が公開される事態を避ける) +- 新 PM 引き継ぎ時に「このディレクトリをコピーすれば動く」状態にできる +- メモリ `project/ignore_convention.md` の「ブランチ依存生成物は `.git/info/exclude` に寄せる」原則を、認証情報には拡張適用 + +### 5-3. 初回起動フロー + +``` +1. PM が任意の automation/* スクリプトを初回実行 +2. credentials.json を読む +3. ブラウザが自動で開く → Google アカウントにログイン → スコープ同意 +4. token.json が自動生成・保存される +5. 以降は token.json を使い回し(有効期限切れ時は自動リフレッシュ) +``` + +### 5-4. スコープ + +- `https://www.googleapis.com/auth/spreadsheets` — Sheets 読み書き +- `https://www.googleapis.com/auth/drive.readonly` — Doc 取得(読み取りのみで十分) + +書き込みスコープを Drive に与えないことで、PM の Drive 内ファイルを誤って改変するリスクをゼロにする。 + +## 6. ステータス CSV スキーマ(19 列) + +| # | カラム名 | 型 | 記入主体 | enum 値 / 例 | +| --- | --- | --- | --- | --- | +| 1 | マニュアル名 | str | PM | "配線マニュアル" | +| 2 | 担当局 | str | PM | "総務" "渉外" "財務" "企画" | +| 3 | 担当部門 | str | PM | "会場" "副局長" "広報" 等 | +| 4 | 担当者名 | str | PM | "赤嶺" "黒木康士朗" | +| 5 | Google Doc URL | URL | PM | "https://docs.google.com/document/d/.../edit" | +| 6 | **解説HTML生成** | enum | PM | "生成する" / "生成しない" | +| 7 | 生成HTML URL | URL | automation | "https://manuals.../wiring.html" | +| 8 | パイプライン状態 | enum | automation | "未生成" "生成中" "検証中" "検証済(OK)" "検証済(NG)" "Doc 直配信" "エラー" | +| 9 | 最終生成日時 | datetime | automation | "2026-05-13 13:30" | +| 10 | 検証VERDICT | enum | automation | "OK" / "NG" / "" | +| 11 | 追加件数 | int | automation | 0, 1, 2, ... | +| 12 | 欠落件数 | int | automation | 0, 1, 2, ... | +| 13 | 改変件数 | int | automation | 0, 1, 2, ... | +| 14 | 検証レポート | path | automation | "docs/manuals/.../verify_claude.card.txt" | +| 15 | 確認担当者 | str | PM | "赤嶺" | +| 16 | 確認結果 | enum | 確認担当者 | "未確認" / "訂正OK" / "要修正" / "再生成依頼" | +| 17 | 確認備考 | str | 確認担当者 | フリーテキスト | +| 18 | 個別生成指示 | markdown | PM | "- 配線番号 ①〜㉖ はバッジで強調" | +| 19 | 備考 | str | 全員 | フリーテキスト | + +「PM」記入の列は人が触る、「automation」記入の列は自動化が書き戻す、と境界が明確。 + +### enum 値のプルダウン推奨設定(Google Sheets 側) + +| カラム | プルダウン値(リストを直接指定) | +| --- | --- | +| 解説HTML生成 | `生成する,生成しない` | +| パイプライン状態 | `未生成,生成中,検証中,検証済(OK),検証済(NG),Doc 直配信,エラー` | +| 検証VERDICT | `OK,NG` | +| 確認結果 | `未確認,訂正OK,要修正,再生成依頼` | + +## 7. 実装ステップ + +| Step | 内容 | 状態 | 主作業者 | +| --- | --- | --- | --- | +| 1 | 「解説HTML生成」列を CSV に追加(19 列に拡張) | 完了 | Claude | +| 2 | Google Cloud Console で OAuth セットアップ、credentials.json を `~/.config/seeft-pipeline/` に配置 | **進行中** | PM | +| 3 | `scripts/automation/sheets_client.py` 実装、認証フローと行読み書きをテスト | 未着手 | Claude | +| 4 | `scripts/automation/process_one.py` 実装、1 マニュアルで end-to-end 動作確認 | 未着手 | Claude | +| 5 | `scripts/automation/drive_client.py` 実装、Doc URL → HTML エクスポート | 未着手 | Claude | +| 6 | `scripts/automation/watcher.py` 実装、polling でステータス変更検知 | 未着手 | Claude | +| 並行 | `uploader.py` インターフェースだけ先に切る | 未着手 | Claude | +| 並行 | ホスティング先決定(→ 本実装) | 未着手 | PM × インフラ部門 | + +## 8. 依存パッケージ・ファイル配置 + +### Python 依存(`scripts/automation/pyproject.toml`) + +```toml +[project] +name = "seeft-automation" +version = "0.1.0" +requires-python = ">=3.11" +dependencies = [ + "google-api-python-client>=2.0", + "google-auth-httplib2>=0.2", + "google-auth-oauthlib>=1.2", +] +``` + +### システム依存 + +- pandoc (`brew install pandoc`) +- uv (`brew install uv`) +- Claude Code CLI (`claude login` 済み) + +### 認証ファイル + +- `~/.config/seeft-pipeline/credentials.json` — GCP からダウンロード +- `~/.config/seeft-pipeline/token.json` — 初回起動で自動生成 + +### スプシ ID + +- 環境変数 or 設定ファイル: `SEEFT_STATUS_SHEET_ID=1jz_870-Id89UYS-00F9ozZUNWL92IPntRqtNYYCRF0c` +- ハードコードせず外部から差し替え可能に + +## 9. 新 PM 引き継ぎ用クイックスタート + +新 PM が初日に動かすための最短手順: + +```bash +# 1. リポジトリを clone +git clone https://github.com/NUTFes/SeeFT.git +cd SeeFT + +# 2. 環境を作る +brew install pandoc uv +uv sync --project scripts/claude-slide +uv sync --project scripts/automation + +# 3. Claude にログイン +claude login + +# 4. 上林さんから OAuth 認証情報を受け取り、所定の場所に置く +mkdir -p ~/.config/seeft-pipeline +# credentials.json と token.json を ~/.config/seeft-pipeline/ にコピー + +# 5. 試運転 +uv run --project scripts/automation python scripts/automation/process_one.py 配線マニュアル + +# 6. 結果がスプシに反映されれば OK +``` + +## 10. 関連ドキュメント + +| MD | 視点 | 主な内容 | +| --- | --- | --- | +| `index.html` | 執行部 | 13 スライドの提案資料 | +| `infra-hosting-discussion.md` | インフラ部門 | ホスティング先決定のための相談材料 | +| `selective-html-generation.md` | 機能設計 | 「解説HTML生成」列と Doc 直表示の両立 | +| `45th_マニュアル可視化方針(仮).md` | 可視化方針 | 全体方針(参照) | +| `../manual-proposal-v4.md` | 提案本文 | 執行部 MT 向け Q1-Q6 形式 | +| `../manual-slide-pipeline.md` | 技術リファレンス | 既存パイプラインの解説 | +| `../manual-slide-pipeline-qa.md` | Q&A | 各種疑問への回答集、TODO 高レベル方針 | +| `../agent-sdk-usage.md` | 実装者 | `generate_slide.py` / `verify_slide.py` が裏で使う Claude Agent SDK の使い方リファレンス | + +## 11. コード上の連携先 + +- `scripts/claude-slide/generate_slide.py` — Phase 1.5 で CSV 読み込み機能を追加済み、`load_per_manual_instructions()` 関数 +- `scripts/claude-slide/verify_slide.py` — AI 検証本体 +- `mobile/lib/widgets/manual_viewer.dart:8-27` — URL パターン分岐、改修不要 +- `mobile/lib/widgets/shift_card.dart:165-171` — マニュアルボタン、改修不要 + +## 12. メモ・運用ルール + +- **「生成しない」のマニュアル**は Doc URL を manual_url にそのまま入れる。生成パイプラインは走らない(process_one.py が早期 return する) +- **エラー時の動作**: Doc 取得失敗 / generate 失敗 / verify 失敗、いずれもパイプライン状態 = "エラー"、備考に詳細を書く。次回再生成で復活可能 +- **同時実行制御**: process_one.py は単一プロセスで走らせる前提(同じマニュアルを並行処理しない)。watcher は処理中のマニュアルを再起動しないようロック +- **権限分離**: PM はスプシのオーナー権限、確認担当者は編集権限のみ、関係者外はビュー権限。スコープを絞ることで誤編集を防ぐ +- **PM 不在時のフォールバック**: 自動化が動かなくなった場合、PM の Mac で `generate_slide.py` を直接叩く運用に戻す(Phase 1 と同じフロー)。コードを残しておくこと + +--- + +このドキュメントは Phase 2 実装の進捗に合わせて更新する。実装した内容は同日中に本 MD に反映するルールにすると、コードと文書がズレない。 diff --git a/docs/proposals/manual-slide-pipeline.md b/docs/proposals/manual-slide-pipeline.md new file mode 100644 index 00000000..aa82fc06 --- /dev/null +++ b/docs/proposals/manual-slide-pipeline.md @@ -0,0 +1,181 @@ +# 解説マニュアル自動生成パイプライン + +ステータス: v1 稼働中、ブラッシュアップ進行中 +最終更新: 2026-05-11 +現 PM: 上林(かんばやし) +引き継ぎ予定: 2026-08月 +執行部説明予定: 2026-05 今週中 + +## 何を解決するか + +技大祭のマニュアル類は Google Document で作成されているが、当日スマホで見るには +情報密度が高すぎる・ナビゲーションが弱い・PDF を1ページずつスクロールする運用は +読みづらい、といった問題があった。 + +このパイプラインは Google Doc → スマホ最適化された解説 HTML への自動変換を担う。 + +入力: +- Google Doc を「ダウンロード → HTML」エクスポートしたファイル +- ドキュメント内の画像が同じフォルダにある状態 + +出力: +- 自己完結した HTML 1ファイル(CSS・JavaScript インライン、画像 base64 埋め込み) +- 任意のスマホ・PC ブラウザで開けて、オフラインでも動く +- カード形式 UI、フローティング目次ボタン、章ごとの折りたたみ等を備える + +## 全体構成 + +``` +[ Google Doc ] + │ Drive エクスポート(手動) + ▼ +[ ソース HTML + images/ ] + │ + │ pandoc で HTML → Markdown 変換 + │ regex で Google Docs のノイズ除去 + │ + ▼ +[ Markdown text + 画像ファイル名リスト ] + │ + │ LLM 呼び出し(3バックエンドから選択) + │ system_prompt = .claude/manual-prompt-card.md + │ + ▼ +[ HTML with {{filename}} プレースホルダー ] + │ + │ replace_placeholders() で画像を base64 化して埋め込み + │ + ▼ +[ slide_xxx.card.html ] (自己完結) +``` + +## 3バックエンド比較 + +| バックエンド | スクリプト | 認証 | 画像 | コスト | 品質 | +| --- | --- | --- | --- | --- | --- | +| Anthropic API | `scripts/generate_manual_slide.py` | `ANTHROPIC_API_KEY` | base64 inline(vision 有) | 従量課金 | 高 | +| Sakura AI Engine | `scripts/sakura-slide/generate_slide.py` | `SAKURA_API_KEY` | ファイル名のみ | 従量(安) | 中 | +| Claude Agent SDK | `scripts/claude-slide/generate_slide.py` | `claude login`(サブスク) | ファイル名のみ | サブスク枠(追加0円) | 高 | + +メイン運用想定は Claude Agent SDK 版。理由は以下: +- Anthropic API: 質は高いが従量課金で予算予測しにくい +- Sakura: 安いが gpt-oss-120b はカード形式の指示への追従が弱い +- Claude Agent SDK: サブスク $20-100/月で月額固定、Opus 4.7 が使えて品質は API 版と同等 + +## プロンプト + +共有プロンプト: `.claude/manual-prompt-card.md` + +3バックエンドが同じプロンプトを参照する設計。プロンプト改善が全バックエンドに自動で波及する。 + +主要な要件(プロンプトに記述済): +- SeeFT デザインシステムのカラー(teal #009688、ゴールド枠線 #C9A227 等) +- 目次セクション + 章ごとの折りたたみ + ライトボックス +- カンバン形式の役割分担カード(1枚で完結する情報密度) +- TOC ボタンカード、フローティング目次ボタン、章末ナビ、サブ目次 +- スマホ縦持ち最適化、本文 15px 以上 +- 元 Markdown にない情報を絶対に追加しない(メタ説明文、略称、UI ヒント等を禁止) +- 元 Markdown のコンテンツを絶対に省略しない + +## 運用方法 + +### 前提セットアップ(初回のみ) + +```bash +brew install pandoc +brew install uv +claude login # Claude Agent SDK 版のサブスク認証 +``` + +### 単一マニュアルを生成 + +```bash +uv run --project scripts/claude-slide python scripts/claude-slide/generate_slide.py --prompt card --model claude-opus-4-7 docs/manuals/01_44th_配線マニュアル +``` + +### 全マニュアルを一括生成 + +```bash +for d in docs/manuals/*/; do name=$(basename "$d"); echo "===== $name ====="; uv run --project scripts/claude-slide python scripts/claude-slide/generate_slide.py --prompt card --model claude-opus-4-7 "$d" || echo "FAIL: $name"; done +``` + +各マニュアル 3-6 分、合計 30-40 分程度。 + +### 生成された HTML の使い方 + +- 各マニュアルディレクトリの `slide_claude.card.html` を開けば閲覧可能 +- 自己完結なので、Slack に添付・GitHub Pages 公開・LINE 共有等で配布できる +- スマホでは縦持ち推奨 + +## コスト + +サブスク認証時: +- 月額 $20 (Pro) または $100 (Max 5x) の固定 +- 各生成は API 換算 $0.5〜1 だが、サブスクから追加課金は発生しない +- レート制限: Pro で 5h あたり ~190 本生成可能、Max 5x なら ~950 本 + +API 認証時(参考): +- 1本あたり $0.5〜1 +- 8本生成で $5-8 + +技大祭の規模なら Pro で十分。 + +## 既知の挙動 + +### max_turns について +Claude Agent SDK 版では `max_turns=10` を設定済。 + +理由: 大入力(28KB+ markdown)で Claude が稀に内部的にツール試行する挙動があり、 +`max_turns=1` だと「Reached maximum number of turns (1)」で失敗するケースがあった。 +`max_turns=10` で安全マージンを確保。実際の num_turns は 1 で済むことが多い。 + +### 画像配置精度 +LLM はファイル名と Markdown 文脈(figcaption の順序等)から推測して画像を配置する。 +Opus 4.7 はこの推論が強く、配線マニュアル 25 枚で全て正しく配置できた。 +ファイル名が完全に非記述的(`image1.png` 等)でも、文脈が十分なら正確に配置される。 + +vision を入れる検討は `manual-slide-vision-todo.md` を参照。 + +## 残課題(執行部発表前に解決したい) + +優先度高: +- 全8マニュアルの HTML 品質検証(実際にスマホで開いて読みやすさ確認) +- 説明資料 HTML の磨き込み(執行部向けプレゼン用) + +優先度中: +- 残バグの発見と修正 +- 引き継ぎチェックリストの確定 + +優先度低(v2 候補、引き継ぎ後): +- vision 化(画像も LLM に渡す版) +- compare_manual_versions.sh の3バックエンド対応 +- 自己レビューループ(生成 → プロンプト準拠チェック → 修正) + +## スケジュール(45th 技大祭向け) + +| 期間 | 内容 | +| --- | --- | +| 2026-05 今週 | 執行部発表(ハード締切) | +| 2026-05 後半 | 全マニュアル試作品の品質検証、フィードバック収集 | +| 2026-06 〜 07月 | 本番運用準備、必要な改善 | +| 2026-08月 | 新 PM への引き継ぎ開始 | +| 2026-09月前半 | 45th 技大祭で実運用 | +| 2026-09月から | 現 PM 海外実務訓練、新 PM が継続運用 | + +## 引き継ぎ + +引き継ぎ用の詳細資料は別ファイルに分離してある: + +- `docs/proposals/manual-slide-handover.html` — **新 PM 向け引き継ぎ資料**(環境セットアップ、3バックエンド詳細、運用コマンド、既知挙動、トラブルシューティング、v2 候補、チェックリスト) + +本 MD は技術リファレンスとして残し、引き継ぎ実務は HTML 側を参照する。 + +## 関連ドキュメント + +- `.claude/manual-prompt-card.md` — 現行のカード形式プロンプト(メイン) +- `.claude/manual-prompt.md` — 初代スライド形式プロンプト(参考) +- `.claude/manual-pipeline.md` — 設計検討の経緯 +- `docs/proposals/manual-slide-pipeline.html` — 執行部発表用スライド(非技術的) +- `docs/proposals/manual-slide-handover.html` — 新 PM 向け引き継ぎ資料(技術的) +- `docs/proposals/manual-slide-vision-todo.md` — vision 化検討(保留) +- `scripts/compare_manual_versions.sh` — Sakura 版で新旧プロンプトを並列生成(補助ツール) diff --git a/docs/proposals/verify-mechanical-improvements.html b/docs/proposals/verify-mechanical-improvements.html new file mode 100644 index 00000000..b33815bf --- /dev/null +++ b/docs/proposals/verify-mechanical-improvements.html @@ -0,0 +1,354 @@ + + + + + +機械検証スクリプト改善まとめ — verify_slide_mechanical.py + + + +
    + +
    +

    機械検証スクリプトの過検出を抑える改善

    +
    + 対象: scripts/claude-slide/verify_slide_mechanical.py
    + 作業日: 2026-05-31 / 担当: 上林(PM)
    + 背景: card-strict 生成 HTML の検証が常に VERDICT: NG になり、本物の差分がノイズに埋もれていた +
    +
    + +
    +

    1. 何をしたか(ひとことで)

    +
    +検証スクリプトが「内容の改変」と一緒に数えてしまっていた3種類のノイズ +(目次UIの重複・記号のゆれ・表の罫線)を、比較の前に取り除いた。 +最も効いたのは「追加」誤検出で、全ファイルで 59〜97% 減った +(目次UIの重複が消えたため)。 +ただし合計の差分件数で見ると 25〜54% 減にとどまる。 +判定ロジック自体は厳格なまま——内容の追加・欠落・改変は今までどおり全部 NG にする。 +
    +
    +正直な前置き:「追加」激減の一部は 「改変」への付け替えでもある。 +ナビ・罫線ノイズを消すと文の区切りが変わり、以前は「追加+欠落」の別々2件だったものが +「改変」1ペアに統合されることがある。実際、配線 card(緩いプロンプト)では改変が +27→31 件と微増した。総量は確実に減っているが、 +「追加が消えた=全部きれいになった」ではない点に注意。
    +また、ねらいは「VERDICT を無理やり OK にすること」ではない。画像ラベル付与や章番号の付与といった +本物の差分は残るので判定は NG のまま。狙いは、その本物の差分が +ノイズに埋もれて見えなくなる状態を解消すること。 +
    +
    + +
    +

    2. 改善した3点

    + +
    + 改善 A +

    ナビゲーション UI を比較対象から外した(最大の効果)

    +

    + プロンプトは目次(<nav>)・フローティング目次ボタン(FAB )・ + 閉じるボタン(×)の生成を義務付けている。これらは元 Google Doc には + 存在しない UI 部品なのに、検証器が本文として拾い「元にない情報=追加」と誤判定していた。 + 特に目次は仕様上 2 箇所に重複(トップ+オーバーレイ)するうえ、 + <ol> の連番(1. 2. 3.)まで本文に混ざっていた。 +

    +

    そこで pandoc に渡す前に、生成 HTML から該当 DOM ごと削除する。 + <nav> は入れ子にならないので、非貪欲マッチで安全に 1 要素ずつ落とせる。

    +
    + 消える誤検出の例: + 目次由来の '該当シフト 2.' '内容 3.'、FAB の '≡ × 目次'、 + オーバーレイ目次の重複ラベル群。 +
    +
    + +
    + 改善 B +

    「表記ゆれ」を改変から切り出し、別ティアにした

    +

    + 元 Doc では ~&・半角括弧、生成側では ・全角括弧、 + といった見た目だけの差を、これまでは「改変」として NG 要因に数えていた。 + 内容(漢字・かな・英数字)は同一なのに NG が膨らむ原因だった。 +

    +

    + NFKC 正規化+波ダッシュ統一+空白無視で畳んだとき一致するペアは、 + 「改変」ではなく「表記ゆれ(許容差)」に分類する。 + デフォルトでは VERDICT を NG にしない。可読文字の追加・削除・置換は畳まれないので、 + 本物の改変はこれまでどおり検出され続ける。 +

    +
    + 表記ゆれに回る例(NG にしない): + 9:00~17:00 → + 9:0017:00、 + 元 Doc 由来のスペース混入 完成 した完成した、 + youtubeか らyoutubeから。 +
    +

    + 従来どおり記号差も NG にしたい場合は --strict-symbols を付ける。 + この場合、表記ゆれも VERDICT に算入される。 +

    +
    + +
    + 改善 C +

    表の罫線ノイズを両側で除去した

    +

    + pandoc は表を罫線付きテキストに変換する。元 Doc はグリッド表 + (+---+)、生成側はシンプル表(-----)になりがちで、 + 同じ内容なのに罫線の記法が違うだけで「ブロックまるごと欠落」と誤検出していた。 + そこで罫線・セル区切り(- + | や box-drawing)を src/gen 双方で除去し、 + 表の中身(チーム割り・物品数など)だけを残して比較する。 + 電話番号の単独ダッシュ(080-6801-1943)は 3 連以上の罫線ランに当たらないので温存される。 +

    +
    +
    + +
    +

    3. ビフォー / アフター(実測)

    +

    配線マニュアル・幼稚園WARSマニュアルの計5パターンで再実行した結果。数値は誤検出件数(少ないほど良い)。

    + + + + + + + + + + + +
    マニュアル / 版追加欠落改変合計
    (追+欠+改)
    表記ゆれ
    (新・許容差)
    WARS card-strict10 → 22 → 312 → 624 → 11 (−54%)5
    WARS card19 → 610 → 514 → 1443 → 25 (−42%)6
    配線 card-strict34 → 131 → 2021 → 1986 → 40 (−53%)1
    配線 card42 → 326 → 1327 → 3195 → 47 (−51%)7
    配線 det(決定的)17 → 711 → 1112 → 1240 → 30 (−25%)0
    +

    判定は5本とも引き続き NG(本物の差分が残るため。第4節参照)。 +配線 card の改変 27→31(橙字)は誤検出ではなく、後述の「件数の付け替え」による微増。

    +
    +
    −59〜97%
    「追加」誤検出の削減幅(全5本)
    +
    −25〜54%
    合計差分件数の削減幅(全5本)
    +
    新カテゴリ
    表記ゆれを分離(判定に非算入)
    +
    +
    +読み方:「追加」の激減は改善A(目次UI除去)の効果で、これは堅実。 +ただし合計の減りは約半分で、card-strict(本番で使うプロンプト)が最も改善し、 +card(緩い版)は改変が増えて伸びが鈍い。緩いプロンプトは元々書き換えが多いため、 +ノイズを除いても本物の改変が多く残る。
    +det の表記ゆれが 0 件なのは、決定的変換は記号を一切置換しないため—— +「LLM が記号を整えてしまう」という仮説の裏付けにもなっている。 +
    +
    + +
    +

    3.5. 「件数」では伝わらない——文字ベースの保持率サマリを追加

    +
    +

    +件数(改変19件…)は画像alt移動・表組み替え・元Doc由来のゴミで水増しされ、 +「実際に文章がどれだけ変わったか」が第三者に伝わらないという問題があった。 +そこでレポート冒頭に文字ベースの一致度サマリを出すようにした。 +件数を見なくても、この1ブロックで忠実度が分かる。 +

    +
    ## サマリ(文章の一致度・文字ベース)
    +- 本文保持率: 90.9% (元Docの可読文字 3,050 字のうち 2,772 字が生成HTMLに残存)
    +- AIが追加した可読文字: 220 字(生成HTML 全体 3,019 字)
    +  内訳: 配線マニュアル / 該当シフトを表示 / 集合場所・チーム分けを表示 / 所要時間を表示 / …
    +- AIが落とした/書き換えた可読文字: 278 字
    +  内訳: 表晴天時チーム分け / 図 / :\Users\0102\AppData\Loc… / Word\DSC_ / JPG / …
    +- 完全一致した文: 41/81 文
    +

    +保持率は文ペアリングに依存させない。元Doc の各文が生成HTML全体の +どこかに(章順を入れ替えても)現れる文字を「保持」と数える。これで表ブロックの +ペア失敗に引きずられず、章の組み替えにも強い。 +

    +
    +内訳が効く例:上の配線 card-strict は「220字追加」だけ見ると不安だが、内訳を見ると +正体は ○○を表示 という見出しラベルの自動付与だと分かる(本文の捏造ではない)。 +落とした側の内訳も :\Users\…\AppData Word\DSC_ など +元Docに紛れたWindows画像パスのゴミで、消えて正解と判断できる。 +数字だけでは下せない判断が、内訳で即できる。 +
    +
    + + + + + + + + + + + +
    マニュアル / 版本文保持率AI追加文字完全一致文
    配線 det(決定的)100.0%1658/81
    配線 card-strict90.9%22041/81
    配線 card88.6%47530/81
    WARS card-strict97.2%4815/29
    WARS card92.0%1054/29
    +
    +指標の妥当性:決定的変換(det)が 保持率 100% / 追加 16字 になったのが +裏付け。本文を写経するだけの変換なので満点が出るはず——その通りになった。 +順序も det > card-strict > card で、プロンプトが緩いほど保持率が下がり追加が増える、 +という直感と一致する。 +
    +
    + +
    +

    4. なぜ判定は NG のまま残るのか

    +
    +

    過検出を消した後に残る差分は、本物(=人が見て判断すべき差)が中心になった。代表例:

    +
      +
    • 画像ラベルの付与:元 Doc は alt 無しの []、生成側は [塗り絵1] のように + 画像へラベルを付けている(情報の追加と取れる)
    • +
    • 章番号の付与:見出しに 1 / 2 / 3 の連番を付けている
    • +
    • ラベルの繰り返し:連絡先で 渉外局 を各人に付け直している
    • +
    • 表紙の語順入れ替え:タイトルと委員会名・日付の並び順が変わっている
    • +
    • 複雑な表の組み替え:配線マニュアルの晴天/雨天チーム表は構造が大きく、 + なお欠落判定が残る(さらなる正規化の余地)
    • +
    +

    +これらは「ノイズ」ではなく、文章不変ポリシーの観点で人が確認すべき差分。 +検証器が NG を出し続けるのは正しい挙動で、改善の目的(ノイズ除去)とは両立する。 +

    +
    +
    + +
    +

    5. 使い方

    +
    # 通常(表記ゆれは許容差として VERDICT に算入しない)
    +uv run --project scripts/claude-slide python scripts/claude-slide/verify_slide_mechanical.py docs/manuals/01_44th_配線マニュアル
    +
    +# 厳格モード(記号差も NG にする・従来挙動)
    +uv run --project scripts/claude-slide python scripts/claude-slide/verify_slide_mechanical.py docs/manuals/01_44th_配線マニュアル --strict-symbols
    +
    +# card 版や det 版を検証する場合は対象を切り替え
    +... --html-name slide_claude.card.html --output-name verify_mechanical.card.txt
    +
    +レポートには新たに 「## 表記ゆれ(許容差・VERDICT に非算入)」 セクションが追加され、 +記号差・空白差がそこに集約される。「## 改変」には内容の差だけが残るので、 +レビュー時はまず改変・追加・欠落の3つだけ見ればよい。 +
    +
    + +
    +

    6. 変更点(技術メモ)

    +
    +

    変更ファイル:

    +
      +
    • scripts/claude-slide/verify_slide_mechanical.py(+144 / −22 行)
    • +
    +

    主な追加・変更関数:

    +
      +
    • preprocess_generated_html() — base64 除去に加え、<nav>・FAB・閉じるボタンを DOM ごと削除(改善A)
    • +
    • strip_table_art() — pandoc の表罫線・セル区切りを除去(改善C)。改行を潰す前段で実行
    • +
    • fold_cosmetic() — NFKC+波ダッシュ統一+空白除去で表記ゆれ判定キーを作る(改善B)
    • +
    • categorize() — 2段ペアリング化。pass1 で表記ゆれを先に回収 → pass2 で類似度ペアリング。戻り値に cosmetic を追加
    • +
    • compute_fidelity() — 文字ベースの保持率サマリを算出(第3.5節)。ペアリング非依存・章組み替えに強い照合
    • +
    • render_report() — 冒頭に文字ベースサマリ、「表記ゆれ」セクション、統計行を追加。VERDICT は内容差分のみで判定(--strict-symbols 時のみ表記ゆれを算入)
    • +
    +

    +すべて AI を呼ばない決定的処理のまま。同じ入力 → 同じ出力という性質は維持。 +生成側(プロンプト)は変更していない。 +

    +
    +
    + +
    +

    7. 残課題(任意・次の改善余地)

    +
    +
      +
    • 画像 alt([ラベル])の正規化:両側で [image] に畳めば画像ラベル付与の誤検出が減る
    • +
    • 章番号バッジの除去:見出し先頭の連番を chrome として扱うか、内容として扱うかの方針決め
    • +
    • 配線マニュアルの大型表:行・列の対応づけ単位での比較に切り替えると欠落判定がさらに減る
    • +
    • 生成プロンプト側の対処:表記ゆれ(/)はそもそも生成させない指示強化も選択肢
    • +
    +
    +
    + +
    + + From 444e95615c4db5f9d14fb946f5a47b923bfbe9b6 Mon Sep 17 00:00:00 2001 From: taminororo <169162271+taminororo@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:53:45 +0900 Subject: [PATCH 10/14] =?UTF-8?q?fix:=20card-strict=20=E3=81=A7=E7=94=9F?= =?UTF-8?q?=E6=88=90HTML=E3=81=AB=E7=94=BB=E5=83=8F=E3=81=8C=E5=9F=8B?= =?UTF-8?q?=E3=82=81=E8=BE=BC=E3=81=BE=E3=82=8C=E3=81=AA=E3=81=84=E4=B8=8D?= =?UTF-8?q?=E5=85=B7=E5=90=88=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LLM が {{ファイル名}} プレースホルダーを使わず素のファイル名や相対パスで を出力した場合、replace_placeholders が拾えず base64 化されなかった (card-strict ルートで多発、のぼり広告・幼稚園WARS で画像が表示されない状態)。 - replace_placeholders を堅牢化: {{}} に加え、src の basename がローカル画像に 一致する を決定的に base64 埋め込みする(_resolve_image_src を追加)。 既に data: の src は冪等に素通り。LLM のプレースホルダー遵守に依存しない。 - --embed-only モードを追加: LLM を呼ばず既存HTMLの画像だけを再埋め込み(壊れた 出力の決定的な復旧用)。 Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01HtcZLCKZk7zMvqiDAntY2y --- scripts/claude-slide/generate_slide.py | 51 ++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/scripts/claude-slide/generate_slide.py b/scripts/claude-slide/generate_slide.py index 4d22bb2a..49629cba 100644 --- a/scripts/claude-slide/generate_slide.py +++ b/scripts/claude-slide/generate_slide.py @@ -121,9 +121,40 @@ def load_images_base64(manual_dir: str) -> dict[str, str]: return images +def _resolve_image_src(src: str, images: dict[str, str]) -> str | None: + """`` の src 値がローカル画像を指すなら data URI を返す。違えば None。 + + card-strict では LLM が `{{fname}}` を守らず、素のファイル名 (`image1.jpg`) や + 相対パス (`images/image1.png`, `./images/image1.png`) で src を書くことがある。 + その揺れを決定的に吸収して必ず base64 埋め込みする保険。 + + 引数: + src : img タグの生の src 値(例: "image1.jpg" / "images/image1.png" / + "./images/image1.png" / "data:image/png;base64,...") + images : {ファイル名 -> data URI} の辞書(load_images_base64 の返り値) + + 注意: 既に `data:` 化済みの src は再処理しても None を返すこと(冪等性)。 + """ + if src.startswith("data:"): + return None # 既に埋め込み済み。再処理しても壊さない(冪等) + return images.get(os.path.basename(src)) + + def replace_placeholders(html: str, images: dict[str, str]) -> str: + # 1) {{fname}} プレースホルダー(プロンプトが指示する正規ルート) for fname, data_uri in images.items(): html = html.replace(f"{{{{{fname}}}}}", data_uri) + + # 2) 素のファイル名・相対パスで書かれた も決定的に base64 化する保険。 + # LLM が {{}} を守らなかった場合(card-strict で多発)でも画像が必ず埋まる。 + def _embed(m: "re.Match[str]") -> str: + quote = m.group("q") + data_uri = _resolve_image_src(m.group("src"), images) + if data_uri is None: + return m.group(0) + return f"src={quote}{data_uri}{quote}" + + html = re.sub(r'src=(?P["\'])(?P[^"\']*)(?P=q)', _embed, html) return html @@ -209,6 +240,11 @@ def main() -> int: default=None, help="使用するモデル(例: claude-opus-4-7, claude-sonnet-4-6)。未指定なら Claude Code のデフォルト", ) + parser.add_argument( + "--embed-only", + action="store_true", + help="LLM を呼ばず、既存の出力 HTML に画像を base64 再埋め込みするだけ(決定的・再生成不要)", + ) args = parser.parse_args() manual_dir = resolve_manual_dir(args.manual_dir) @@ -216,6 +252,21 @@ def main() -> int: output_filename = "slide_claude.html" if variant == "default" else f"slide_claude.{variant}.html" output_path = os.path.join(manual_dir, output_filename) + # 既存 HTML への画像再埋め込みのみ(壊れた card-strict の決定的な復旧用) + if args.embed_only: + if not os.path.isfile(output_path): + print(f" ERROR: 出力 HTML が見つかりません: {output_path}", file=sys.stderr) + return 1 + with open(output_path, "r", encoding="utf-8") as f: + slide_html = f.read() + before = len(slide_html) + slide_html = replace_placeholders(slide_html, load_images_base64(manual_dir)) + with open(output_path, "w", encoding="utf-8") as f: + f.write(slide_html) + print(f"=== 画像再埋め込み(--embed-only)===") + print(f" {output_path}: {before//1024}KB → {os.path.getsize(output_path)//1024}KB") + return 0 + prompt_path = _resolve_prompt_path(variant) system_prompt, user_prompt_template = _load_prompt_from_md(prompt_path) From 8877aa4ed1d136fdbe824b8870a3862b9c80aa63 Mon Sep 17 00:00:00 2001 From: taminororo <169162271+taminororo@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:54:16 +0900 Subject: [PATCH 11/14] =?UTF-8?q?feat:=20=E9=83=A8=E9=96=80=E9=95=B7?= =?UTF-8?q?=E3=83=AC=E3=83=93=E3=83=A5=E3=83=BC=E3=81=AE=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=82=92=20instructions.md=20=E3=81=A7=E3=83=97=E3=83=AD?= =?UTF-8?q?=E3=83=B3=E3=83=97=E3=83=88=E3=81=AB=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 再生成ループ(部門長の修正案→再生成)を、共有プロンプトを書き換えずに回すための仕組み。 マニュアルディレクトリに instructions.md があれば、その内容を「この回の追加・修正指示 (最優先)」として user プロンプト末尾に注入する。Slack の修正案をローカルにコピペ→再生成、 の運用に対応する。 Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01HtcZLCKZk7zMvqiDAntY2y --- scripts/claude-slide/generate_slide.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scripts/claude-slide/generate_slide.py b/scripts/claude-slide/generate_slide.py index 49629cba..21607b40 100644 --- a/scripts/claude-slide/generate_slide.py +++ b/scripts/claude-slide/generate_slide.py @@ -174,12 +174,20 @@ async def call_claude_sdk( md_content: str, image_files: list[str], model: str | None, + extra_instructions: str = "", ) -> tuple[str, dict]: image_list = "\n".join(f"- {f}" for f in image_files) user_text = user_prompt_template.format( image_list=image_list, html_content=md_content, ) + # 部門長レビューの修正案など、この回だけの追加指示を末尾に注入する。 + # 共有プロンプトを書き換えずにマニュアル単位で再生成を回すための仕組み。 + if extra_instructions.strip(): + user_text += ( + "\n\n# この回の追加・修正指示(前回レビューの反映。最優先で従うこと)\n" + + extra_instructions.strip() + ) # max_turns を 20 に: 大きい入力(55KB+ markdown)で Claude が tool 試行する場合に # disallowed_tools 拒否で turn が消費されるため、余裕大きめ。 @@ -278,6 +286,14 @@ def main() -> int: md_content, image_files = load_source(manual_dir) print(f" Markdown: {len(md_content)//1024}KB, Images: {len(image_files)} files") + # マニュアルごとの追加・修正指示(部門長レビューの反映用)。あれば末尾に注入する。 + extra_instructions = "" + instr_path = os.path.join(manual_dir, "instructions.md") + if os.path.isfile(instr_path): + with open(instr_path, "r", encoding="utf-8") as f: + extra_instructions = f.read() + print(f" 追加指示: {instr_path} ({len(extra_instructions)} chars)") + response_text, _usage = anyio.run( call_claude_sdk, system_prompt, @@ -285,6 +301,7 @@ def main() -> int: md_content, image_files, args.model, + extra_instructions, ) slide_html = extract_html(response_text) From bc8f4dddd0016ab0b18cce921984934e72a3edc4 Mon Sep 17 00:00:00 2001 From: taminororo <169162271+taminororo@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:54:33 +0900 Subject: [PATCH 12/14] =?UTF-8?q?feat:=20=E6=A9=9F=E6=A2=B0=E6=A4=9C?= =?UTF-8?q?=E8=A8=BC=E3=81=AB=E9=83=A8=E9=96=80=E9=95=B7=E5=90=91=E3=81=91?= =?UTF-8?q?=E3=81=AE=E6=96=87=E7=AB=A0=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=83=AC=E3=83=9D=E3=83=BC=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 部門長(非エンジニア)が Slack スレッドでそのまま読める日本語サマリ verify_report.card-strict.md を出力する。デザインは検証せず、文章の欠落・改変・追加だけを示す。 決定的な比較ロジック(categorize/compute_fidelity)は無改変。出力前に構造ノイズを本文差から 分離する分類層を追加した: - 見出しの採番(2 手順)・figcaption の角括弧化([部品一覧])・末尾連番/目次ページ番号は「整形差」 - 箇条書き記号(・↔-)は表記ゆれとして fold_cosmetic で吸収 - 折りたたみの トグルラベルは HTML 前段で要素ごと除去 - 図番号(図N)は「画像・図表の差」、CJK を含まない断片(元Doc由来のパス等)は本文外として退避 - 並べ替えで反対側に実在する文は「確認不要」に振り分け 結果、部門長が見るべき実質の本文差だけが残る(のぼり広告で要確認 13→5 件)。 Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01HtcZLCKZk7zMvqiDAntY2y --- .../claude-slide/verify_slide_mechanical.py | 213 +++++++++++++++++- 1 file changed, 211 insertions(+), 2 deletions(-) diff --git a/scripts/claude-slide/verify_slide_mechanical.py b/scripts/claude-slide/verify_slide_mechanical.py index de5fd471..d9201763 100644 --- a/scripts/claude-slide/verify_slide_mechanical.py +++ b/scripts/claude-slide/verify_slide_mechanical.py @@ -93,12 +93,18 @@ def find_source_html(manual_dir: str) -> str: r']*class="[^"]*(?:fab-toc|toc-close)[^"]*"[^>]*>.*?', re.DOTALL | re.IGNORECASE, ), + # 折りたたみ
    ラベル(「○○を表示」「表示/非表示」等)。 + # プロンプトが付ける開閉ボタンの文言で元 Doc には無い。中身(details 本体)は残す。 + re.compile(r"]*>.*?", re.DOTALL | re.IGNORECASE), ] #