From db1b8aea2760d78fc0a1ed33ad8cc765cf7e90a4 Mon Sep 17 00:00:00 2001 From: kosako <778050+kosako@users.noreply.github.com> Date: Sun, 12 Jul 2026 00:28:50 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(doctor):=20agent-tools=20status=20cont?= =?UTF-8?q?ract=20v3=20=E3=81=AB=E8=BF=BD=E5=BE=93=20(Closes=20#194)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit agent-tools#186 (#198, bb085a9) で status contract が v2→v3 になった (target state に deployed_but_inactive を追加 = 未登録なのに実体が残る 配置物の可視化)。doctor の expected version を 3 へ更新し (exact-match 維持・fail-closed のまま)、conflict / stale と同様に deployed_but_inactive の件数 warn を追加する (掃除候補の可視化という新語彙の趣旨に沿う消費)。 test-doctor: fake status.sh fixture を v3 化し、deployed_but_inactive target を足して新 warn の発火を検証。unknown version ケースは "expected 3" に追従。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Wqz3c5ope2oVauyyWZNkyv --- scripts/doctor.sh | 12 ++++++++---- scripts/test-doctor.sh | 17 +++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/scripts/doctor.sh b/scripts/doctor.sh index 1b8cf00..531733d 100755 --- a/scripts/doctor.sh +++ b/scripts/doctor.sh @@ -670,7 +670,7 @@ section "agent-tools (report-only)" # (executing code from another repo) is opt-in via enableAgentToolsStatus # so doctor's no-side-effects invariant is never delegated implicitly. # See docs/ai-environment-boundary.md and the agent-tools -# status-manifest-contract (contract_version 2). +# status-manifest-contract (contract_version 3). # The expected path defaults to the dotfiles directory convention # (~/src/agent/agent-tools) but is overridable via the AGENT_TOOLS env so a # non-standard checkout can still be reported. presence only; never cloned. @@ -695,10 +695,10 @@ else # the JSON is malformed (a failed substitution would trip set -e). sj() { printf '%s' "$status_json" | yq -p json "$1" 2>/dev/null || true; } contract_version="$(sj '.contract_version // ""')" - if [[ "$contract_version" != "2" ]]; then - warn "agent-tools status contract_version=${contract_version:-unknown}, expected 2 (not interpreting fields)" + if [[ "$contract_version" != "3" ]]; then + warn "agent-tools status contract_version=${contract_version:-unknown}, expected 3 (not interpreting fields)" else - ok "agent-tools present; status contract v2" + ok "agent-tools present; status contract v3" if [[ "$(sj '.repo.clean // false')" == "true" ]]; then ok "agent-tools working tree clean" @@ -741,6 +741,10 @@ else if [[ "$(sj '[.sync_targets[]? | select(.state == "stale")] | length')" != "0" ]]; then warn "agent-tools has stale sync targets (generated artifact newer than target)" fi + # v3 (#194): gated-but-still-deployed leftovers are cleanup candidates. + if [[ "$(sj '[.sync_targets[]? | select(.state == "deployed_but_inactive")] | length')" != "0" ]]; then + warn "agent-tools has deployed-but-inactive sync targets (gated entries still on disk; clean up or re-approve)" + fi fi unset -f sj fi diff --git a/scripts/test-doctor.sh b/scripts/test-doctor.sh index 3db8098..b1b88b4 100755 --- a/scripts/test-doctor.sh +++ b/scripts/test-doctor.sh @@ -146,7 +146,7 @@ agent_marker="$agent_dir/ran-marker" mkdir -p "$agent_scripts" # Root-pinning fake (see write_root_pinned_status_sh): regression for #73. write_root_pinned_status_sh "$agent_scripts/status.sh" \ - '{"contract_version":2,"repo":{"present":true,"clean":true},"assets":{"total":1,"manifest_errors":0},"checks":{"manifest_validation":"pass","prompt_injection_static":"pass"},"generated":{"total":1,"stale":0},"register":{"catalog_present":true,"registered":1,"human_review_required":0,"unsupported":0},"sync_targets":[{"tool":"codex","name":"x","state":"conflict"}]}' + '{"contract_version":3,"repo":{"present":true,"clean":true},"assets":{"total":1,"manifest_errors":0},"checks":{"manifest_validation":"pass","prompt_injection_static":"pass"},"generated":{"total":1,"stale":0},"register":{"catalog_present":true,"registered":1,"human_review_required":0,"unsupported":0},"sync_targets":[{"tool":"codex","name":"x","state":"conflict"},{"tool":"codex","name":"y","state":"deployed_but_inactive"}]}' # A) Opt-in disabled: present but status.sh must not run. Force the capability # off in a throwaway copy so the test is independent of the real default @@ -178,9 +178,10 @@ set_capability_all "$optin_root" enableAgentToolsStatus true # B) Opt-in enabled: status.sh runs, summary shown, conflict flagged. rm -f "$agent_marker" if at_out="$(HOME="$fixture_home" "$optin_root/scripts/doctor.sh" personal 2>&1)"; then - if grep -Fq "agent-tools present; status contract v2" <<< "$at_out" \ - && grep -Fq "sync conflicts" <<< "$at_out" && [[ -e "$agent_marker" ]]; then - ok "test passed: opt-in runs status.sh and summarizes (conflict flagged)" + if grep -Fq "agent-tools present; status contract v3" <<< "$at_out" \ + && grep -Fq "sync conflicts" <<< "$at_out" \ + && grep -Fq "deployed-but-inactive sync targets" <<< "$at_out" && [[ -e "$agent_marker" ]]; then + ok "test passed: opt-in runs status.sh and summarizes (conflict + inactive leftovers flagged)" else printf '%s\n' "$at_out" >&2 fail "test failed: opt-in summary/conflict/marker missing" @@ -202,7 +203,7 @@ echo '{"contract_version":99}' SH chmod +x "$agent_scripts/status.sh" if at_out="$(HOME="$fixture_home" "$optin_root/scripts/doctor.sh" personal 2>&1)"; then - if grep -Fq "expected 2 (not interpreting fields)" <<< "$at_out"; then + if grep -Fq "expected 3 (not interpreting fields)" <<< "$at_out"; then ok "test passed: unknown contract version is not interpreted (exit 0)" else printf '%s\n' "$at_out" >&2 @@ -284,7 +285,7 @@ else fi # AT-override) AGENT_TOOLS overrides the expected path (issue #71). The -# default ~/src/agent/agent-tools is absent (removed in E), so a v2 summary +# default ~/src/agent/agent-tools is absent (removed in E), so a v3 summary # plus the run marker proves doctor read the overridden checkout. override_dir="$fixture_home/custom/agent-tools" override_scripts="$override_dir/scripts" @@ -293,10 +294,10 @@ mkdir -p "$override_scripts" # Same root-pinning contract as the default-path fake: doctor must pass # --root equal to the AGENT_TOOLS-overridden checkout (#71 + #73). write_root_pinned_status_sh "$override_scripts/status.sh" \ - '{"contract_version":2,"repo":{"present":true,"clean":true},"assets":{"total":0,"manifest_errors":0},"checks":{"manifest_validation":"pass","prompt_injection_static":"pass"},"generated":{"total":0,"stale":0},"register":{"catalog_present":false,"registered":0,"human_review_required":0,"unsupported":0},"sync_targets":[]}' + '{"contract_version":3,"repo":{"present":true,"clean":true},"assets":{"total":0,"manifest_errors":0},"checks":{"manifest_validation":"pass","prompt_injection_static":"pass"},"generated":{"total":0,"stale":0},"register":{"catalog_present":false,"registered":0,"human_review_required":0,"unsupported":0},"sync_targets":[]}' rm -f "$override_marker" if at_out="$(HOME="$fixture_home" AGENT_TOOLS="$override_dir" "$optin_root/scripts/doctor.sh" personal 2>&1)"; then - if grep -Fq "agent-tools present; status contract v2" <<< "$at_out" && [[ -e "$override_marker" ]]; then + if grep -Fq "agent-tools present; status contract v3" <<< "$at_out" && [[ -e "$override_marker" ]]; then ok "test passed: AGENT_TOOLS overrides the expected path" else printf '%s\n' "$at_out" >&2 From 86ea718b997dc0599b3d2c120c6e1f5a94ad0b09 Mon Sep 17 00:00:00 2001 From: kosako <778050+kosako@users.noreply.github.com> Date: Sun, 12 Jul 2026 00:34:47 +0900 Subject: [PATCH 2/2] =?UTF-8?q?review:=20unknown-version=20=E3=81=AE?= =?UTF-8?q?=E5=90=A6=E5=AE=9A=E6=A4=9C=E8=A8=BC=E3=81=A8=20doc=20=E3=81=AE?= =?UTF-8?q?=20v2=20=E6=AE=8B=E5=AD=98=E3=82=92=E6=98=AF=E6=AD=A3=20(#195?= =?UTF-8?q?=20should=20=E5=AF=BE=E5=BF=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - test-doctor: unknown contract version の fixture に sentinel fields (repo / sync_targets) を持たせ、warn を出しつつ fields を解釈する退行を 否定検証で固定 (working tree / sync conflicts / deployed-but-inactive の summary 行が出ないこと)。 - docs/ai-environment-boundary.md: contract_version 2 の残存記載を 3 へ更新し deployed_but_inactive を warning 対象に追記。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Wqz3c5ope2oVauyyWZNkyv --- docs/ai-environment-boundary.md | 2 +- scripts/test-doctor.sh | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/ai-environment-boundary.md b/docs/ai-environment-boundary.md index 559d144..8f6089f 100644 --- a/docs/ai-environment-boundary.md +++ b/docs/ai-environment-boundary.md @@ -82,7 +82,7 @@ AI skills / agents project は、`dotfiles` の policy を前提に動く。poli - `dotfiles` は AI skills / agents project の path を表示してよい。 - `dotfiles` は AI skills / agents project を自動更新しない。 - `dotfiles` は AI skills / agents project の secret を読まない。 -- `dotfiles` の `doctor` は `~/src/agent/agent-tools`(既定。非標準な checkout 先は `AGENT_TOOLS` env で override 可)の presence を report する。status(`scripts/status.sh --json`、report-only、`contract_version: 2`)の読み取りは別 repo のコード実行になるため、`enableAgentToolsStatus` capability での明示 opt-in 時のみ実行し、安全な summary(`conflict` / `stale` / 失敗 check 等は warning)を出す(Issue #7)。clone / pull / sync は一切しない。 +- `dotfiles` の `doctor` は `~/src/agent/agent-tools`(既定。非標準な checkout 先は `AGENT_TOOLS` env で override 可)の presence を report する。status(`scripts/status.sh --json`、report-only、`contract_version: 3`)の読み取りは別 repo のコード実行になるため、`enableAgentToolsStatus` capability での明示 opt-in 時のみ実行し、安全な summary(`conflict` / `stale` / `deployed_but_inactive` / 失敗 check 等は warning)を出す(Issue #7、v3 追従は #194)。clone / pull / sync は一切しない。 - AI skills / agents project は `dotfiles` の capability を前提条件として参照してよい。 - AI skills / agents project が install、network tunnel、secret access を必要とする場合は、`dotfiles` 側の capability と approval policy に従う。 diff --git a/scripts/test-doctor.sh b/scripts/test-doctor.sh index b1b88b4..0ff9b19 100755 --- a/scripts/test-doctor.sh +++ b/scripts/test-doctor.sh @@ -194,16 +194,21 @@ else fi # C) Opt-in + unknown contract version: not interpreted, still exit 0. +# Sentinel fields prove fail-closed: a doctor that warns but still interprets +# fields would emit the summary lines below, so their absence is asserted too. cat > "$agent_scripts/status.sh" <<'SH' #!/bin/sh json=0 for a in "$@"; do [ "$a" = "--json" ] && json=1; done [ "$json" = 1 ] || exit 1 -echo '{"contract_version":99}' +echo '{"contract_version":99,"repo":{"present":true,"clean":true},"sync_targets":[{"tool":"codex","name":"x","state":"conflict"},{"tool":"codex","name":"y","state":"deployed_but_inactive"}]}' SH chmod +x "$agent_scripts/status.sh" if at_out="$(HOME="$fixture_home" "$optin_root/scripts/doctor.sh" personal 2>&1)"; then - if grep -Fq "expected 3 (not interpreting fields)" <<< "$at_out"; then + if grep -Fq "expected 3 (not interpreting fields)" <<< "$at_out" \ + && ! grep -Fq "agent-tools working tree clean" <<< "$at_out" \ + && ! grep -Fq "sync conflicts" <<< "$at_out" \ + && ! grep -Fq "deployed-but-inactive sync targets" <<< "$at_out"; then ok "test passed: unknown contract version is not interpreted (exit 0)" else printf '%s\n' "$at_out" >&2