generate-diffmap について
generate-diffmap は、ソフトウェア変更を説明する Markdown 仕様書の作成と共有を支援するエージェントスキルです。コードを確認して範囲を定め、段階的な説明や図、ソース参照を使って変更をまとめます。
主な機能
- 01更新し続ける Markdown 仕様書
- 02執筆前の変更範囲の確認
- 03段階ごとの変更説明
- 04図とコールスタック表示
- 05ソースへの参照リンク
- 06Diffmap による配信と共有
活用シーン
- 01既存のプルリクエストの記録
- 02予定しているコード変更の説明
- 03完了した実装フェーズの追跡
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tanishqkancharla/ diffmap --skill generate-diffmapインストール後、Claude Code で/generate-diffmapを入力して読み込まれたか確認します。
同梱ファイルagents/openai.yaml
詳細
進行中の仕様書
仕様書は specs/<short-kebab-case-name>.md に置く単一の Markdown ファイルです。現在のシステム、計画、完了した作業を示します。計画が変わったときや作業が完了したときは、仕様書を更新してください。既存のプルリクエストを順に確認する場合は、仕様書があればそれを使い、なければ .diffmap/ に作成してください。作成する前に、リポジトリ内のパスとシンボルを確認してください。
まずスコープを確定してください。 作成する前に、関連するコードを読んでください。設計、スコープ、フェーズの分け方が不明確な場合は、多くを書き始める前にユーザーに確認し、選択肢とおすすめの案を提示してください。
提供と共有
リポジトリのルートから実行します。
npx @tanishqkancharla/diffmap list # reuse the URL if this file is already served
npx @tanishqkancharla/diffmap serve specs/<name>.md
npx @tanishqkancharla/diffmap share specs/<name>.md # only when asked; prints https://diffmap.dev/g/<id>
サーバーを実行したままにし、ファイルパスと URL をユーザーに伝えてください。ブラウザーを開くのは、依頼された場合に限ります。共有する前に、完了した作業をコミットし、「フェーズが完了したとき」の例のように、参照先をそのコミットに設定してください。share は、参照されている各コミットの差分を gist に保存しますが、コミットのない参照先は、共有時点のファイルを開くだけです。共有された gist は限定公開です。URL を知っている人なら誰でも、スナップショットに含まれるすべてのファイルを読むことができます。プルリクエスト上の仕様書も https://diffmap.dev/<owner>/<repo>/pull/<n>/specs/<name>.md から参照でき、特定のコミットに固定されます。
構成
変更をすばやく理解する必要がある人に向けて書きます。画面、コマンド、API 呼び出し、エラーなど、使う人にとって何が変わるのかから始め、読者に必要な範囲で実装へと掘り下げます。仕様書には通常、問題の概要、解決策の概要、目標と対象外、フェーズ、参考資料を含めます。それぞれに必要な内容は、仕様書の内容に応じて決めます。
フェーズ。 それぞれ単独でコミットできる小ささにします。見出しは、予定段階ならそのまま、進行中なら ⏳、完了なら ✅ を付けます。各フェーズは、クライアントから見た短い 現在 と 提案 から始め、その後は変更に合った方法で説明します。新しいフローなら図とコールスタック、リファクタリングならコールスタックの差分、依存関係の更新なら数文が適しています。変更の規模に合わせて長さを調整し、各項目は適切な箇所で一度だけ説明します。仕様書は変更を説明するものであり、タスク管理表ではありません。
図解。 図、コールスタック、コードは、段落よりも変更をわかりやすく説明できることが多いため、文章が長くなったら活用します。読者に必要な情報を示す場合に限って追加し、枠を埋めるためだけには使いません。
各ビューは、サービスがメッセージを受け取り、状態を更新し、次へメッセージを送るという、ひとつのモデルに沿っています。各ビューは、サービス間またはサービス内部を、すべての経路またはひとつのメッセージの追跡という観点から見ます。
| すべての経路 | ひとつのメッセージ | |
|---|---|---|
| サービス間 | mermaid フローチャート:存在する要素と、それぞれの所有者 | services:状態を変化させる、サービス間を移動するメッセージ |
| ひとつのサービス内 | 疑似コードと型:状態、ペイロード、ロジック | callstack:ひとつのメッセージを結果に変換する方法 |
各要素が状態を保持する、同時に実行される、または複数に分岐する場合は services を使います。戻り値のある関数呼び出し、分岐、または早期リターンを表す場合はコールスタックを使います。Mermaid のシーケンス図と状態図は、より簡潔な選択肢です。ビューに約 7 つを超えるサービスが必要な場合は、一部をまとめて別途説明します。
- 変更を示すには、
mermaidまたはservicesの後にdiffを追加し、コールスタックと同様に各行を-(現在のみ)、+(提案のみ)、またはスペース(両方)で始めます。コードにはdiffフェンスを使います。 - Mermaid 図には、
mermaid diff [Invalid records stop before storage]のように、言語の後に[タイトル]を付けます。servicesフェンスでは、最初のステップの前にstart "caption"を置き、図全体のタイトルにします。その下に>行を置き、プロセス全体を数文で要約します。各ステップには、そこで起きることを説明するキャプションを付けます。 - ノードと矢印には実際の名前を付け、既存のソースへのリンクには
%% refディレクティブを使います。
コールスタック。 行は result = function(inputs) の形式でデータの流れとして書き、重要な箇所には return の行を入れます。各行には -(元の状態)、+(変更)、またはスペース(変更なし)を付けます。差分では、変更前と変更後で同じ関数を比較します。呼び出しを ├── と └── でネストします。実際の関数にはソース参照を付けます。リンクのないヘルパーは疑似コードであり、保証された API ではありません。スタックはおよそ 12 行以内に収め、深いフローは概要と、変更された呼び出し先ごとのスタックに分けます。
コード。 フェンスは短く保ち、段落で説明している点に絞ります。変更のないコードは // ... で省略します。言語名の後に {2-3} を付けて行を強調します(または callstack の後に {3-4} を付けて行を指定します)。長いコメントではなく、末尾の注記で説明します。名前が登場する箇所には // [!note symbol] text、フェンスの先頭から数えた行には // [!note L3-L5] text を使います。コールスタックでは、同じ注記を # で記述します。
リンク。 既存のソースには参考資料へのリンクを付けます。予定されているシンボルはプレーンテキストで記述します。
文章
文章、コメント、図のラベルにはすべて、ジョージ・オーウェルの「Politics and the English Language」にある6つのルールを適用します。コード名は専門用語ではありません。
フェーズの例
この例では、Diffmap のソースパスではなく、説明用のファイルとコミット SHA を使用します。変更がいくつかのレイヤーにまたがるため、複数のビューを使用しています。小規模なフェーズであれば、必要なビューは少なくなります。
### ⏳ Phase 1: Validate request input
**Today**
The handler passes records straight to the service. A client that sends an empty name gets a success response, and the bad record surfaces later as a storage error the client never sees, so they keep sending bad input.
**Proposed**
A client that sends an empty name gets an error naming the `name` field, so they can fix the request right away. Bulk import in Phase 2 needs every saved record to be valid, so this check comes first.
From the client's side, the same empty-name request now fails at once:
```ts
const response = await client.saveRecord({ name: "" });
// before: { ok: true }
// after: { ok: false, error: "name must not be empty" }
```
Today every record reaches storage. In the proposed flow, a valid record follows the same save path, but an invalid one returns an error before storage. The unlinked `validateInput` helper is pseudocode, not a proposed public API:
```mermaid diff [Invalid records stop before storage]
flowchart LR
- A[requestHandler] -->|record| B[existingService]
+ A[requestHandler] -->|record| V[validateInput]
+ V -->|valid record| B[existingService]
+ V -->|invalid record| E[Return name error]
B -->|record| D[Storage]
%% ref node:A [[src/request.ts#requestHandler]]
%% ref node:B [[src/service.ts#existingService]]
```
The call stack shows how the validation result controls the return path:
```callstack
response = requestHandler(record) [[src/request.ts#requestHandler]]
-└── return existingService(record) [[src/service.ts#existingService]]
+├── error = validateInput(record) # reject empty names
+├── if error: return error
+├── result = existingService(record) [[src/service.ts#existingService]]
+│ ├── storage.save(record)
+│ └── return { ok: true }
+└── return result
```
In code, the handler returns before the existing save:
```ts {2-3}
function requestHandler(record: RecordInput) {
const error = validateInput(record);
if (error) return error;
return existingService(record);
}
// [!note validateInput] Returns an error when `name` is blank, otherwise undefined.
// [!note L3] Storage never sees an invalid record.
```
The new check:
```ts
function validateInput(record: RecordInput) {
if (record.name.trim() === "")
return { ok: false, error: "name must not be empty" };
return undefined;
}
```
フェーズが完了したら
次のフェーズを開始する前に、以下を行ってください。
- コードをコミットします。フェーズに複数のコミットが含まれる場合は、以下の手順で SHA と記載されている箇所では
<base>..<head>を使用します。 - Today と Proposed の直後に、その SHA を含む
changesフェンスを追加します。フェーズの概要カードが表示されます。SHA の下に、レビュアーが読むべきファイルを、読む順に 1 行に 1 パスずつ記載します。通常は、クライアント向けのエントリーポイントから始めて、レイヤーを順にたどります。ロックファイル、生成コード、スナップショット、フィクスチャ、機械的な名前変更など、特に確認する必要のないファイルは省きます。カードでは、それらが「N other files」の下にまとめられます。 - フェーズで変更したすべての参照先を、そのコミットに向けます。スタックの行、
%% refディレクティブ、文章中の参照が対象です。 - 図、コールスタック、疑似コードを実装内容に合わせて更新し、見出しに ✅ を付けます。
- 仕様の更新を別途コミットします。そこにはコードのコミットの SHA を記載します。
serveを再起動します。起動時にコミット参照が検証されます。
この例のフェーズは、次のようになります。
```changes
a1b2c3d
src/request.ts
src/validate.ts
```
```callstack
response = requestHandler(record) [[a1b2c3d:src/request.ts#L8-L20]]
-└── return existingService(record) [[src/service.ts#existingService]]
+├── error = validateInput(record) [[a1b2c3d:src/validate.ts#L1-L9]]
+├── if error: return error
+├── result = existingService(record) [[src/service.ts#existingService]]
+│ ├── storage.save(record)
+│ └── return { ok: true }
+└── return result
```
ソース参照
パスはリポジトリのルートからの相対パスです。
[[path/to/file.ts#symbolName]]は TypeScript または JavaScript の宣言へのリンクです。曖昧さがある場合は、[[src/store.ts#Store.save]]のように対象を明示します。[[path]]はファイルへのリンクです。[[path#L12-L30]]は行範囲へのリンクです。[[<sha>:path#L12-L30]]はコミット内の行へのリンクです。複数のコミットにまたがるフェーズの場合は<base>..<head>を使用します。<base>は最初のコミットの直前のコミットです。行番号はコミット後の状態で付けられるため、削除された行にはリンクできません。git show <sha> -- <path>とgit show <sha>:<path> | nl -baで確認できます。- コミットを指定しない参照は、そのファイルに未コミットの変更があればその内容を、なければ現在のファイルを開きます。共有された gist は、共有時点のファイルを開きます。
- 文章中の参照は、クリック可能なインラインコードとして表示されます。構文を表示するには、参照をバッククォートで囲みます。
- Mermaid では、
%% ref node:<id> [[reference]]はノードまたは参加者に、%% ref edge:<index> [[reference]]はエッジまたはメッセージにリンクします。インデックスは宣言順に 0 から数えます。対象ごとに 1 つのディレクティブを使用し、必要に応じて複数の参照を指定します。
フェンス:mermaid は図を描画し、services はサービス、その状態、およびサービス間のイベントをアニメーション付きで描画します(どちらかの後に diff を追加すると、- または + の行を強調表示します)。callstack はスタックの行を、changes はコミットまたは範囲の概要(未コミットの変更の場合は空)と、その下に記載されたレビュー対象ファイルを表示します。html は自分で記述した信頼済み HTML を表示します。その他の言語は疑似コードと型を表し、任意で {…} による強調表示や [!note …] の行を使用できます。言語名の後に [Title] を付けると、たとえば ```callstack [Request flow] のように、コールスタック、コードフェンス、または Mermaid 図にタイトルが付きます。
その他の情報を確認する
- リファレンスとフェンス構文の全容 → diffmap README
- 実例 → annotations、diagrams、services のフィクスチャ