policy-algebra について
policy-algebra は、短い意図や詳細な計画から凍結済みの Starlark ガバナンスルールを生成するスキルです。レビューや任意のファイル出力に加え、正規化した内容で差分を検出できます。
主な機能
- 01意図やファイルから不変条件を抽出
- 02Starlark でガバナンスルールを作成
- 03凍結前にドラフトをレビュー
- 04ルールブロックをファイルに保存
- 05--verify で差分を確認
活用シーン
- 01短いポリシーの意図からルールを生成
- 02計画の制約をガバナンスルールに変換
- 03保存済みルールブロックの差分を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ camacho/ ai-skills --skill policy-algebraインストール後、Claude Code で/policy-algebraを入力して読み込まれたか確認します。
詳細
/policy-algebra
簡潔な意図の文字列(詳細なインタビュー)または詳細な計画入力(簡単な確認)から、凍結され、ドリフトをチェックできるガバナンスルールを生成します。呼び出し元が後続の作業に挿入できるフェンス付き Starlark ブロックを生成し、正規化したうえでドリフトを検出する --verify を提供します。
呼び出し方法
/policy-algebra <target> # generate rules
/policy-algebra <target> --write <path> # generate + persist raw block
/policy-algebra --verify <frozen> <candidate> # drift check
<target> は、読み取り可能なファイルパス(SHALLOW モード)または意図の文字列(DEEP モード)です。
適応型入力検出
if target is a readable file path:
mode = SHALLOW
input = read(target)
else:
mode = DEEP
input = target
DEEP フロー
-
/grill-me用の枠組みとなるプロンプトを作成します。「次の内容について、ルールの不変条件を抽出するためにユーザーへインタビューしてください。 <target prompt>
次の点に関する質問に重点を置いてください。
- 範囲:このルールはどの成果物/イベント/フェーズを対象としますか?
- 不変条件:常に真でなければならないことは何ですか?決して起きてはならないことは何ですか?
- 変数の空欄:インスタンス間で何が変わりますか?
- 結合子:既存の演算子(+、diff、intersect、メソッド呼び出し)で十分ですか?それとも新しいヘルパーが必要ですか?
- 失敗モード:ルールを満たせない場合、どうなりますか?
自然言語で述語を構造化したリストとして返してください。 /policy-algebra が出力から Starlark を作成します。」
-
Skillツールを使い、このプロンプトを指定して/grill-meを呼び出します。 -
/grill-meの出力を不変条件のリストとして取得します。 -
下書き作成に進みます(SHALLOW フローのステップ 5)。
SHALLOW フロー
- ファイルの内容を読み取ります。
- 不変条件の宣言をスキャンします。「Invariants」「Rules」「Constraints」などの見出し、MUST/NEVER/ALWAYS を含む箇条書き、述語のようなコードブロックを確認します。
- これらを不変条件のリストとして抽出します。
- 抽出できた不変条件が 2 つ未満、または内容が曖昧な場合は、ファイルの内容をターゲットとして DEEP モードに移行します。
notation.mdの規約に従って、不変条件のリストから Starlark を作成します。- 下書きを
/reviewに渡します(パネルのヒントは指定しません。/reviewが編成します)。 /reviewが収束すれば凍結します。上限に達した場合は、/reviewのエスカレーションをそのまま適用します。
下書きの規約
対象ごとにまとめます(まず DEFAULTS、次に名前付き関数)。各関数本体は 5 行以内にし、関数ごとに説明コメントを 1 つ付けます。変更は行いません。ルールの全容については notation.md を参照してください。
凍結して返す
/review が APPROVE した後、Starlark のテキストを正確に FROZEN ブロックとして取得します。凍結時に正規化してはいけません。正規化は比較時のみ行い、凍結と返却の処理を単純に保ちます。
出力形式:
ALGEBRA:
```starlark
<raw frozen starlark block, as /review approved>
REVIEW_ROUNDS: PANEL: STATUS: CONVERGED FILE: (「--write」を使用した場合のみ) NOTE: (任意)
If `--write <path>` was supplied, write the raw Starlark block to that path and include `FILE: <path>` in the return block.
## `--verify` flow
1. Read both files.
2. Extract first ` ```starlark ` fenced block from each file.
3. Normalize both blocks (strip trailing whitespace, collapse blank lines, trim edges).
4. If equal → print `MATCH`, exit 0. Else → print `DRIFT` + unified diff, exit 1.
Use the following bash to verify drift — no external dependencies needed:
```bash
# policy-algebra verify — inline, no dependencies
# Usage: bash verify.sh <frozen_file> <candidate_file>
normalize() { sed 's/[[:space:]]*$//' | sed '/^$/d'; }
extract_starlark() { sed -n '/^```starlark/,/^```$/p' "$1" | sed '1d;$d'; }
FROZEN=$(extract_starlark "$1" | normalize)
CANDIDATE=$(extract_starlark "$2" | normalize)
if [ "$FROZEN" = "$CANDIDATE" ]; then
echo "MATCH"
exit 0
else
echo "DRIFT"
diff <(echo "$FROZEN") <(echo "$CANDIDATE")
exit 1
fi
複数ブロックの動作
ファイルに ```starlark ブロックが複数含まれる場合、extractAlgebra は最初のブロックのみを暗黙的に使用します。これは意図的な動作です。あらゆるドキュメントで、正規ルールを最初のフェンス付きブロックに記述してください。
失敗モード
| 失敗 | 動作 |
|---|---|
| ターゲットが省略されている | エラー:「target required」 |
| SHALLOW:ファイルを読み取れない | パス文字列を意図として DEEP に移行 |
| SHALLOW:抽出可能な不変条件が 2 つ未満 | ファイルの内容を意図として DEEP に移行 |
DEEP:/grill-me を利用できない | エラー:「deep interview requires /grill-me skill」 |
| DEEP:ユーザーがインタビューを中断 | /grill-me の終了時の動作をそのまま適用 |
| 下書きの Starlark が空 | エラー:「no invariants found to codify」。不変条件のリストを返す |
/review が収束せず上限に達した | /review のエスカレーションをそのまま適用し、独自の処理は行わない |
--write <path> の書き込み先に書き込めない | パスを含むエラーを返しますが、インラインの結果も返します |
--verify のいずれかのパスを読み取れない | パスを含むエラーを返し、終了コード 2 |
```starlark ブロックがない | NoAlgebraBlockError とパスを返し、終了コード 2 |
canonicalize が例外をスロー | スタックトレースを含むエラーを返し、終了コード 3 |
終了コード
| コード | 意味 |
|---|---|
| 0 | MATCH(verify)/SUCCESS(通常) |
| 1 | DRIFT(verify のみ) |
| 2 | 入力エラー(ファイルがない、不正な引数、NoAlgebraBlockError) |
| 3 | 内部エラー |
ツール共通の注意事項
- Claude Code:
Skillツールを使って、/grill-me(DEEP インタビュー)と/review(パネルによる合意形成)を呼び出します。どちらもプロジェクトローカルの スキルとして.claude/skills/にあります。 - Codex / Cursor:
Skillツールは Claude Code 専用です。該当するスキルファイルを直接読みます。.claude/skills/grill-me/SKILL.md— インタビュー手順.claude/skills/review/SKILL.md— パネルによる合意形成 コマンドとして呼び出すのではなく、手順をその場で適用します。 この SKILL.md のドラフト作成、凍結、--verifyのロジックは ツールに依存せず、Claude Code、Codex、Cursor で同じように動作します。
- **CLI(
--verifyパス)**は実行環境に依存しません。上記の--verifyセクションにあるインラインの bash スニペットを使ってください。Node や pnpm は不要です。
ガバナンス
このスキルはセルフホスト可能です。独自のルールは、凍結された Starlark ガバナンスブロックとして記述されています。これらの不変条件からの逸脱はバグです。/policy-algebra --verifyで検証してください。
# policy-algebra governance — self-hosted
# FROZEN — do not edit; regenerate via /policy-algebra
DEFAULTS = {
"min_invariants": 2,
"exit_codes": {"match": 0, "drift": 1, "input_error": 2, "internal_error": 3},
"required_output_fields": ["ALGEBRA", "REVIEW_ROUNDS", "PANEL", "STATUS"],
"converged_status": "CONVERGED",
}
# SELECT_MODE: file path → SHALLOW; intent string → DEEP.
def SELECT_MODE(target):
if is_readable_file(target):
return "SHALLOW"
return "DEEP"
# SHALLOW_OR_DEEP: fall through to DEEP when extraction yields < min_invariants or is ambiguous.
def SHALLOW_OR_DEEP(invariants, ambiguous):
if len(invariants) < DEFAULTS["min_invariants"] or ambiguous:
return "DEEP"
return "SHALLOW"
# EXTRACT_BLOCK: use first starlark fence only; multiple blocks → first wins.
def EXTRACT_BLOCK(fences):
if len(fences) == 0:
return None
return fences[0]
# OUTPUT_VALID: output must carry all required fields; STATUS=CONVERGED only after review.
def OUTPUT_VALID(output, review_loop_passed):
fields_present = all([f in output for f in DEFAULTS["required_output_fields"]])
converged_ok = output.get("STATUS") != DEFAULTS["converged_status"] or review_loop_passed
return fields_present and converged_ok
# VERIFY: canonicalize both blocks and compare; never modify source files.
def VERIFY(frozen_canon, candidate_canon):
if frozen_canon == candidate_canon:
return DEFAULTS["exit_codes"]["match"]
return DEFAULTS["exit_codes"]["drift"]
# EXIT_CODE: map outcome strings to numeric codes.
def EXIT_CODE(outcome):
return DEFAULTS["exit_codes"].get(outcome, DEFAULTS["exit_codes"]["internal_error"])