- 作成日: 2026-02-18
- 対象リポジトリ:
/Users/berlysia/.local/share/chezmoi - 目的: 「Research → Plan → Annotation → Todo → Implement」の運用を、Claude Codeの内蔵 Plan Mode に依存せず、Skill + Hooks +
CLAUDE.mdで強制・支援できる体制にする
現在の運用は「Plan Mode + ExitPlanMode + validate-plan-guard」に強く寄っている。
一方で、目指す体制は以下:
- 調査と計画をファイル (
.tmp/research.md,.tmp/plan.md) に固定する - 人間の注釈反復を中心に据える
- 計画承認前の実装着手を hook で技術的に防ぐ
- 実装進捗を
plan.mdの TODO で追う
このため、Plan Mode を前提にしたガードではなく、文書状態に基づくガードへ移行する。
以下をすべて満たしたら完了:
- 複雑タスクで
.tmp/research.mdと.tmp/plan.mdが自動的に生成される(Skill) -
.tmp/plan.md未承認状態でWrite/Edit/MultiEdit/NotebookEdit/Bashの実装系書き込みが拒否される(Hook) - 未承認状態でも
.tmp/research.mdと.tmp/plan.mdの更新は許可される - 承認後は通常どおり実装可能
-
CLAUDE.mdが「Plan Mode必須」ではなく「Document Workflow必須」を定義 - hook の unit test が追加され、既存テストとともに通る
- 既存の
validate-plan-guard.tsとの共存期間を経て、前提を Document Workflow 側へ段階移行できる
- Plan Mode 機能そのものの削除
- 全タスクへの一律強制(小タスクは直接実行を許可)
- 既存 permission-auto-approve の大規模改修
- Skill レイヤー: ワークフロー起動・文書生成・反復プロンプトの定型化
- Hook レイヤー: 計画承認前の実装を拒否する実行ガード
CLAUDE.mdレイヤー: ルーティング規則と運用手順の明文化
.tmp/research.md: 調査成果(実装前).tmp/plan.md: 実装計画 + 注釈反映 + TODO.tmp/workflow-state.json: 現在フェーズ(任意、導入推奨)
workflow-state.json は最小構成でも可:
{
"mode": "document-workflow",
"phase": "planning",
"researchPath": ".tmp/research.md",
"planPath": ".tmp/plan.md",
"approved": false
}| 区分 | 追加/変更 | ファイル |
|---|---|---|
| Skill | 追加 | .skills/annotated-plan-workflow/SKILL.md |
| Skill補助 | 追加 | .skills/annotated-plan-workflow/references/templates.md |
| Hook | 追加 | home/dot_claude/hooks/implementations/document-workflow-guard.ts |
| Hook test | 追加 | home/dot_claude/hooks/tests/unit/document-workflow-guard.test.ts |
| Hook設定 | 変更 | home/dot_claude/.settings.hooks.json.tmpl |
| 運用規約 | 変更 | home/dot_claude/CLAUDE.md |
| 任意同期 | 変更(任意) | CLAUDE.md(リポジトリローカル説明の更新) |
-
plan.mdの運用規約を合意 - 承認判定方式を
## Approval/- Status: approvedに固定 - 対象スコープ判定を統一(3ステップ以上、または設計判断を含む)
- 小タスク除外条件を確定(例: 1-2ファイル修正は直接実行)
決定事項(推奨):
- 承認判定は
plan.md内## ApprovalセクションのStatus: approved - Hook は
.tmp/plan.mdが存在しない場合、まずはwarn-onlyで開始
- 新規 Skill
annotated-plan-workflow作成 -
/research相当の調査テンプレート定義 -
/plan相当の計画テンプレート定義 - 注釈反映ループの定型文 (
don’t implement yet) を固定 - 実装開始テンプレート(TODO更新強制)を固定
成果物:
.skills/annotated-plan-workflow/SKILL.md.skills/annotated-plan-workflow/references/templates.md
-
document-workflow-guard.tsを追加 - 対象ツール判定:
Write,Edit,MultiEdit,NotebookEdit,Bash -
.tmp/plan.md承認前はコードファイル編集を deny -
.tmp/research.md,.tmp/plan.mdへの編集は常に許可(絶対/相対パス両対応) -
Bashの書き込み系コマンド(redirection,tee,sed -i等)を承認前 deny -
warn-onlyモードを環境変数で切替可能にする -
warn-only時も「本来はdenyだった操作」をログ出力できるようにする - unit test を追加
成果物:
home/dot_claude/hooks/implementations/document-workflow-guard.tshome/dot_claude/hooks/tests/unit/document-workflow-guard.test.ts
-
home/dot_claude/.settings.hooks.json.tmplに新 hook を追加 - 実行順序を確認(auto-approve より前で拒否するか後で拒否するか)
-
chezmoi apply後の反映動作を確認
-
home/dot_claude/CLAUDE.mdの Task Intake Routing を更新 - 「Plan Mode 必須」文言を「Document Workflow 必須」へ置換
- 実装着手条件を
.tmp/research.md + approved .tmp/plan.mdに変更 - 運用例(標準プロンプト)を追記
-
npm run test実行 -
npm run typecheck実行 - 手動E2E(後述シナリオ)実施
- 1週間は
warn-onlyで運用し、「would-block」ログを集計するための設定と観測手順を実装 - 問題なければ enforce モードへ移行できる構成にした(hook command から env を外すだけ)
Plan Mode 非依存の Document Workflow 体制を、Skill + Hook + CLAUDE.md の3層で実装し、実運用可能な品質で導入する。
- DW-01 → DW-02, DW-03
- DW-04 → DW-05, DW-06, DW-07
- DW-07 → DW-08, DW-09, DW-10
- DW-08, DW-09, DW-10 → DW-13
- DW-11, DW-12 → DW-13
- DW-13 → DW-14 → DW-15
- Total todos: 15
- Complexity: High
-
DW-01 Create Skill Scaffold
- What: Skillディレクトリとファイル骨格を作成する。
- Where:
.skills/annotated-plan-workflow/SKILL.md,.skills/annotated-plan-workflow/references/templates.md - How:
.skills/approach-check/SKILL.mdのfrontmatter/構成を踏襲して新規作成する。 - Why: ワークフローの入口(共通手順)を固定化するため。
- Verify:
rg --files .skills | rg 'annotated-plan-workflow/(SKILL.md|references/templates.md)'
-
DW-02 Implement Skill Workflow Contract
- What:
SKILL.mdに trigger/rules/workflow を実装する。 - Where:
.skills/annotated-plan-workflow/SKILL.md - How: 対象条件を「3ステップ以上 or 設計判断あり」に固定し、
don’t implement yetをハードルール化する。 - Why: 運用判定の揺れを防ぎ、計画承認前実装を抑止するため。
- Verify:
rg -n '3\\+ steps|don.t implement yet|single source of truth' .skills/annotated-plan-workflow/SKILL.md
- What:
-
DW-03 Add Prompt Templates
- What: 調査・計画・注釈反映・TODO追加・実装開始のテンプレートを追加する。
- Where:
.skills/annotated-plan-workflow/references/templates.md - How:
.tmp/research.md/.tmp/plan.mdを前提に、各テンプレートへ期待成果と禁止事項を明記する。 - Why: セッション間でも一貫したプロンプト品質を維持するため。
- Verify:
rg -n '\\.tmp/research.md|\\.tmp/plan.md|don.t implement yet' .skills/annotated-plan-workflow/references/templates.md
-
DW-04 Create Hook Skeleton
- What: Document Workflow ガードhookの骨格を実装する。
- Where:
home/dot_claude/hooks/implementations/document-workflow-guard.ts - How:
defineHook({ trigger: { PreToolUse: true } })で開始し、tool名分岐を作る。 - Why: 実装着手制御を技術的に実現する基盤が必要なため。
- Verify:
node --check home/dot_claude/hooks/implementations/document-workflow-guard.ts
-
DW-05 Implement Path Normalization & Doc Allowlist
- What:
.tmp/plan.md/.tmp/research.mdの編集だけは常時許可する判定を実装する。 - Where:
home/dot_claude/hooks/implementations/document-workflow-guard.ts - How: 絶対/相対/
.//~/をresolve + normalizeで正規化して比較する。 - Why: 計画更新そのものがブロックされる自己矛盾を防ぐため。
- Verify: unit test で
./.tmp/plan.mdと絶対パス編集がsuccessになること。
- What:
-
DW-06 Implement Approval Gate
- What:
Status: approvedとresearch.md存在の二条件を実装前提にする。 - Where:
home/dot_claude/hooks/implementations/document-workflow-guard.ts - How:
.tmp/plan.md読み取り + 正規表現判定、.tmp/research.mdの存在確認を組み合わせる。 - Why: 調査と計画承認の両方を満たしたときのみ実装に進めるため。
- Verify: pending時
deny、approved時successのテストを追加して通す。
- What:
-
DW-07 Implement Bash Write Detection + Warn Mode
- What:
Bashの書き込み系コマンド検出とwarn-only観測ログ出力を実装する。 - Where:
home/dot_claude/hooks/implementations/document-workflow-guard.ts - How:
>,tee,sed -i,cp/mv/touch/mkdir/rmなどのパターンで write-like 判定し、DOCUMENT_WORKFLOW_WARN_ONLY=1ならwould-blockをstderr出力する。 - Why:
Write/Edit制限だけでは防げない Bash バイパスを潰すため。 - Verify:
Bash("echo hi > src/a.ts")が pending でdeny、warn-only でsuccess + would-blockになること。
- What:
-
DW-08 Register Hook in Settings Template
- What: 新hookを
PreToolUseに接続する。 - Where:
home/dot_claude/.settings.hooks.json.tmpl - How: matcher を
Write|Edit|MultiEdit|NotebookEdit|Bashに設定し、document-workflow-guard.tsを呼ぶエントリを追加する。 - Why: 実行時にhookが発火しないとガードが機能しないため。
- Verify:
chezmoi execute-template < home/dot_claude/.settings.hooks.json.tmpl | jq empty
- What: 新hookを
-
DW-09 Add Core Unit Tests
- What: 基本4ケース(pending deny / approved allow / doc編集allow / Bash deny)を追加する。
- Where:
home/dot_claude/hooks/tests/unit/document-workflow-guard.test.ts - How:
home/dot_claude/hooks/tests/unit/block-tsx.test.tsの構成を参考にtest-helpers.tsを使って実装する。 - Why: ガードの基礎挙動を回帰から守るため。
- Verify:
node --test home/dot_claude/hooks/tests/unit/document-workflow-guard.test.ts
-
DW-10 Add Edge Case Unit Tests
- What: warn-only / Bash read-only pass /
.tmp絶対パス許可の境界ケースを追加する。 - Where:
home/dot_claude/hooks/tests/unit/document-workflow-guard.test.ts - How:
process.env.DOCUMENT_WORKFLOW_WARN_ONLYのセット/復元、process.chdirの復元をafterEachで管理する。 - Why: 実運用で最も壊れやすい境界条件を固定するため。
- Verify: 追加ケースを含めて同テストファイルが全件 pass。
- What: warn-only / Bash read-only pass /
-
DW-11 Update Global CLAUDE Workflow Rules
- What:
Document Workflow前提の運用規則へ更新する。 - Where:
home/dot_claude/CLAUDE.md - How: Task Intake Routing/実装着手条件を
.tmp/research.md+ approved.tmp/plan.mdベースに置換し、don’t implement yetを明記する。 - Why: Skill/Hook と運用文書の不整合をなくすため。
- Verify:
rg -n 'Document Workflow|\\.tmp/research.md|\\.tmp/plan.md|don.t implement yet' home/dot_claude/CLAUDE.md
- What:
-
DW-12 Update Repo-local Context Doc
- What: リポジトリ側
CLAUDE.mdに新運用への参照を追記する。 - Where:
CLAUDE.md - How: 「Plan Mode依存ではなく document-workflow guard で制御」の方針を短く追記する。
- Why: プロジェクト文脈を読むだけで運用前提が伝わる状態にするため。
- Verify:
rg -n 'document-workflow|annotated-plan' CLAUDE.md
- What: リポジトリ側
-
DW-13 Run Automated Verification
- What: テストと型チェックを実行する。
- Where: リポジトリルート
package.jsonscripts - How: まず対象テストを個別実行し、その後
npm run testとnpm run typecheckを走らせる。 - Why: 変更の副作用を早期検出するため。
- Verify:
node --test home/dot_claude/hooks/tests/unit/document-workflow-guard.test.ts
npm run test
npm run typecheck
すべて終了コード0。 - Result Note:
npm run testは現行スクリプト定義上 0件実行(終了コード0)。 追加確認としてnode --test home/dot_claude/hooks/tests/unit/*.test.tsを実行し、claude-companion-detector系で既存起因の失敗2件を確認。 本変更対象のdocument-workflow-guardテストは全件 pass。
-
DW-14 Execute Manual E2E Scenarios
- What: Scenario A-E を手動で検証する。
- Where: 本
plan.mdの「手動E2Eシナリオ」節 - How: pending/approved/warn-only の状態を切り替えながら
Write/Edit/Bashを順に試す。 - Why: 単体テストで拾えない実行順・設定連携の問題を検出するため。
- Verify: A-E の期待結果がすべて再現できることを記録する。
-
DW-15 Rollout in Warn-only Then Enforce
- What: 段階導入を行い、一定期間の観測後に enforce へ移行する。
- Where: 実運用環境の設定(
DOCUMENT_WORKFLOW_WARN_ONLY)と logs - How: 1週間 warn-only で運用し、
would-block件数と誤検知パターンを集計して閾値を満たせば enforce へ切り替える。 - Why: 開発停止リスクを抑えつつ安全に移行するため。
- Verify: 観測レポートに「誤検知率」「主要誤検知コマンド」「移行可否判断」を残す。
ファイル: .skills/annotated-plan-workflow/SKILL.md
---
name: annotated-plan-workflow
description: "Run research -> plan -> annotation loop -> todo -> implementation without Plan Mode."
context: inherit
---
# Annotated Plan Workflow
## Trigger
- User asks for tasks with 3+ steps or design decisions
- User explicitly asks to avoid built-in Plan Mode
## Workflow
1. Read target area deeply and write `.tmp/research.md`
2. Draft `.tmp/plan.md` with files, approach, trade-offs, snippets
3. Ask user to annotate plan directly
4. Reflect all notes, repeat until approved
5. Add granular TODO checklist
6. Implement all tasks and mark completion in `.tmp/plan.md`
## Hard Rules
- Never implement before plan approval
- Always use explicit guard phrase: `don’t implement yet`
- Keep `.tmp/plan.md` as single source of truth for progressファイル: home/dot_claude/hooks/implementations/document-workflow-guard.ts
#!/usr/bin/env -S bun run --silent
import { existsSync, readFileSync } from "node:fs";
import { join, normalize, resolve } from "node:path";
import { defineHook } from "cc-hooks-ts";
import { createDenyResponse } from "../lib/context-helpers.ts";
const PLAN_PATH = ".tmp/plan.md";
const RESEARCH_PATH = ".tmp/research.md";
const APPROVAL_REGEX = /^- Status:\s*approved\s*$/m;
const GUARDED_TOOLS = new Set([
"Write",
"Edit",
"MultiEdit",
"NotebookEdit",
"Bash",
]);
const BASH_WRITE_PATTERNS = [
/>/,
/\btee\b/,
/\bsed\s+-i\b/,
/\bperl\s+-i\b/,
/\bcp\b/,
/\bmv\b/,
/\btouch\b/,
/\bmkdir\b/,
/\brm\b/,
];
function normPath(cwd: string, p: string): string {
const expanded = p.startsWith("~/")
? `${process.env.HOME || ""}/${p.slice(2)}`
: p;
return normalize(resolve(cwd, expanded));
}
function isDocPath(cwd: string, p: string): boolean {
const target = normPath(cwd, p);
return (
target === normPath(cwd, ".tmp/plan.md") ||
target === normPath(cwd, "./.tmp/plan.md") ||
target === normPath(cwd, ".tmp/research.md") ||
target === normPath(cwd, "./.tmp/research.md")
);
}
function hasApprovedPlan(cwd: string): boolean {
const full = join(cwd, PLAN_PATH);
if (!existsSync(full)) return false;
const text = readFileSync(full, "utf-8");
return APPROVAL_REGEX.test(text);
}
function hasResearchArtifact(cwd: string): boolean {
return existsSync(join(cwd, RESEARCH_PATH));
}
function isBashWriteLike(command: string): boolean {
return BASH_WRITE_PATTERNS.some((p) => p.test(command));
}
const hook = defineHook({
trigger: { PreToolUse: true },
run: (context) => {
const { tool_name, tool_input } = context.input;
if (!GUARDED_TOOLS.has(tool_name)) return context.success({});
const cwd = process.cwd();
const approved = hasApprovedPlan(cwd);
const researched = hasResearchArtifact(cwd);
const warnOnly = process.env.DOCUMENT_WORKFLOW_WARN_ONLY === "1";
const denyReason =
"Document workflow gate: implementation is blocked until `.tmp/research.md` exists and `.tmp/plan.md` has `- Status: approved`.";
if (tool_name === "Bash") {
const command = String(tool_input.command || "");
if (!isBashWriteLike(command)) return context.success({});
if (approved && researched) return context.success({});
if (warnOnly) {
console.error(
`[document-workflow-guard][would-block] Bash: ${command}`,
);
return context.success({});
}
return context.json(createDenyResponse(denyReason));
}
const targetPath = String(
tool_input.file_path || tool_input.notebook_path || "",
);
if (!targetPath) return context.success({});
if (isDocPath(cwd, targetPath)) return context.success({});
if (approved && researched) return context.success({});
if (warnOnly) {
console.error(
`[document-workflow-guard][would-block] ${tool_name}: ${targetPath}`,
);
return context.success({});
}
return context.json(createDenyResponse(denyReason));
},
});
export default hook;
if (import.meta.main) {
const { runHook } = await import("cc-hooks-ts");
await runHook(hook);
}ファイル: home/dot_claude/.settings.hooks.json.tmpl
{
"PreToolUse": [
{
"matcher": "Write|Edit|MultiEdit|NotebookEdit|Bash",
"hooks": [
{
"type": "command",
"command": "bun {{ .chezmoi.homeDir }}/.claude/hooks/implementations/document-workflow-guard.ts"
}
]
}
]
}既存 PreToolUse ブロックへ追加する形で統合する。
順序は「早く拒否したいなら auto-approve より前」「ログ収集優先なら後」で決める。
ファイル: home/dot_claude/CLAUDE.md
## Document Workflow (Plan Mode 非依存)
中規模以上(3ステップ以上、または設計判断を含む)のタスクは、以下を必須とする:
1. `.tmp/research.md` を作成し、対象コードを深く調査する
2. `.tmp/plan.md` を作成し、変更方針・対象ファイル・TODOを明記する
3. ユーザー注釈を反映する反復を行う(実装禁止)
4. `## Approval` セクションで `Status: approved` が付与されてから実装する
### Guard Phrase
- 計画反復中は必ず `don’t implement yet` を明示する
### 実装開始条件
- `.tmp/research.md` が存在する
- `.tmp/plan.md` が承認済みである
- TODOが作成済みであるファイル: home/dot_claude/hooks/tests/unit/document-workflow-guard.test.ts
#!/usr/bin/env node --test
import { describe, it } from "node:test";
import { afterEach } from "node:test";
import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import hook from "../../implementations/document-workflow-guard.ts";
import { createPreToolUseContext, invokeRun } from "./test-helpers.ts";
describe("document-workflow-guard", () => {
const originalCwd = process.cwd();
const originalWarnOnly = process.env.DOCUMENT_WORKFLOW_WARN_ONLY;
afterEach(() => {
process.chdir(originalCwd);
if (originalWarnOnly === undefined) {
delete process.env.DOCUMENT_WORKFLOW_WARN_ONLY;
} else {
process.env.DOCUMENT_WORKFLOW_WARN_ONLY = originalWarnOnly;
}
});
it("blocks Write when plan is not approved", async () => {
const repo = mkdtempSync(join(tmpdir(), "dwg-"));
mkdirSync(join(repo, ".tmp"), { recursive: true });
writeFileSync(join(repo, ".tmp/research.md"), "ok");
writeFileSync(join(repo, ".tmp/plan.md"), "- Status: pending");
process.chdir(repo);
const context = createPreToolUseContext("Write", { file_path: "src/a.ts" });
await invokeRun(hook, context);
context.assertDeny();
});
it("allows editing .tmp/plan.md while pending", async () => {
const repo = mkdtempSync(join(tmpdir(), "dwg-"));
mkdirSync(join(repo, ".tmp"), { recursive: true });
writeFileSync(join(repo, ".tmp/research.md"), "ok");
writeFileSync(join(repo, ".tmp/plan.md"), "- Status: pending");
process.chdir(repo);
const context = createPreToolUseContext("Edit", {
file_path: "./.tmp/plan.md",
});
await invokeRun(hook, context);
context.assertSuccess({});
});
it("allows Write after approval", async () => {
const repo = mkdtempSync(join(tmpdir(), "dwg-"));
mkdirSync(join(repo, ".tmp"), { recursive: true });
writeFileSync(join(repo, ".tmp/research.md"), "ok");
writeFileSync(join(repo, ".tmp/plan.md"), "- Status: approved");
process.chdir(repo);
const context = createPreToolUseContext("Write", { file_path: "src/a.ts" });
await invokeRun(hook, context);
context.assertSuccess({});
});
it("blocks Bash write-like commands before approval", async () => {
const repo = mkdtempSync(join(tmpdir(), "dwg-"));
mkdirSync(join(repo, ".tmp"), { recursive: true });
writeFileSync(join(repo, ".tmp/research.md"), "ok");
writeFileSync(join(repo, ".tmp/plan.md"), "- Status: pending");
process.chdir(repo);
const context = createPreToolUseContext("Bash", {
command: "echo hi > src/a.ts",
});
await invokeRun(hook, context);
context.assertDeny();
});
it("warn-only mode allows but would-block", async () => {
const repo = mkdtempSync(join(tmpdir(), "dwg-"));
mkdirSync(join(repo, ".tmp"), { recursive: true });
writeFileSync(join(repo, ".tmp/research.md"), "ok");
writeFileSync(join(repo, ".tmp/plan.md"), "- Status: pending");
process.chdir(repo);
process.env.DOCUMENT_WORKFLOW_WARN_ONLY = "1";
const context = createPreToolUseContext("Write", { file_path: "src/a.ts" });
await invokeRun(hook, context);
context.assertSuccess({});
});
});.tmp/plan.md は少なくとも以下の構造を持つ:
# Plan: <task>
## Goal
- ...
## File Changes
- path: reason
## Tasks
- [ ] Phase 1 ...
- [ ] Phase 2 ...
## Risks
- ...
## Approval
- Status: pending
- Approved by:
- Approved at:注釈反復時は ## Notes セクションを都度更新する運用とする。
.tmp/research.mdのみ作成.tmp/plan.mdはStatus: pendingWrite(src/new.ts)を試行- deny されること
.tmp/plan.mdをStatus: approvedに更新Write(src/new.ts)を試行- 許可されること
- 承認前でも
.tmp/plan.md編集を試行 - 許可されること
.tmp/plan.mdはStatus: pendingBash("echo hi > src/new.ts")またはBash("sed -i ...")を試行- deny されること
DOCUMENT_WORKFLOW_WARN_ONLY=1で起動- 未承認状態で
Write(src/new.ts)を試行 - 実行は通るが
would-blockログが残ること
- 誤ブロックで開発が止まる
- 緩和:
DOCUMENT_WORKFLOW_WARN_ONLY=1で段階導入
- 緩和:
- 小タスクに対する過剰運用
- 緩和:
CLAUDE.mdに小タスク除外条件を明記
- 緩和:
- 運用が形骸化(承認だけ付ける)
- 緩和: Skillに「TODO必須」「注釈反復回数の明示」を入れる
- Skill を先に導入して運用を開始
- Hook を warn-only で導入
CLAUDE.mdを切り替え- テストと実運用ログを確認
- enforce モードへ移行
既存には validate-plan-guard.ts(ExitPlanMode フック)が存在する。
本計画ではこれを即削除せず、共存期間を設ける。
- 旧: Plan Mode終了時の検証
- 新: 実装着手時の文書状態検証
最終的には、新ガードが安定した段階で「Plan Mode前提の強制」を弱める。