Skip to content

Commit 62b864f

Browse files
kosakoclaude
andcommitted
Count and prune instruction artifacts in status / build (#48)
#48 の PR-4a (観測まで)。status / doctor / prune を instruction 対応にする。 - status.generated_state: instruction (generated/<tool>/instructions/*) も total / stale に数える。鮮度は InstructionMarker の build_id を source content から再計算して 比較する (mtime 非依存)。doctor は status 統合なので自動的に instruction を含む。 - build --prune: 対応する instruction asset が無い generated instruction を削除する (agent-tools marker を持つもののみ)。あわせて skill -> instruction 転換で残った stale skill directory もここで prune される (PR-1 で先送りした課題の解消)。 - status / build の self-test に instruction の generated カウント・stale・prune を追加。 - docs/instruction-artifact-kind.md の実装状況を更新。 injection の instruction strict と実 asset 投入は PR-4b。 Refs #48 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ec4da6f commit 62b864f

5 files changed

Lines changed: 83 additions & 4 deletions

File tree

docs/instruction-artifact-kind.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ connect / sync (所有判定) が同じ format を共有する。
9090
instruction 生成、ファイル内コメント marker (`scripts/lib/instruction_marker.rb`)、
9191
check-manifests の 1-per-target 検証、catalog の target-artifact 化と register の
9292
ビルド可能性証明、connect (`scripts/connect.sh`)、sync の instruction 配置
93-
(catalog を source of truth として列挙)。
94-
- 後続: status / doctor / prune の instruction 対応、injection の instruction strict、
95-
検証用の実 instruction asset 投入。
93+
(catalog を source of truth として列挙)、status / doctor の instruction generated
94+
カウントと鮮度判定、build --prune の instruction 対応。
95+
- 後続: injection の instruction strict (external URL を high に)、検証用の実
96+
instruction asset 投入。

scripts/lib/build.rb

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,22 @@ def rel(path)
143143
# marker のない directory は warning として返し、残す。
144144
def prune
145145
expected = Hash.new { |h, k| h[k] = [] }
146+
instruction_expected = Hash.new(false)
146147
Assets.load_all(@root).each do |asset|
147-
asset[:targets].each { |tool| expected[tool] << asset[:name] }
148+
(asset[:targets] || []).each do |tool|
149+
if ArtifactTargets.resolve(asset, tool) == "instruction"
150+
instruction_expected[tool] = true
151+
else
152+
expected[tool] << asset[:name]
153+
end
154+
end
148155
end
149156

150157
pruned = []
151158
kept = []
152159
TOOLS.each do |tool|
160+
# skill: manifest に対応しない generated directory を削除する。
161+
# (skill -> instruction 転換で残った stale skill もここで消える)
153162
Dir.glob(File.join(@root, "generated", tool, "skills", "*")).sort.each do |dir|
154163
next unless File.directory?(dir)
155164
next if expected[tool].include?(File.basename(dir))
@@ -161,6 +170,20 @@ def prune
161170
kept << rel(dir)
162171
end
163172
end
173+
174+
# instruction: その tool 向け instruction asset が無ければ generated を削除する。
175+
next if instruction_expected[tool]
176+
177+
Dir.glob(File.join(@root, "generated", tool, "instructions", "*")).sort.each do |file|
178+
next unless File.file?(file)
179+
180+
if InstructionMarker.parse(File.read(file))
181+
FileUtils.rm_f(file)
182+
pruned << rel(file)
183+
else
184+
kept << rel(file)
185+
end
186+
end
164187
end
165188
[pruned, kept]
166189
end

scripts/lib/status.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
require_relative "build"
1818
require_relative "sync"
1919
require_relative "artifact_targets"
20+
require_relative "instruction_marker"
2021

2122
module Status
2223
CONTRACT_VERSION = 2
@@ -72,6 +73,7 @@ def injection_outcome(findings)
7273
end
7374

7475
# generated artifacts の数と、source より古い (build_id 不一致) artifact の数。
76+
# skill (directory) と instruction (単一ファイル) の両方を数える。
7577
def generated_state
7678
sources = Assets.sources_by_name(@root)
7779
total = 0
@@ -83,6 +85,12 @@ def generated_state
8385
total += 1
8486
stale += 1 unless fresh?(artifact, sources)
8587
end
88+
Dir.glob(File.join(@root, "generated", tool, "instructions", "*")).sort.each do |artifact|
89+
next unless File.file?(artifact)
90+
91+
total += 1
92+
stale += 1 unless fresh_instruction?(artifact, sources)
93+
end
8694
end
8795
[total, stale]
8896
end
@@ -102,6 +110,17 @@ def fresh?(artifact, sources)
102110
false
103111
end
104112

113+
# instruction (ファイル内コメント marker) の鮮度判定。
114+
def fresh_instruction?(path, sources)
115+
marker = InstructionMarker.parse(File.read(path))
116+
return false unless marker
117+
118+
source = sources[marker["name"]]
119+
return false unless source
120+
121+
marker["build_id"] == Build.build_id_for(@root, source["path"], source["format"])
122+
end
123+
105124
# catalog (docs/register-catalog.md) の register summary。
106125
def register_summary
107126
catalog_path = File.join(@root, "generated", "catalog.json")

scripts/tests/build-test.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,13 @@ grep -q "ドキュメントは日本語" "$claude_instr" \
238238
[ ! -d "$tmp/instr/generated/claude-code/skills" ] \
239239
|| fail "instruction must not be generated as a skill"
240240

241+
# --- case 4d: --prune は instruction asset が消えた generated も削除する ---
242+
rm "$tmp/instr/shared/instructions/personal-ops.md" "$tmp/instr/shared/instructions/personal-ops.asset.yml"
243+
"$build" --root "$tmp/instr" --prune > "$tmp/out-iprune" 2>&1 || fail "instruction prune should pass: $(cat "$tmp/out-iprune")"
244+
grep -q "pruned: generated/codex/instructions/AGENTS.md" "$tmp/out-iprune" || fail "instruction not pruned: $(cat "$tmp/out-iprune")"
245+
[ ! -e "$tmp/instr/generated/codex/instructions/AGENTS.md" ] || fail "orphan instruction should be removed"
246+
[ ! -e "$tmp/instr/generated/claude-code/instructions/CLAUDE.md" ] || fail "orphan claude instruction should be removed"
247+
241248
# --- case 5: repository 本体が build できる ---
242249
repo_root=$(CDPATH= cd -- "$script_dir/../.." && pwd)
243250
"$build" --root "$repo_root" --quiet > "$tmp/out-repo" 2>&1 \

scripts/tests/status-test.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,33 @@ repo_root=$(CDPATH= cd -- "$script_dir/../.." && pwd)
129129
"$status_sh" --root "$repo_root" --json > "$tmp/s9" 2>&1 || fail "repo status should succeed"
130130
[ "$(jget "$tmp/s9" repo present)" = "true" ] || fail "repo.present should be true"
131131

132+
# --- case 10: instruction の generated も generated.total に数える ---
133+
mkdir -p "$tmp/irepo/shared/instructions" "$tmp/icodex" "$tmp/iclaude"
134+
cat > "$tmp/irepo/shared/instructions/personal-ops.md" <<'EOF'
135+
# ops
136+
EOF
137+
cat > "$tmp/irepo/shared/instructions/personal-ops.asset.yml" <<'EOF'
138+
schema_version: 1
139+
name: personal-ops
140+
kind: instruction
141+
visibility: public
142+
targets:
143+
- codex
144+
- claude-code
145+
risk:
146+
prompt_injection: low
147+
privacy: low
148+
source:
149+
path: shared/instructions/personal-ops.md
150+
format: markdown
151+
EOF
152+
"$build" --root "$tmp/irepo" --quiet > /dev/null
153+
"$status_sh" --root "$tmp/irepo" --codex-home "$tmp/icodex" --claude-home "$tmp/iclaude" --json > "$tmp/is10" 2>&1
154+
[ "$(jget "$tmp/is10" generated total)" = "2" ] || fail "instruction generated should count (2 targets): $(cat "$tmp/is10")"
155+
[ "$(jget "$tmp/is10" generated stale)" = "0" ] || fail "fresh instruction should not be stale"
156+
# source 変更で instruction も stale になる
157+
echo "changed" >> "$tmp/irepo/shared/instructions/personal-ops.md"
158+
"$status_sh" --root "$tmp/irepo" --codex-home "$tmp/icodex" --claude-home "$tmp/iclaude" --json > "$tmp/is10b" 2>&1
159+
[ "$(jget "$tmp/is10b" generated stale)" = "2" ] || fail "changed instruction source should be stale: $(cat "$tmp/is10b")"
160+
132161
echo "ok: status self-test passed"

0 commit comments

Comments
 (0)