mcpField
S
start

start

github.com/terrylicaエージェントワークフロー283 回インストール1 日前に更新
リポジトリ

start について

startは、プロジェクトにループ契約を作成し、実行ごとに契約を読み直す`/loop`の開始を準備するautoloopスキルです。契約の状態管理、セッションのバインド、起動設定を行い、継続実行するワークフローを支援します。

主な機能

  1. 01ループ契約の作成と登録
  2. 02旧形式の契約レイアウトを移行
  3. 03セッションとハートビートのフックを導入
  4. 04launchdウェイカーを設定
  5. 05`/loop`の起動手順を提示

活用シーン

  1. 01長期的な自律タスクの設定
  2. 021つのプロジェクトで複数ループを管理
  3. 03既存のループ契約を新レイアウトへ移行

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/terrylica/cc-skills --skill start

インストール後、Claude Code で/startを入力して読み込まれたか確認します。

詳細

autoloop: 開始

LOOP_CONTRACT.md を作成し、実行のたびに契約を読み込む自己修正型の /loop を開始します。

自己進化型スキル:このスキルは使用するにつれて改善されます。指示が誤っている、パラメーターがずれている、または回避策が必要になった場合は、先延ばしにせず、このファイルをすぐに修正してください。実際に再現できる問題がある場合にのみ更新してください。

引数

  • 位置引数(任意):契約ファイルのパス。デフォルトは ./LOOP_CONTRACT.md です。

ステップ 1:フックがインストールされていることを確認する

autoloop の両方のフックがまだインストールされていない場合は、~/.claude/settings.json にインストールします。冪等です。

  • PostToolUse → heartbeat-tick.sh — ツール呼び出しのたびにハートビートを刻み、cwd のずれを検出します。
  • SessionStart → session-bind.sh — stdin ペイロードから owner_session_id を確実にバインドします(壊れた $CLAUDE_SESSION_ID 環境変数の取得処理を置き換えます。参照:anthropics/claude-code#47018)。
  • PreToolUse(ScheduleWakeup) → pacing-veto.sh — ペーシングを装ったウェイカー(300~1199 秒のキャッシュミスゾーンにある遅延、または理由テキストにトークンバジェット/キャッシュウォーム/セルフペーシング/クールダウン/休憩が含まれる 270 秒超の遅延)を拒否します。実際の外部ブロッカーがない場合は、Tier 0(ターン内)を強制します。
# Source the hook install library
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/autoloop}"
source "$PLUGIN_ROOT/scripts/hook-install-lib.sh"

# Wave 5 A4: strip macOS quarantine xattrs in case this is the first run
# after `claude plugin marketplace add`. No-op on Linux / clean trees.
strip_plugin_quarantine_xattrs "$PLUGIN_ROOT" >/dev/null 2>&1 || true

# Wave 5 A8: explicit "auto-setup-on-start". If neither hook is installed,
# announce that we're running first-time setup before the install_all_hooks
# call so the user understands the side effect. If both are already
# installed, stay quiet — there's nothing to surface.
SETTINGS_PATH="$HOME/.claude/settings.json"
if [ "$(is_hook_installed "$SETTINGS_PATH" 2>/dev/null)" != "yes" ] || \
   [ "$(is_session_bind_installed "$SETTINGS_PATH" 2>/dev/null)" != "yes" ]; then
  echo "Installing autoloop hooks into ~/.claude/settings.json (one-time setup)..."
fi

# Install all four autoloop hooks (idempotent). DO NOT swallow stderr —
# install_all_hooks emits actionable diagnostics on failure (malformed JSON,
# write errors, missing hook scripts) that the user needs to see. If install
# fails, abort the bootstrap rather than continuing into a half-bootstrapped
# state where the loop appears registered but session-bind / heartbeat /
# pacing-veto / empty-firing-detector never run.
if ! install_all_hooks; then
  echo "ERROR: Failed to install autoloop hooks." >&2
  echo "  The bootstrap cannot continue safely — a registered loop without" >&2
  echo "  its hooks installed is what /autoloop:tinker calls F2_missing_hooks." >&2
  echo "  Inspect: jq . $SETTINGS_PATH ; ls $PLUGIN_ROOT/hooks/" >&2
  exit 1
fi

# Wave 6 anti-fragility (v12.52.0+): runtime self-test the just-installed
# hooks. Without this, a hook can be registered in settings.json yet crash
# on real invocation due to: missing shebang, broken shellcheck disable,
# stale path to a sibling lib, syntax error from a botched edit, missing
# dependency. The self-test catches those by feeding each hook a synthetic
# Claude Code event payload in a sandbox HOME and asserting exit 0.
if ! run_hook_runtime_selftest "$PLUGIN_ROOT" 2>&1; then
  echo "ERROR: Autoloop hook runtime self-test failed." >&2
  echo "  Hooks were registered in settings.json but at least one crashes" >&2
  echo "  on invocation. Inspect the FAIL line(s) above." >&2
  echo "  Recover: /autoloop:tinker will redo the install with diagnostics." >&2
  exit 1
fi

結果:登録済みループの契約ディレクトリで Claude セッションを開始すると、(1)SessionStart 時にループへバインドされ、その後(2)すべてのツール呼び出しで cwd のずれを検出しながらハートビートが刻まれます。

ステップ 2:事前チェック(v2 レイアウト — .autoloop/<slug>--<hash>/CONTRACT.md)

Wave 3 では、新しいディスク上のレイアウトが導入されました。契約は <project_cwd>/.autoloop/<campaign-slug>--<short-hash>/CONTRACT.md に置かれ、 同階層に state/ ディレクトリが配置されます。これにより、1 つの cwd 内で複数のキャンペーンを共存させやすくなり、ディレクトリ名から適切な契約を特定できるようになります。

従来の <cwd>/LOOP_CONTRACT.md が存在するディレクトリで、初めて /autoloop:start を実行すると、自動的に移行されます:

PROJECT_CWD="${1:-$(pwd -P)}"
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/autoloop}"
source "$PLUGIN_ROOT/scripts/registry-lib.sh"
source "$PLUGIN_ROOT/scripts/state-lib.sh"

# Returns "noop" if nothing to migrate, or two lines starting with
# "migrated_to_loop_id:" / "migrated_to_path:" on success.
MIGRATION_OUTPUT=$(migrate_legacy_contract "$PROJECT_CWD" "${CLAUDE_SESSION_ID:-$$-$(date +%s)}")
if [ "$MIGRATION_OUTPUT" != "noop" ]; then
  echo "$MIGRATION_OUTPUT"
  echo "Legacy LOOP_CONTRACT.md migrated to .autoloop/ subdir; resuming with new layout."
fi

次に、このプロジェクトに v2 の契約がすでに存在するかどうかを確認します:

existing=$(compgen -G "$PROJECT_CWD/.autoloop/*--[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]/CONTRACT.md" 2>/dev/null || true)
if [ -n "$existing" ]; then
  echo "v2 contract(s) present:"
  echo "$existing"
  # Ask the user: resume an existing campaign, start a new one alongside, or stop?
fi

AskUserQuestion を使って、既存のものを再開、新しいキャンペーンを並行して開始(2 つ目の .autoloop/<slug>--<hash>/ を作成)、または停止のいずれかを選んでもらいます。

ステップ 3:契約の入力情報を収集する

AskUserQuestion を使って、以下を収集します:

1. name        — short slug for this loop (e.g. "odb-research", "flaky-ci-watcher")
2. scope       — one-line Core Directive describing the long-horizon goal
3. cadence     — typical wake-up cadence hint (continuous | event-driven | hourly | daily)

契約パスは name と安定した short_hash から決定論的に導出されるため、ユーザーがパスを選ぶことはありません:

SLUG=$(slugify "$NAME_INPUT")
SHORT_HASH=$(compute_short_hash "${CLAUDE_SESSION_ID:-$$-$(date +%s)}" "$(date -u +%s)")
CONTRACT_DIR="$PROJECT_CWD/.autoloop/${SLUG}--${SHORT_HASH}"
CONTRACT_PATH="$CONTRACT_DIR/CONTRACT.md"
mkdir -p "$CONTRACT_DIR/state/revision-log"

$CONTRACT_DIR がすでに存在する場合は、short_hash に数値のサフィックスを追加するか、別の name を選ぶようユーザーに尋ねます。

ステップ 4:契約を作成する

プラグインに同梱されているテンプレートをコピーし、プレースホルダーを置換します。テンプレートを解決するには $CLAUDE_PLUGIN_ROOT(すべてのプラグインスキルに対して Claude Code が設定)を使用してください。SKILL.md がスクリプトとして実行されず、指示として読み込まれる場合は $0 が設定されないため、使用しないでください。

# CLAUDE_PLUGIN_ROOT points at /…/plugins/<plugin-name> when invoked from
# a skill. Fall back to the marketplace cache path if unset (e.g., when a
# user invokes the skill's Bash manually outside the harness).
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/autoloop}"
TEMPLATE="$PLUGIN_ROOT/templates/LOOP_CONTRACT.template.md"
if [ ! -f "$TEMPLATE" ]; then
  echo "ERROR: template missing at $TEMPLATE — reinstall the plugin" >&2
  exit 1
fi
cp "$TEMPLATE" "$CONTRACT_PATH"

# Also copy the PROVENANCE.md template (Wave 3) — sits alongside CONTRACT.md
PROV_TEMPLATE="$PLUGIN_ROOT/templates/PROVENANCE.template.md"
if [ -f "$PROV_TEMPLATE" ]; then
  cp "$PROV_TEMPLATE" "$CONTRACT_DIR/PROVENANCE.md"
fi

次に、sed(または Claude のツールでファイルを編集)を使ってユーザー入力を挿入します:

  • <SHORT_DESCRIPTIVE_NAME> → ユーザーが指定した name
  • <ISO_8601_UTC> → date -u +"%Y-%m-%dT%H:%M:%SZ"(必ず -u を指定してください。date のみではローカル時刻が返され、契約の UTC フィールドと気付かないまま不一致になります)
  • <RELATIVE_PATH_TO_LOOP_CONTRACT_MD> → $CONTRACT_PATH(.autoloop/<slug>--<hash>/CONTRACT.md の下に配置されます)
  • <CORE DIRECTIVE> / <PROJECT OR CAMPAIGN TITLE> → ユーザーが指定した scope

テンプレートを配置した後、init_contract_frontmatter_v2 を呼び出して、v2 の作成記録フィールド(loop_id、campaign_slug、created_at_utc など)をすべて設定します:

NEW_LOOP_ID=$(derive_loop_id "$CONTRACT_PATH")
init_contract_frontmatter_v2 "$CONTRACT_PATH" "$NEW_LOOP_ID" "$CONTRACT_DIR/state"

ステップ 5:マシンレジストリにループを登録する

ステップ 2/3 でループ ID を導出した後、このループをマシンレベルのレジストリに登録します。

ここでは owner_session_id を pending-bind に設定します。 $CLAUDE_SESSION_ID 環境変数はスキルの Bash サブプロセス内で設定されないため、SessionStart フック(hooks/session-bind.sh)が次回のセッション開始時に、この値をアトミックに置き換えます(anthropics/claude-code#47018)。インラインで取得すると、常に空または不正な値が保存されます。正しくバインドできるのはこのフックだけです。

# Source the registry library
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/autoloop}"
source "$PLUGIN_ROOT/scripts/registry-lib.sh"

# Derive loop_id from contract path (if not already done)
loop_id=$(derive_loop_id "$CONTRACT_PATH")

# Create entry JSON with all required fields. owner_session_id="pending-bind"
# is the canonical placeholder; the SessionStart hook replaces it atomically
# on the next session start. bound_cwd starts empty; heartbeat-tick records
# it on the first tick and detects drift thereafter.
#
# state_dir resolution: ALWAYS go through `state_dir_path "$loop_id"
# "$CONTRACT_PATH"` instead of hand-rolling `<dir>/.loop-state/<id>/`.
# Wave 3 introduced the v2 layout (.autoloop/<slug>--<hash>/state/) and
# `state_dir_path` returns the correct path for both v1 and v2 contracts.
# Hand-rolling .loop-state would register a v1 path against a v2 contract
# and the heartbeat-tick hook would write to a directory the contract
# doesn't recognize.
entry=$(jq -n \
  --arg loop_id "$loop_id" \
  --arg contract_path "$(realpath "$CONTRACT_PATH")" \
  --arg state_dir "$(state_dir_path "$loop_id" "$CONTRACT_PATH")" \
  --arg owner_session_id "pending-bind" \
  --arg bound_cwd "" \
  --arg owner_pid "$$" \
  --arg owner_start_time_us "$(date +%s%N | cut -c1-16)" \
  --arg launchd_label "com.user.claude.loop.$loop_id" \
  --arg started_at_us "$(date +%s%N | cut -c1-16)" \
  --arg expected_cadence_seconds "$cadence_seconds" \
  --arg generation "0" \
  '{loop_id: $loop_id, contract_path: $contract_path, state_dir: $state_dir, owner_session_id: $owner_session_id, bound_cwd: $bound_cwd, owner_pid: $owner_pid, owner_start_time_us: $owner_start_time_us, launchd_label: $launchd_label, started_at_us: $started_at_us, expected_cadence_seconds: $expected_cadence_seconds, generation: $generation}')

# Register in machine registry (atomic, serialized write)
if ! register_loop "$entry"; then
  echo "WARNING: Failed to register loop in machine registry (may already exist)" >&2
fi

ステップ 6:launchd plist を生成して読み込む

ループを登録した後、launchd plist を生成して読み込みます。

# Source the launchd library
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/autoloop}"
source "$PLUGIN_ROOT/scripts/launchd-lib.sh"
source "$PLUGIN_ROOT/scripts/state-lib.sh"

# Derive loop_id and state_dir
loop_id=$(derive_loop_id "$CONTRACT_PATH")
state_dir=$(state_dir_path "$loop_id" "$CONTRACT_PATH")

# Stub waker script path (Phase 9 will ship the actual script)
waker_script="$PLUGIN_ROOT/scripts/waker.sh"

# Calculate polling interval (default 150 seconds = half of typical cadence)
interval_seconds="${cadence_seconds:-300}"
interval_seconds=$((interval_seconds / 2))
if [ "$interval_seconds" -lt 60 ]; then
  interval_seconds=60
fi

# Generate plist. The Wave-6 anti-fragility wave hardened generate_plist to
# refuse a plist pointing at a missing waker_script (turned silent failure
# into loud failure).
if ! generate_plist "$loop_id" "$state_dir" "$waker_script" "$interval_seconds"; then
  echo "ERROR: Failed to generate launchd plist" >&2
  echo "  The bootstrap cannot continue safely; recover with /autoloop:tinker" >&2
  exit 1
fi

# Load plist (bootstraps on macOS; skipped gracefully on non-macOS).
# DO NOT swallow stderr — load_plist's diagnostic output (FDA permission,
# label collision, malformed plist, MDM block) is the only signal the user
# has when launchctl rejects the plist.
if ! load_plist "$loop_id" "$state_dir"; then
  echo "ERROR: Failed to load launchd plist" >&2
  echo "  The loop is registered but will not fire; recover with /autoloop:tinker" >&2
  exit 1
fi

# Wave-6 anti-fragility: post-bootstrap launchctl assertion.
# load_plist returning 0 only means the bootstrap call succeeded; verify
# the label actually appears in `launchctl list` so we never ship a
# half-loaded state. Skipped on non-macOS.
if [ "$(uname -s)" = "Darwin" ]; then
  label="com.user.claude.loop.${loop_id}"
  if ! launchctl list "$label" >/dev/null 2>&1; then
    echo "ERROR: launchctl list does not show '$label' after bootstrap" >&2
    echo "  This is a half-loaded plist state. Recover with /autoloop:tinker" >&2
    exit 1
  fi
fi

ステップ 7:ポインタートリガーを出力する(ステップ 7 から更新)

ユーザーが /loop に渡せるスニペットを出力します。

/loop

Read and execute the latest autonomous work contract at:
  $CONTRACT_PATH

Follow its instructions verbatim. That file self-updates; this trigger stays fixed.

ステップ 8:ループをすぐに開始するか確認する

2 つの選択肢を指定して AskUserQuestion を使用します。

  • Start now — ポインタートリガーを入力として、ネイティブの /loop スキルを Skill(loop) 経由で呼び出します
  • Not yet — ユーザーが(たとえばコントラクトをコミットした後に)手動で /loop を呼び出します

Start now の場合は、ポインタートリガースニペットを args として Skill(loop) を呼び出します。それ以外の場合は、"コントラクトを $CONTRACT_PATH に作成しました。準備ができたら、上記のポインタートリガーを使って /loop を実行してください。" と出力します。

ステップ 9:コミットを提案する

最初のコミット案を出力します。

git add "$CONTRACT_PATH"
git commit -m "loop(bootstrap): scaffold LOOP_CONTRACT.md for $name"

ステップ 10:バインド検証ブロックを出力する(Wave 6)

ステップ 9 の後、以下の明示的な検証ブロックを出力します。これは、/autoloop:start が終了する前にユーザーに表示される最後の通知です。必ず出力してください。Wave 6 のきっかけとなったバインド保留状態のまま失敗する問題は、ループが実際には自律的に動作していないにもかかわらず、/autoloop:start が正常に終了していたことも一因でした。

🔧 Loop registered: $loop_id ($display_name)
   project_root:  $project_root              # = .created_at_cwd; bind hooks match cwd against this
   contract:      $CONTRACT_PATH

📡 Autonomous-fire requirement
   The launchd waker can only fire /loop on this loop AFTER the
   SessionStart hook has bound owner_session_id. The hook fires when
   you open Claude Code with cwd anywhere in the path hierarchy:

       $project_root  ⊆  cwd  ⊆  $(dirname "$CONTRACT_PATH")

   Until that bind happens, the registry shows owner_session_id =
   "pending-bind" and the waker logs "dead+fresh anomaly" instead of
   firing. Run /autoloop:muster to verify state.

✅ Verify within 30s of your next session start
   /autoloop:muster
       expect: STATUS=ACTIVE  (not PENDING-BIND or PENDING-BIND-STUCK)

   If PENDING-BIND-STUCK after 5min:
     • cd "$project_root" && claude        # opens a session that the hook can bind
     • /autoloop:tinker $loop_id           # patches owner_session_id from THIS session
     • heal-self.sh runs hourly and will auto-bind to a live session
       whose JSONL mtime is <5min — a third safety net

変数を展開したうえで、このブロックを一字一句そのまま出力してください。言い換えたり短縮したりしないでください。トラブルシューティングのヒントにあるパスは、初めてバインド保留状態に遭遇するオペレーターにとって重要です。

アンチパターン

  • ユーザーの明示的な確認なしに既存のコントラクトを上書きしないでください
  • 現在の作業ツリー外のディレクトリにコントラクトを書き込まないでください(サンドボックスの回避)
  • Core Directive 全体をプロンプトとして /loop を呼び出さないでください。必ず短いポインタートリガーパターンを使用してください

トラブルシューティング

症状対処法
テンプレートが見つからないclaude plugin reinstall autoloop@cc-skills
/loop が "unknown skill" と表示するClaude Code のバージョンが 2.1.101 未満です。ネイティブの /loop を利用するにはアップグレードしてください
ポインタートリガーが古いファイルを再読み込みする$CONTRACT_PATH が絶対パス、または CWD に依存しないパスであることを確認してください

実行後の振り返り

  1. 自分の場所を確認します。 — 編集する前に、この SKILL.md が正式なファイルであることを確認します。
  2. 何が失敗しましたか? — 原因となった指示を修正します。
  3. 何がずれましたか? — 最新の慣用表現と一致していない場合は、テンプレートを更新します。
  4. 記録します。 — きっかけ、修正内容、証拠を含む進化ログのエントリーを作成します。

コメント