初期検討段階のドキュメントと運用ルールを取り込む

ルール策定前に書かれた設計ドキュメント7点を、以降の基準点として
そのままの状態で記録する。CLAUDE.md のルール1〜4は未適用。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdLT5x5ka5y6CaWLeE3EKb
This commit is contained in:
2026-09-18 08:43:01 +09:00
co-authored by Claude Opus 5
commit 55243f626d
12 changed files with 2395 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
---
name: decide
description: 設計上の決定を記録する。メインドキュメントの更新・検討済み案の記録・journal作成・コミットを一度に行う。
disable-model-invocation: true
argument-hint: [決定の要旨]
---
# 決定を記録する
CLAUDE.md のルール1〜4に従い、決定を4箇所に分散して記録する。
どれか1つでも欠けると後から追えなくなるので、全部やる。
## 手順
### 0. 決定内容を確認する
以下が揃っているか確認し、足りなければユーザーに聞く。推測で埋めない。
- 採用した案は何か
- それによって**今後効く制約**は何か(却下理由ではなく、現在の状態として書ける文)
- 落とした案があるか
- この決定が未確認の推測に依存していないか
### 1. メインドキュメントを更新する
該当する直下の `*.md` を更新する。
- 採用した案・現在の設定を書く。「なぜAなのか」は1〜3行まで。
- 「なぜBではないのか」は書かない。代わりに**制約文**として書く。
- ✗ `B案はKLEパーサーが壊れるので却下した`
- ○ `クラスター情報はKLE JSONに出力しない(KLE互換性の制約)`
- 比較表・検討過程・ベンチマーク結果をここに書かない。
### 2. 落とした案を1行残す
メインドキュメント末尾の `## 検討済み・非採用` に追記する。
```
- labels[9] へのID埋め込み (journal/2026-09-16-cluster-metadata.md)
```
- 理由は書かない。journalファイル名だけ。
- セクションが5行を超えたら、古いものから落とす。長くなるのは書きすぎのサイン。
### 3. journal を書く
`journal/YYYY-MM-DD-<topic>.md` を作る。トピックを詰め込まない(1決定1ファイル)。
構成は上から順に:
1. **結論** — 何を採用したか
2. **案の比較** — 簡潔な表。メリット/デメリットを1行ずつ
3. 検証ログ・根拠・捨てた案の詳細
冒頭20行で結論と選択肢が分かること。整形は最低限でよい。
他ファイルへのリンクも、他ファイルからのリンクも張らない。
### 4. 推測を扱う
この決定が未確認の推測に依存する場合:
- メインドキュメント本文に `[推測:P-n]` を付ける
- 末尾の `## 未確認事項` に項目を足し、**何を確認できれば事実に格上げできるか**を書く
逆に、この決定で検証が済んだ推測があれば、タグを外して `## 未確認事項` から削除する。
本文タグと末尾項目は必ず対で操作する(片方だけ残さない)。
### 5. コミットする
メインドキュメントと journal を**同じコミット**に入れる。1コミット1決定。
メッセージ:
```
<何を変えたか(1行)>
<なぜそうしたか 数行>
Journal: journal/2026-09-16-cluster-metadata.md
```
「なぜ」の詳細はコミットメッセージに書ききらない。journal 側にある。
## 注意
- 決定が固まっていない段階では使わない。調査中は journal だけ書けばよい。
- 複数の決定をまとめてコミットしない。分ける。
+64
View File
@@ -0,0 +1,64 @@
---
name: doc-lint
description: ドキュメントがCLAUDE.mdの運用ルール1〜4に沿っているか検査する。推測タグと未確認事項の対応漏れ、journalへのリンク、比較表の混入などを検出する。
argument-hint: [ファイル...]
---
# ドキュメントのルール違反を検査する
機械的に判定できる項目はスクリプトで検出し、判断が要る項目だけ目視で見る。
**検出して報告するだけで、勝手に直さない。** 修正は内容を確認してもらってから。
## 1. スクリプトを走らせる
```
python .claude/skills/doc-lint/doc-lint.py [ファイル...]
```
引数なしで直下の `*.md` と `journal/*.md` を全部見る。ERROR があれば終了コード 1。
検出する項目:
| 種別 | 内容 |
|---|---|
| ERROR | 本文の `[推測:P-n]` に対応する `## 未確認事項` の項目がない |
| ERROR | `## 未確認事項` の項目に `- 格上げ条件:` の行がない |
| ERROR | `## 未確認事項` のIDが重複している |
| ERROR | journal へのリンク / journal から他ファイルへのリンク |
| ERROR | `## 検討済み・非採用` の項目が journal ファイル名を参照していない |
| ERROR | journal のファイル名が `YYYY-MM-DD-<topic>.md` 形式でない |
| WARN | `## 検討済み・非採用` が5件を超えた |
| WARN | journal の冒頭20行に結論が見当たらない |
| WARN | メインドキュメントに比較表・優先度評価・却下案の記述らしきもの |
**本文タグのない `## 未確認事項` の項目はエラーにしない。** 「まだ本文に書けない未解決の問い」として正当。
WARN の散文検出は当たりが粗い。取りこぼしも誤検出もあるので、出た/出ないを結論にしない。
## 2. スクリプトで見られない項目を確認する
こちらが本体。ファイルを読んで判断する。
- **「なぜBではないのか」が理由として残っていないか。**
却下理由ではなく、現在効いている制約として書かれているか。
- ✗ `階層構造はKLEが非対応なので諦めた`
- ○ `階層クラスターはKLE JSONに出力しない(KLE互換性の制約)`
- **裏取りしていない記述にタグが付いていないか。** 検査項目の中で最も重要で、
機械では判定できない。「〜できるはず」「〜と考えられる」「実現可能」で実際に試していない
記述を探し、`[推測:P-n]` が付いているか確認する。
- **同じ事実が複数ドキュメントに複製されていないか。** 片方だけ古くなる。
- **journal の冒頭20行で、結論と選択肢が分かるか。** 見出しの有無ではなく中身を読む。
- **コミット規約**(必要なら `git log --oneline` を見る)
- 1コミット1決定になっているか
- journal を伴う決定のメッセージに `Journal:` 行があるか
## 3. 報告する
ERROR / WARN / 目視で見つけたもの を分けて出す。
修正案は出してよいが、適用は確認を取ってから。事実関係の誤りを見つけた場合も、直さず指摘する。
## 注意
- 運用ルール策定前に書かれた既存ドキュメントは、まだルール1〜4が適用されていない。
WARN が大量に出るのは想定内で、再編(`/restructure-doc`)のタイミングで解消する。
再編前のファイルの WARN を個別に直そうとしない。
+157
View File
@@ -0,0 +1,157 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""CLAUDE.md の運用ルール1〜4のうち、機械的に判定できる項目を検査する。
使い方:
python .claude/skills/doc-lint/doc-lint.py [ファイル...]
引数なしなら リポジトリ直下の *.md と journal/*.md を対象にする。
ERROR が1件でもあれば終了コード 1。
"""
import sys, re, os, io, glob
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")
TAG = re.compile(r"\[推測:(P-\d+)\]")
ENTRY = re.compile(r"^###\s+(P-\d+)\b")
H2 = re.compile(r"^##\s+(.*?)\s*$")
MDLINK = re.compile(r"\]\(([^)]+\.md[^)]*)\)")
JOURNALFN= re.compile(r"^\d{4}-\d{2}-\d{2}-.+\.md$")
PROMOTE = re.compile(r"^\s*[-*]\s*格上げ条件\s*[::]\s*\S")
# ルールブック自身は、アンチパターンを例として引用するため散文ヒューリスティックの対象外
PROSE_EXEMPT = {"CLAUDE.md"}
issues = []
def add(sev, path, line, msg):
issues.append((sev, path, line, msg))
def load(path):
"""コードフェンス内を空行に潰した (行番号, 本文) の列を返す。"""
raw = io.open(path, encoding="utf-8").read().splitlines()
out, fence = [], False
for i, l in enumerate(raw, 1):
if l.lstrip().startswith("```"):
fence = not fence
out.append((i, ""))
continue
out.append((i, "" if fence else l))
return out
def section(lines, title):
"""## <title> 配下の (行番号, 本文) を返す。無ければ None。"""
start = None
for idx, (n, l) in enumerate(lines):
m = H2.match(l)
if m and m.group(1) == title:
start = idx + 1
break
if start is None:
return None
body = []
for n, l in lines[start:]:
if H2.match(l):
break
body.append((n, l))
return body
def check(path):
rel = path.replace(os.sep, "/")
base = os.path.basename(rel)
is_journal = rel.startswith("journal/")
lines = load(path)
unconf = section(lines, "未確認事項")
rejected = section(lines, "検討済み・非採用")
unconf_span = set(n for n, _ in unconf) if unconf else set()
# --- ルール4: 推測タグと未確認事項の対応 ---
entries = set()
if unconf is not None:
blocks = [] # [(pid, 行番号, [本文行])] 重複IDも個別に検査する
for n, l in unconf:
m = ENTRY.match(l)
if m:
pid = m.group(1)
if pid in entries:
add("ERROR", rel, n, "未確認事項のID %s が重複している" % pid)
entries.add(pid)
blocks.append((pid, n, []))
elif blocks:
blocks[-1][2].append(l)
for pid, ln, body in blocks:
if not any(PROMOTE.match(x) for x in body):
add("ERROR", rel, ln,
"%s に「- 格上げ条件:」の行がない(何を確認すれば事実に格上げできるか)" % pid)
for n, l in lines:
if n in unconf_span:
continue
for pid in TAG.findall(l):
if unconf is None or pid not in entries:
add("ERROR", rel, n,
"本文の [推測:%s] に対応する項目が ## 未確認事項 にない" % pid)
# 逆向き(本文タグのない未確認事項)は「未解決の問い」として正当なのでエラーにしない
# --- ルール3: journal への/からのリンク禁止 ---
for n, l in lines:
for target in MDLINK.findall(l):
if target.lstrip("./").startswith("journal/"):
add("ERROR", rel, n, "journal へのリンクが張られている: %s" % target)
elif is_journal:
add("ERROR", rel, n, "journal から他ファイルへのリンクが張られている: %s" % target)
# --- ルール1: 検討済み・非採用 ---
if rejected is not None:
items = [(n, l) for n, l in rejected if l.strip().startswith(("-", "*"))]
for n, l in items:
if not re.search(r"\(journal/[^)]+\.md\)", l):
add("ERROR", rel, n,
"検討済み・非採用の項目が journal ファイル名を参照していない")
if len(items) > 5:
add("WARN", rel, items[5][0],
"検討済み・非採用が %d 件(目安5件)。古いものから落とす" % len(items))
# --- journal 固有 ---
if is_journal:
if not JOURNALFN.match(base):
add("ERROR", rel, 1, "journal のファイル名が YYYY-MM-DD-<topic>.md 形式でない")
head = " ".join(l for n, l in lines[:20])
if "結論" not in head:
add("WARN", rel, 1, "冒頭20行に結論が見当たらない(結論を先頭に置く)")
# --- ルール1: 調査・比較の混入(要目視確認) ---
if not is_journal and base not in PROSE_EXEMPT:
# 検討済み・非採用 は却下案を列挙する場所なので対象外
skip = set(n for n, _ in rejected) if rejected else set()
for n, l in lines:
if n in skip:
continue
if "メリット" in l and "デメリット" in l:
add("WARN", rel, n, "比較表らしき記述(メリット/デメリット)。journal 行きでは?")
elif re.search(r"却下|不採用|見送っ|も検討したが|案[A-Z]|[A-Z]案", l):
add("WARN", rel, n, "却下案の検討過程らしき記述。制約文に書き換えるか journal へ")
elif re.search(r"優先度", l) and l.lstrip().startswith("#"):
add("WARN", rel, n, "優先度評価は journal 行きでは?")
def main(argv):
targets = argv[1:]
if not targets:
targets = sorted(glob.glob("*.md")) + sorted(glob.glob("journal/*.md"))
if not targets:
print("対象ファイルなし"); return 0
for t in targets:
if os.path.isfile(t):
check(t)
else:
print("見つからない: %s" % t)
errs = [i for i in issues if i[0] == "ERROR"]
warns = [i for i in issues if i[0] == "WARN"]
for sev, path, line, msg in errs + warns:
print("%-5s %s:%d %s" % (sev, path, line, msg))
print("\n%d files / ERROR %d / WARN %d" % (len(targets), len(errs), len(warns)))
return 1 if errs else 0
if __name__ == "__main__":
sys.exit(main(sys.argv))
+96
View File
@@ -0,0 +1,96 @@
---
name: restructure-doc
description: 既存ドキュメント1ファイルをCLAUDE.mdのルール1〜4に沿って仕分ける。事実・推測・採用案を残し、調査過程をjournalへ移す。
disable-model-invocation: true
argument-hint: <ファイル名>
---
# ドキュメントを再編する
運用ルール策定前に書かれたドキュメントを、CLAUDE.md のルール1〜4に沿った形に作り直す。
**1回の実行で1ファイルだけ**扱う。
## 最重要の制約
**情報を失わせない。** この作業は「移動と再配置」であって、要約でも削除でもない。
- 内容を短くしない。判断に迷った記述は、消さずに残してユーザーに聞く。
- メインドキュメントから外す記述は、必ず journal に移す。捨てない。
- 元の主張を勝手に言い換えない。特に断定度を上げ下げしない。
## 手順
### 1. 全体を読む
対象ファイルを最初から最後まで読む。部分的に読んで判断しない。
### 2. 4分類する
各ブロック(見出し単位、または段落単位)を次のどれかに分ける。
| 分類 | 行き先 |
|---|---|
| a. 採用した案 / 現在の設定 / 設計に影響する制約 | メインドキュメントに残す |
| b. 裏取りの済んだ事実 | メインドキュメントに残す(文中のどこでもよい) |
| c. 未確認の推測・未解決の問い | メインドキュメント末尾の `## 未確認事項` |
| d. 調査過程 / 案の比較 / 却下案 / 優先度評価 | `journal/` へ移す |
判定のヒント:
- メリット/デメリット表、優先度リスト、「〜も検討したが」 → d
- 「〜できるはず」「〜と考えられる」「実現可能」で、実際に試していない → c
- 外部仕様の記述(KLEのプロパティ定義など)は、出典を確認できていれば b、していなければ c
### 3. 分類結果を提示して承認を得る
**ここで必ず止まる。** ファイルを書き換える前に、分類の一覧をユーザーに見せて確認する。
特に b と c の境目(裏取りできているか)は自分で判断せず聞く。
### 4. メインドキュメントを書き直す
- a を中心に再構成する。b は文中に自然に置く。
- 「なぜBではないのか」の記述が残っていたら、**制約文**に書き換える。
- ✗ `階層構造はKLEが非対応なので諦めた`
- ○ `階層クラスターはKLE JSONに出力しない(KLE互換性の制約)`
- 落とした案があれば末尾 `## 検討済み・非採用` に1案1行(理由なし、journalファイル名を参照)。
### 5. 未確認事項を立てる
c に分類したものを末尾の `## 未確認事項` にまとめる。
- 本文が前提にしている推測 → 本文に `[推測:P-n]` を付け、対応する項目を置く
- まだ本文に書けない未解決の問い → 本文タグなしで項目だけ置く
- 各項目に**格上げ条件**を必ず書く。思いつかないものはユーザーに聞く(空欄で残さない)
```markdown
### P-1 KLEはlabels[9]の内容を保存時に保持する
- 位置づけ: 本文「クラスター管理」が前提にしている
- 格上げ条件: KLE本家でインポート→エクスポートし、labels[9]が残ることを確認する
```
### 6. journal へ移す
d を `journal/YYYY-MM-DD-<元ファイル名>.md` に移す。
- 冒頭に**結論**(このファイルの再編で何が確定し、何を落としたか)
- 次に案の比較(元ファイルの表をそのまま持ってきてよい)
- 以下に元の調査記述
- 整形は最低限でよい
### 7. コミットする
1ファイル1コミット。メインドキュメントと journal を同じコミットに入れる。
```
<ファイル名> を運用ルールに沿って再編
<何を残し、何をjournalへ移したか 数行>
Journal: journal/2026-09-16-<元ファイル名>.md
```
## 注意
- 複数ファイルを一度に触らない。ファイル間で内容が重複していると気づいた場合は、
勝手に統合せず、指摘だけしてそのファイルの再編を終える。
- 再編の途中で事実関係の誤りを見つけても、勝手に直さない。指摘する。