Wake — ドリームレポートから選択した内容を適用
dreamがレポートを作成し、ユーザーがチェックを付けた後、wakeがその選択を引き継いで適用します。
安全の原則
wakeは決して何を適用するかを独自に判断しません。選択のソース(優先順位順):
- JSONファイル
DREAM-CHOICES-<date>.json(HTMLの「Save」ボタン、または自動モードでバリデーターが作成したもの —"auto": trueフラグ付き、dream SKILL.mdの「Auto mode」参照) - スキル引数 —
wake M1,M3,N2またはwake all
どちらも指定されていない場合、ユーザーに何を選択するか尋ね、何もしません。
ワークフロー
Phase 1 — ロックしてレポートと選択を見つける
まずロックで競合を防止(dream/SKILL.mdのPhase 0と同様)。2つのセッションが同時に同じレポートでwakeを実行すると、Editが互いに上書きし、MEMORY.mdがめちゃくちゃになります。
LOCK_DIR="$CWD_BASH/.wake-lock"
if mkdir "$LOCK_DIR" 2>/dev/null; then
echo $$ > "$LOCK_DIR/pid"
else
LOCK_AGE=$(( $(date +%s) - $(stat -c %Y "$LOCK_DIR" 2>/dev/null || stat -f %m "$LOCK_DIR") ))
if [ "$LOCK_AGE" -gt 3600 ]; then
rm -rf "$LOCK_DIR" && mkdir "$LOCK_DIR" && echo $$ > "$LOCK_DIR/pid"
else
echo "WAKE ALREADY RUNNING (lock age ${LOCK_AGE}s) — abort"
exit 0
fi
fi
# Phase 5の最後: rm -rf "$LOCK_DIR"
最初にパスを計算(dream/SKILL.mdの_BASHはbashコマンド、_WINはPython用):
# パスを計算 (Win11 Git Bash)
CWD_BASH=$(pwd) # /d/Projects/TEMP
CWD_WIN=$(pwd -W 2>/dev/null || pwd) # D:/Projects/TEMP (非Windowsの場合はフォールバック)
SLUG=$(echo "$CWD_WIN" | sed 's|[:/]|-|g') # D--Projects-TEMP
MEMORY_DIR_BASH="$HOME/.claude/projects/$SLUG/memory"
MEMORY_DIR_WIN=$(cygpath -w "$MEMORY_DIR_BASH" 2>/dev/null | sed 's|\\|/|g' || echo "$MEMORY_DIR_BASH")
test -d "$MEMORY_DIR_BASH" && echo "OK $MEMORY_DIR_WIN" || { echo "MEMORY DIR NOT FOUND — wake cannot proceed without it"; }
# 1. CWD内の最新のdreamレポートMDを検索
LATEST_REPORT=$(ls "$CWD_BASH"/DREAM-REPORT-*.md 2>/dev/null | sort -r | head -1)
if [ -z "$LATEST_REPORT" ]; then
echo "NO REPORT FOUND — run dream skill first to generate DREAM-REPORT-<date>.md"
# 終了しない — Claudeがこの出力を読み、ワークフローを自身で停止する
else
echo "Report: $LATEST_REPORT"
fi
# ファイル名から日付を抽出: DREAM-REPORT-2026-05-02.md → 2026-05-02
REPORT_DATE=$(basename "$LATEST_REPORT" | sed 's|DREAM-REPORT-||; s|\.md||')
# 2. 選択JSONを検索 (優先順位: cwd → ~/Downloads/ → ~/Desktop/)
CHOICES=""
for path in "$CWD_BASH/DREAM-CHOICES-$REPORT_DATE.json" "$HOME/Downloads/DREAM-CHOICES-$REPORT_DATE.json" "$HOME/Desktop/DREAM-CHOICES-$REPORT_DATE.json"; do
if [ -f "$path" ]; then CHOICES="$path"; break; fi
done
# フォールバック: 日付に関係なく最新のCHOICES
if [ -z "$CHOICES" ]; then
CHOICES=$(ls -t "$HOME/Downloads/DREAM-CHOICES-"*.json "$CWD_BASH/DREAM-CHOICES-"*.json 2>/dev/null | head -1)
fi
echo "Choices: ${CHOICES:-NOT FOUND}"
日付確認: JSON内のreport_dateがMDファイル名の日付と一致しない → WARN、ユーザーに確認:
⚠️ DREAM-CHOICESの日付 (2026-05-01) が最新のDREAM-REPORT (2026-05-02) と一致しません。そのまま使用しますか、中止しますか? (apply/cancel)
argsもJSONもない場合:
DREAM-CHOICES-*.jsonが見つからず、引数にもIDがありません。選択肢:
- HTMLレポート
DREAM-REPORT-<date>.htmlを開き、チェックを付けて 💾 Save choices をクリック- またはチャットでIDを指定:
wake M1,M3,N2またはwake all
Phase 2 — レポートを読み込み、JSONブロックを解析
DREAM-REPORT-<date>.md を全体読み込み。JSON契約の提案については、<project>/.claude/skills/dream/references/action_types.mdを参照。
解析(信頼性の高い契約): 言語が json のフェンスコードブロックを検索。それぞれが個別の提案です。
Pythonを使用して解析(シェルのバックティック付きawkより信頼性が高い):
import re, json, sys
content = open(report_path, encoding='utf-8').read()
# パターン: triple-backtick + json + newline + content + newline + triple-backtick
pattern = r'```json\n(.*?)\n```'
blocks = re.findall(pattern, content, re.DOTALL)
proposals = []
malformed_indices = []
for i, block in enumerate(blocks):
try:
proposals.append(json.loads(block))
except json.JSONDecodeError as e:
print(f"WARN: malformed JSON block #{i}: {e}", file=sys.stderr)
malformed_indices.append(i)
# 後で failed[] に追加 — 適用ログ用にブロックインデックスを記録
必須フィールドを持つオブジェクトの配列を取得:
id(文字列、例 "M1")category("memory" | "notes" | "index" | "other")action(次のいずれか: update, merge, delete, soft_delete, create_new, extract, remove_links, shorten_lines, add_links, promote_skill, retire_skill, purge_trash)- アクション固有フィールド (dream/SKILL.mdの「Action types」セクション参照)
マップ {id: proposal} を構築。
選択内容を読み込み:
- JSONファイルがある場合 →
selected = json.selected(IDの配列) - args =
allの場合 →selected = マップ内のすべてのID - args =
M1,M3,N2の場合 → カンマで分割(余分なスペースをトリム)
確認:selected内の各IDがマップに存在すること。ない場合は警告、スキップ、「skipped」に追加。
Phase 3 — 確認サマリー(1つのゲート)
チャットに表示:
🌅 Wakeは、dreamレポート
<date>から N件のアイテム を適用する準備ができています:メモリ統合:
- M1. handoff_pikabu_* を project_pikabu_mcp.md にマージ(3つのソースファイル)
- M3. TRASH/*(1ヶ月以上前)をソフトデリート
ノート統合:
- N2. runware監査を抽出 → reference_runware_audit_2026_04.md
インデックス再構築:
- I1. デッドリンクを削除:4リンク
スキップ(レポートに存在せず):<あればリスト>
適用しますか? 「はい」/「go」/「apply」と答えてください。キャンセルする場合は「stop」/「cancel」。
選択されたアイテムのタイトルに [UNVERIFIED] が含まれている場合 → サマリーに ⚠️ を付けて表示し、dreamが陳腐化の理由を検証できなかったため、ユーザーの信頼に基づいて適用されることを注意喚起する。
確認を待つ。これが最終ゲート — 以降は確認なしで適用。
Autoモード: choices JSONに "auto": true かつ "validator_verdicts" が含まれている場合 — ゲートは情報提供のみ:サマリーとバリデーターの判定を表示し、確認を待たずに適用。適用前に自動停止リストを再確認:[UNVERIFIED] がタイトルにあるアイテム、action=delete、purge_trash、promote_skill がauto-JSONのselectedにある場合 → 除外、skipped[] に理由 "auto mode: manual-only action" を追加。auto: true で validator_verdicts がない場合 — JSONを疑わしいと見なし、手動モードと同じように動作(確認を待つ)。Wakeログに Gate: auto (validator) の行を追加。
Full auto(choices JSON内の "auto_level": "full"): アクションの自動停止リストが解除 — selected内の delete、purge_trash、promote_skill、retire_skill が適用される。delete に必須の補償:コンテンツをクリアする前に、ファイルをゴミ箱にコピー — cp "$MEMORY_DIR_BASH/<target>" "$MEMORY_DIR_BASH/TRASH/<target>.pre-delete-$REPORT_DATE.md" — その後Editを実行。promote_skill の場合:アクティベーションパスは必須でWakeログに記録(Promoted skills: <name> → <dest> の行) — スキルのロールバックは、そのフォルダをdestから ~/.claude/satori/staging/ にmvする。タイトルに [UNVERIFIED] があるアイテムはfull autoでも除外(バリデーターはそれらを承認する権限がない — selectedに入っているのはバグ、理由 "full auto: [UNVERIFIED] must not be auto-approved")。Wakeログ — Gate: full-auto (validator)。
なぜアイテムごとではなく1つのゲートか:ユーザーは既にチェックで考えた(またはautoモードで独立したバリデーターが代わりに考えた)。追加の承認は煩わしい。ただしサマリーは重要 — dream以降に何か変更されている可能性がある。
Phase 4 — 適用(確認後)
クロスプロジェクト解決(グローバルモード). proposalに project: <slug> フィールドがある場合 — ターゲットメモリディレクトリは ~/.claude/projects/<slug>/memory/、現在の $MEMORY_DIR_BASH ではない。フィールドがない場合(デフォルトモード) — $MEMORY_DIR_BASH を使用。
def resolve_memory_dir(proposal, default_dir):
proj = proposal.get('project')
if proj:
return os.path.expanduser(f'~/.claude/projects/{proj}/memory')
return default_dir
クロスプロジェクトアクセスを確認: selected内の各一意の project について — apply開始前に os.path.isdir(target_dir) を確認。ない場合 → そのクロスプロジェクトアイテム全体を理由 "memory dir not found for project=" で失敗。
変更前の完全スナップショット(常に、すべてのモードで — ワンコマンドロールバックを保証):
SNAP="$CWD_BASH/_archive/wake-backup-$REPORT_DATE-$(date +%H%M%S)"
mkdir -p "$SNAP"
cp -r "$MEMORY_DIR_BASH/." "$SNAP/"
echo "Snapshot: $SNAP"
# グローバルモード: selected内の各一意のprojectについて、そのmemory dirを "$SNAP/<slug>/" にcp -r
メモリディレクトリは小さい(数百KB) — スナップショットは、何が変更されたかについての議論よりもコストが低い。スナップショットのパスは最終レポートとWakeログに必須。
開始前に一度だけdestディレクトリを準備:
mkdir -p "$MEMORY_DIR_BASH/TRASH"
mkdir -p "$CWD_BASH/_archive/dream-applied-$REPORT_DATE"
mkdir -p "$CWD_BASH/_archive/trash-purged-$REPORT_DATE" # purge_trashアイテム用
# プロジェクトごとのTRASHディレクトリは適用時にオンデマンドで作成(selectedにすべてのプロジェクトがあるとは限らない)
MDレポートのApply order recommendationに従い、selected内の各アイテムを順番に処理(デフォルトでは M → N → I → S → O):
アクション: update
targetファイルを読み込みdiff_preview— 変更の自然言語での説明(例「'## Session 04-01'セクションを既存コンテンツに追加」)。構造化されたdiffではない。- ベストエフォートの適用可能性チェック:
diff_previewが特定の部分文字列('## Session 04-01'、'feedback_X.md'など)に言及している場合 → 部分文字列チェックif substring in current_content。言及された部分文字列がファイル内に明示的に存在するはずだが存在しない場合 → 理由 "target content drifted: '' not found" で失敗 - 変更が適用可能と確信できる場合 — Edit。変更方法が不明確な場合(曖昧なdiff_preview) — 理由 "diff_preview too vague to apply automatically" で失敗、
failed[]に追加。
アクション: merge
- すべての
filesを読み込み、targetを読み込み(存在する場合) - マージされたコンテンツを構築: 既存のtarget + sourceファイルからのセクション
targetに書き込み- 各sourceについて:
mv "$MEMORY_DIR_BASH/<source>" "$MEMORY_DIR_BASH/TRASH/"
アクション: delete
- full auto (
auto_level: "full") の場合 — 最初にpre-deleteバックアップ:cptargetをTRASH/<target>.pre-delete-<date>.mdに(Phase 3参照) - Edit target でコンテンツをクリア(フロントマターにメモ
<!-- deleted YYYY-MM-DD by wake -->を残す)
アクション: soft_delete
files内の各fileについて:mv "$MEMORY_DIR_BASH/<file>" "$MEMORY_DIR_BASH/TRASH/"
アクション: create_new
targetにフロントマター(proposalのtype)+content_templateを書き込み
アクション: extract(ノート)
- cwdから
source_noteを読み込み target_memory_fileを読み込み(存在する場合)またはフロントマターで準備- Edit/Write target に
what_to_extractセクションを追加 - ソースアクション:
keep— sourceに対して何もしないdelete—mv "$CWD_BASH/<source_note>" "$CWD_BASH/_archive/dream-applied-$REPORT_DATE/"move-to-archive— 同上、mv
アクション: remove_links
MEMORY.mdを読み込みlinks_to_remove内の各部分文字列について:- 部分文字列が出現する行を見つける
- 行全体を削除(部分文字列だけでなく)
- Edit
MEMORY.mdで更新されたコンテンツを書き込み - 部分文字列がいずれの行にも見つからない場合 → log warning "link not found in MEMORY.md: "、続行(アイテム全体を失敗させない)
アクション: shorten_lines
MEMORY.mdを読み込みlines_to_shorten内の各オブジェクト{original, replacement}について:- 部分文字列チェック:
originalがコンテンツ内にあること - Edit:
original→replacementに置換
- 部分文字列チェック:
- originalが見つからない場合 → warning、そのアイテムをスキップ、続行
アクション: add_links
MEMORY.mdを読み込みlinks_to_add内の各{section, line}について:section(正確なmarkdown見出し)を含む行を見つける- 見出し行の直後に
lineを挿入
- Edit
MEMORY.md sectionが見つからない場合 → warning、スキップ、続行
アクション: promote_skill(satori)
- ソース:
~/.claude/satori/staging/<name>/SKILL.md。ない場合 → failed。 - 宛先: proposalの
dest_dir;なければループDBのpinned_project(python -c読み取り専用:SELECT pinned_project FROM skill_usage WHERE name=?in~/.claude/satori/state.db)→<pinned>/.claude/skills/<name>/;なければ~/.claude/skills/<name>/。 - 既存のdest →
~/.claude/satori/backups/にバックアップ(cp)、その後cp source → dest。スキルはClaude Codeの再起動後にアクティブ。
アクション: retire_skill(satori)
mv ~/.claude/satori/staging/<name> ~/.claude/satori/archive/<name>-<date>(mkdir -p archive)。Sourceがない場合 → warning、スキップ、続行。
アクション: purge_trash(第二レベルのゴミ箱)
files内の各fileについて:- ソース:
$MEMORY_DIR_BASH/TRASH/<file> - 宛先:
$CWD_BASH/_archive/trash-purged-$REPORT_DATE/<file> - ソースが存在しない場合(ユーザーが手動で削除済み)→ warning、スキップ、続行
- 宛先に同じ名前のファイルがすでにある場合 → 宛先を
<file>.<unix_ts>にリネーム(上書きを防ぐ) mv "$MEMORY_DIR_BASH/TRASH/<file>" "$CWD_BASH/_archive/trash-purged-$REPORT_DATE/<file>"
- ソース:
- 決して
rmしない — ここでも。ファイルは_archive/に移動されるだけ
アクション: add_frontmatter_field
- 提案から
target_file、field_name、field_valueを読み取る target_fileを読み込み- 既存のフロントマター内に
field_nameが存在するか確認:- 存在する場合 → 値を
field_valueで上書き - 存在しない場合 → フロントマターの最後(本文区切りの前)に
field_name: field_valueを追加
- 存在する場合 → 値を
- Edit
target_fileでフロントマターを更新
Phase 5 — Wakeログを書き、レポートをアーカイブし、ロックを解除
Wakeログ: 適用の詳細を _archive/wake-log-$REPORT_DATE.md に書き込む(追記、すでにログがある場合は上書きしないように新しいエントリを追加):
# Wake Log — `<date>` `<time>`
- Report: DREAM-REPORT-<date>.md
- Choices source: DREAM-CHOICES-<date>.json (auto: false)
- Snapshot: _archive/wake-backup-<date>-<time>/
- Gate: manual (user confirm)
## Applied
| ID | Category | Action | Target | Result |
|----|----------|--------|--------|--------|
| M1 | memory | merge | project_pikabu_mcp.md | ✅ |
| N2 | notes | extract | reference_runware_audit_2026_04.md | ✅ |
## Failed
| ID | Category | Action | Reason |
|----|----------|--------|--------|
| M2 | memory | update | target_content_drifted: '## Session 04-01' not found |
## Skipped
| ID | Category | Action | Reason |
|----|----------|--------|--------|
| M3 | memory | soft_delete | not in selected |
## Projects accessed (global mode)
| Slug | Memory Dir | Status |
|------|------------|--------|
| current | ~/.claude/projects/<slug>/memory | ✅ |
| other-proj | ~/.claude/projects/other-proj/memory | ✅ |
## Promoted skills
- <name> → <dest> (if any)
アーカイブ:
mv "$LATEST_REPORT" "$CWD_BASH/_archive/dream-applied-$REPORT_DATE/"
if [ -n "$CHOICES" ]; then
mv "$CHOICES" "$CWD_BASH/_archive/dream-applied-$REPORT_DATE/"
fi
HTMLレポート(*.html)は削除しない — ユーザーが自分で確認したかった場合に備えて保持(追跡可能性)。
ロック解除:
rm -rf "$LOCK_DIR"
Phase 6 — 最終サマリー
チャットに表示:
✅ Wake完了。
<date><time>適用: N | 失敗: M | スキップ: K
スナップショット:
_archive/wake-backup-<date>-<time>/ログ:
_archive/wake-log-<date>.md❓ 何か問題が発生しましたか?
wake rollback <date>で元に戻せます。
ロールバック
wake rollback — ワンコマンドで任意のwakeを元に戻す:
wake rollback [date]
- 最新のwakeスナップショットを検索(
[date]が指定された場合はその日付、なければ_archive/wake-backup-*から最新) - スナップショットディレクトリのすべてのファイルを
$MEMORY_DIR_BASH/にコピーして戻す(本当に戻す!) - TRASHから復元するファイルを拒否しない(メモリディレクトリをスナップショットの状態に完全に戻す)
- 適用されたレポートと選択JSONを
_archive/dream-applied-<date>/から$CWD_BASH/に戻さない — レポートはアーカイブに残る - Wakeログに追記:
Rolled back to snapshot <path> (<date> <time>) - チャットに表示:
⏪ Wakeロールバック完了 —
<date><time>メモリディレクトリをスナップショット
<path>に復元しましたログ:
_archive/wake-log-<date>.md
競合解決: 復元中に $MEMORY_DIR_BASH/ にファイルが既に存在する場合:
- スナップショットにそのファイルがある場合 → スナップショットのバージョンで上書き(cp -rはデフォルトでこれを行う)
- スナップショットにないが
$MEMORY_DIR_BASH/にある場合 → ロールバック後にファイルは残る(wake以降に作成されたもの) —warn: file <path> not in snapshot — not rolled backをログに記録
ロールバックのロック: ロールバック自体のロックは不要(レポートを読み書きしない)。ただし、現在の $MEMORY_DIR_BASH/ の競合を避けるため、実行中のwakeがある間はロールバックできない:
LOCK_DIR="$CWD_BASH/.wake-lock"
if [ -d "$LOCK_DIR" ]; then
echo "⚠️ Wakeが現在実行中 — rollbackが競合する可能性があります。キャンセルするには 'stop' と入力。'continue' で強行(非推奨)"
# Read user input — force continue or abort
fi
# Continue with rollback
決して rm しない — なぜか
delete→ ファイルはTRASHに移動、クリアされたバージョンが残るsoft_delete→ TRASHに移動purge_trash→_archive/trash-purged-<date>/に移動
唯一の例外はlock cleanup: rm -rf "$LOCK_DIR" — これはプロセス間調整用の一時的な空ディレクトリであり、ユーザーデータではない。
OS互換性
| 環境 | パス形式 | 確認 |
|---|---|---|
| Linux / macOS | /home/user/.claude/... | ネイティブ動作 |
| Windows (Git Bash) | /d/Projects/... → Win D:/Projects/... | cygpath -w で変換、Git Bashで完全テスト済み |
| Windows (WSL) | /mnt/d/Projects/... | ネイティブ動作、Windowsパス変換なし |
| macOS (zsh) | /Users/user/... | ネイティブ動作 |
Windows固有の注意点
pwd -WはGit Bashでのみ利用可能(MSYS2)— 非Windowsではfailしないようにフォールバックstat -c %YはGit Bashで利用可能、macOSではstat -f %m- パス計算のsed正規表現はいずれの環境でも動作:
sed 's|[:/]|-|g'
Wakeのトリガーと引数
apply dream:wakeのエイリアス(skill handler経由)wake: JSONを探し、なければユーザーにIDを尋ねるwake all: レポート内のすべての提案を適用(確認あり)wake <IDリスト>: カンマ区切りIDリスト(例wake M1,M3,N2)wake rollback [date]: 最新(または指定日付)のスナップショットに戻す