@tyhld/conductor 0.12.85 → 0.12.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/dist/autoupdate.js +643 -0
- package/dist/cli.js +7 -1
- package/dist/headless.js +157 -10
- package/dist/relay.js +196 -0
- package/dist/selfcheck.js +94 -10
- package/package.json +3 -1
- package/sales-template/install.sh +22 -2
- package/sales-template/setup.sh +201 -0
- package/scripts/autoupdate-install.sh +285 -0
- package/scripts/conductor-run.sh +3 -1
- package/scripts/dist-source.sh +161 -12
- package/scripts/hooks/policy.py +84 -2
- package/scripts/hooks/skill_sync_notify.py +19 -1
- package/scripts/hooks/test_policy.py +64 -0
- package/scripts/hooks/test_skill_sync_notify.py +26 -0
- package/scripts/tyhld-autoupdate.sh +61 -0
- package/scripts/update-guard.sh +29 -7
- package/skills/conductor-craftsman/SKILL.md +13 -5
- package/systemd/build-runner-install.sh +1 -1
- package/systemd/ear-install.sh +1 -1
- package/systemd/guard-sync-install.sh +1 -1
- package/systemd/tyhld-autoupdate-boot.service +27 -0
- package/systemd/tyhld-autoupdate.service +24 -0
- package/systemd/tyhld-autoupdate.timer +20 -0
- package/systemd/tyhld-selfcheck.service +1 -1
- package/systemd/tyhld-selfcheck.timer +1 -1
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# autoupdate-install.sh — 自社PCに「自動更新」を入れる(便 29babc5d・設計 3ed9e2b2 の A3)。
|
|
4
|
+
#
|
|
5
|
+
# 【誰が・いつ打つか】★人が、自社PCの更新の窓で1回だけ打つ(えふさん確定 2026-10-02)。
|
|
6
|
+
# まず確認だけ: bash "$(npm root -g)/@tyhld/conductor/scripts/autoupdate-install.sh"
|
|
7
|
+
# 入れる: APPLY=1 bash "$(npm root -g)/@tyhld/conductor/scripts/autoupdate-install.sh"
|
|
8
|
+
# ★sudo は使わない(要る物は「人が打つ1行」として出して止まる)。何度打っても同じ結果になる。
|
|
9
|
+
#
|
|
10
|
+
# 【★お客さまの機には入れない】sales-template/install.sh・setup.sh はこの台本を呼ばない
|
|
11
|
+
# (自社で実績を取ってから、販売版の受入条件として別に判断する=えふさん確定 2026-10-02)。
|
|
12
|
+
#
|
|
13
|
+
# 【やること】
|
|
14
|
+
# ⓪ 確かめ … systemd(ユーザー)が使える/入っている采配くん(公開済みの配布物)/接続設定/
|
|
15
|
+
# 囲いの部品(bwrap・socat)/Claude Code の入り方
|
|
16
|
+
# ★Claude Code が公式推奨の1方式(https://claude.ai/install.sh のネイティブ導入)だけになっていなければ、
|
|
17
|
+
# 人が打つ1行を出して止まる(★この台本は自分で外さない・入れない)。
|
|
18
|
+
# ① 鍵 … ~/.tyhld/bin/autoupdate/enabled(★職人が書けない場所。これが在る機だけ更新係が動く)
|
|
19
|
+
# ② 入口 … ~/.tyhld/bin/tyhld-autoupdate・tyhld-selfcheck(+常駐の入口一式・隣に dist-source.sh)
|
|
20
|
+
# ③ systemd … tyhld-autoupdate-boot.service(起動時1回)・tyhld-autoupdate.timer(数分ごと)・
|
|
21
|
+
# tyhld-selfcheck.timer(毎日)。常駐(conductor@・耳・司令塔)は起動時の1回の後に起きる
|
|
22
|
+
# (常駐の側に After= の drop-in を置く)。撤去の台帳にも載せる。
|
|
23
|
+
# ④ Claude Code 自身の自動更新を止める … ~/.claude/settings.json の env に DISABLE_AUTOUPDATER=1
|
|
24
|
+
# (控えを取ってから。★DISABLE_UPDATES は使わない=人の claude update まで塞ぐため)。
|
|
25
|
+
# ★上げるのは更新係1つだけ(管制の目標の版を、公式の台本で名指しして入れる)。
|
|
26
|
+
# ⑤ Windows 側で打つ2行を表示する(WSL を起こして居続けさせる近道・AC 接続時に眠らせない)。
|
|
27
|
+
#
|
|
28
|
+
# 【試験用の入口】★安全境界ではない(ほかの CONDUCTOR_* と同じ)。
|
|
29
|
+
# AUTOUPDATE_SYSTEM_BIN_DIRS … ホームの外の claude の置き場(既定 "/usr/local/bin /usr/bin")
|
|
30
|
+
# CONDUCTOR_PROC_VERSION_FILE … WSL かどうかを見る見本(既定 /proc/version)
|
|
31
|
+
# AUTOUPDATE_SKIP_SYSTEMD_CHECK=1 … systemd の有無の確かめを飛ばす(作り物の systemctl で流すとき)
|
|
32
|
+
set -uo pipefail
|
|
33
|
+
|
|
34
|
+
APPLY="${APPLY:-0}"
|
|
35
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
36
|
+
. "$HERE/dist-source.sh"
|
|
37
|
+
|
|
38
|
+
UNIT_DIR="$HOME/.config/systemd/user"
|
|
39
|
+
BIN_DIR="$HOME/.tyhld/bin"
|
|
40
|
+
AU_DIR="$BIN_DIR/autoupdate"
|
|
41
|
+
KEY="$AU_DIR/enabled"
|
|
42
|
+
LEDGER_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/conductor"
|
|
43
|
+
LEDGER_FILE="$LEDGER_DIR/installed-units.txt"
|
|
44
|
+
CLAUDE_SETTINGS="$HOME/.claude/settings.json"
|
|
45
|
+
SYSTEM_BIN_DIRS="${AUTOUPDATE_SYSTEM_BIN_DIRS:-/usr/local/bin /usr/bin}"
|
|
46
|
+
UNITS=(tyhld-autoupdate-boot.service tyhld-autoupdate.service tyhld-autoupdate.timer tyhld-selfcheck.service tyhld-selfcheck.timer)
|
|
47
|
+
ORDER_DROPIN_NAME='10-tyhld-autoupdate.conf'
|
|
48
|
+
RELAY_UNITS=(conductor@.service conductor-ear.service conductor-build-runner.service)
|
|
49
|
+
|
|
50
|
+
info() { echo "[autoupdate-install] $*"; }
|
|
51
|
+
warn() { echo "[autoupdate-install] 注意: $*" >&2; }
|
|
52
|
+
|
|
53
|
+
NG=() # 止まる理由(人が打つ1行つき)
|
|
54
|
+
ng() { NG+=("$1"); }
|
|
55
|
+
|
|
56
|
+
if [[ "$APPLY" == "1" ]]; then info "モード: 適用"; else info "モード: 確認のみ(1バイトも書きません。入れるには APPLY=1 を付けて打ってください)"; fi
|
|
57
|
+
|
|
58
|
+
# ─── ⓪ 確かめ ───────────────────────────────────────────────────────────────
|
|
59
|
+
info "⓪ 確かめ"
|
|
60
|
+
|
|
61
|
+
# systemd(ユーザー)
|
|
62
|
+
if [[ "${AUTOUPDATE_SKIP_SYSTEMD_CHECK:-0}" != "1" ]]; then
|
|
63
|
+
if ! command -v systemctl >/dev/null 2>&1 || ! systemctl --user show-environment >/dev/null 2>&1; then
|
|
64
|
+
ng "systemd(ユーザー)が使えません。WSL なら /etc/wsl.conf に [boot] systemd=true が要ります(人の手番・CC1 に知らせてください)"
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# 入っている采配くん(★公開済みの配布物。作業ツリーは使わない)
|
|
69
|
+
if dist_runtime_resolve; then
|
|
70
|
+
info " 采配くん: $DIST_ROOT(版 $DIST_VERSION)"
|
|
71
|
+
else
|
|
72
|
+
ng "動かす采配くんを確かめられません(${DIST_REASON})。
|
|
73
|
+
npm install -g @tyhld/conductor --@tyhld:registry=https://registry.npmjs.org"
|
|
74
|
+
fi
|
|
75
|
+
for f in scripts/tyhld-autoupdate.sh scripts/tyhld-selfcheck.sh systemd/tyhld-autoupdate-boot.service systemd/tyhld-autoupdate.service systemd/tyhld-autoupdate.timer systemd/tyhld-selfcheck.service systemd/tyhld-selfcheck.timer; do
|
|
76
|
+
if [[ -n "$DIST_ROOT" && ! -f "$DIST_ROOT/$f" ]]; then
|
|
77
|
+
ng "入っている采配くん(版 ${DIST_VERSION:-不明})に $f がありません=自動更新が入る前の版です。
|
|
78
|
+
npm install -g @tyhld/conductor --@tyhld:registry=https://registry.npmjs.org"
|
|
79
|
+
break
|
|
80
|
+
fi
|
|
81
|
+
done
|
|
82
|
+
|
|
83
|
+
# 接続設定(管制に目標の版を聞く・自己診断を届けるのに要る)。★中身は読まない。
|
|
84
|
+
if [[ -f "$HOME/.conductor.env" ]]; then
|
|
85
|
+
info " 接続設定: $HOME/.conductor.env が在ります"
|
|
86
|
+
else
|
|
87
|
+
ng "接続設定($HOME/.conductor.env)がありません。この機は采配くんの常駐が入っていません(CC1 に知らせてください)"
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# 囲いの部品(無いと職人が起動を断る)
|
|
91
|
+
if [[ "$(uname -s 2>/dev/null)" == "Linux" ]]; then
|
|
92
|
+
missing=()
|
|
93
|
+
for b in bwrap socat; do command -v "$b" >/dev/null 2>&1 || missing+=("$b"); done
|
|
94
|
+
if (( ${#missing[@]} > 0 )); then
|
|
95
|
+
ng "囲いの部品がありません(${missing[*]})。
|
|
96
|
+
sudo apt-get install -y bubblewrap socat"
|
|
97
|
+
else
|
|
98
|
+
info " 囲いの部品: bwrap・socat が在ります"
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# Claude Code の入り方(★公式推奨=ネイティブ導入の1方式だけにそろえる・えふさん確定 2026-10-02 夜)
|
|
103
|
+
# 公式(code.claude.com/docs/en/setup)で推奨と明記されているのは Native Install (Recommended)。
|
|
104
|
+
# ネイティブ導入は ~/.local/bin/claude → ~/.local/share/claude/versions/<版>。
|
|
105
|
+
# ★ほかの入り方が残っていると、中継がいちばん新しい方を選び、更新係が戻せない(設計 3ed9e2b2 の発見4)。
|
|
106
|
+
NATIVE_LINK="$HOME/.local/bin/claude"
|
|
107
|
+
NATIVE_DIR="$HOME/.local/share/claude/versions"
|
|
108
|
+
native_ok=0
|
|
109
|
+
if [[ -e "$NATIVE_LINK" ]]; then
|
|
110
|
+
real="$(readlink -f "$NATIVE_LINK" 2>/dev/null || printf '%s' "$NATIVE_LINK")"
|
|
111
|
+
[[ "$real" == "$NATIVE_DIR/"* ]] && native_ok=1
|
|
112
|
+
fi
|
|
113
|
+
others=()
|
|
114
|
+
if [[ -d "$HOME/.nvm/versions/node" ]]; then
|
|
115
|
+
for d in "$HOME"/.nvm/versions/node/*/bin; do
|
|
116
|
+
[[ -e "$d/claude" ]] && others+=("$d/claude")
|
|
117
|
+
done
|
|
118
|
+
fi
|
|
119
|
+
[[ -e "$HOME/.claude/local/claude" ]] && others+=("$HOME/.claude/local/claude")
|
|
120
|
+
for d in $SYSTEM_BIN_DIRS; do
|
|
121
|
+
[[ -e "$d/claude" ]] && others+=("$d/claude")
|
|
122
|
+
done
|
|
123
|
+
if (( native_ok == 0 )) && [[ -e "$NATIVE_LINK" ]]; then
|
|
124
|
+
others+=("$NATIVE_LINK")
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
claude_lines=()
|
|
128
|
+
if (( native_ok == 0 )); then
|
|
129
|
+
claude_lines+=("curl -fsSL https://claude.ai/install.sh | bash -s stable")
|
|
130
|
+
fi
|
|
131
|
+
for c in "${others[@]}"; do
|
|
132
|
+
real="$(readlink -f "$c" 2>/dev/null || printf '%s' "$c")"
|
|
133
|
+
case "$c" in
|
|
134
|
+
"$HOME"/.nvm/versions/node/*/bin/claude)
|
|
135
|
+
claude_lines+=("PATH=\"$(dirname "$c"):\$PATH\" npm uninstall -g @anthropic-ai/claude-code")
|
|
136
|
+
;;
|
|
137
|
+
"$HOME/.claude/local/claude")
|
|
138
|
+
claude_lines+=("rm -r \"\$HOME/.claude/local\"")
|
|
139
|
+
;;
|
|
140
|
+
"$NATIVE_LINK")
|
|
141
|
+
claude_lines+=("rm \"\$HOME/.local/bin/claude\"")
|
|
142
|
+
;;
|
|
143
|
+
*)
|
|
144
|
+
if [[ "$real" == */lib/node_modules/@anthropic-ai/claude-code/* ]]; then
|
|
145
|
+
prefix="${real%%/lib/node_modules/*}"
|
|
146
|
+
if [[ -x "$prefix/bin/npm" ]]; then
|
|
147
|
+
claude_lines+=("sudo \"$prefix/bin/npm\" uninstall -g @anthropic-ai/claude-code")
|
|
148
|
+
else
|
|
149
|
+
claude_lines+=("sudo rm \"$c\"" "sudo rm -r \"$prefix/lib/node_modules/@anthropic-ai/claude-code\"")
|
|
150
|
+
fi
|
|
151
|
+
else
|
|
152
|
+
ng "Claude Code の入り方を見分けられない物があります: $c(実体 $real)。この台本は外しません。CC1 に知らせてください。
|
|
153
|
+
ls -l \"$c\""
|
|
154
|
+
fi
|
|
155
|
+
;;
|
|
156
|
+
esac
|
|
157
|
+
done
|
|
158
|
+
if (( ${#claude_lines[@]} > 0 )); then
|
|
159
|
+
msg="Claude Code が公式推奨の1方式(ネイティブ導入)だけになっていません(ネイティブ導入: $([[ $native_ok == 1 ]] && echo 在る || echo 無い)・ほか: ${others[*]:-無し})。
|
|
160
|
+
★この台本は自分で外さない・入れない。上から順に打ってから、もう一度この台本を打ってください:"
|
|
161
|
+
for l in "${claude_lines[@]}"; do msg+=$'\n'" $l"; done
|
|
162
|
+
ng "$msg"
|
|
163
|
+
else
|
|
164
|
+
info " Claude Code: 公式推奨の1方式だけです($(readlink -f "$NATIVE_LINK" 2>/dev/null))"
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
# ─── ⑤ Windows 側で打つ2行(★止まるときも先に見せる)────────────────────────────
|
|
168
|
+
PROC="${CONDUCTOR_PROC_VERSION_FILE:-/proc/version}"
|
|
169
|
+
windows_lines() {
|
|
170
|
+
local distro="${WSL_DISTRO_NAME:-Ubuntu}"
|
|
171
|
+
echo
|
|
172
|
+
info "⑤ Windows の PowerShell で打つ2行(管理者でなくてよい・1回だけ):"
|
|
173
|
+
echo " 1) ログオンのときに WSL(${distro})を起こし、最小化の窓で居続けさせる近道を置く"
|
|
174
|
+
printf '%s\n' "\$s=(New-Object -ComObject WScript.Shell).CreateShortcut(\"\$env:APPDATA\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\tyhld-wsl.lnk\");\$s.TargetPath=\"\$env:WINDIR\\System32\\wsl.exe\";\$s.Arguments=\"-d ${distro} --exec /bin/sleep infinity\";\$s.WindowStyle=7;\$s.Save()"
|
|
175
|
+
echo " 2) 電源(AC)につないでいるあいだは眠らせない"
|
|
176
|
+
echo "powercfg /change standby-timeout-ac 0"
|
|
177
|
+
echo " ★その窓(最小化の wsl)を閉じると WSL が止まり得ます。Windows のログオンは人のままです。"
|
|
178
|
+
}
|
|
179
|
+
IS_WSL=0
|
|
180
|
+
if [[ -r "$PROC" ]] && grep -qi microsoft "$PROC"; then IS_WSL=1; fi
|
|
181
|
+
|
|
182
|
+
if (( ${#NG[@]} > 0 )); then
|
|
183
|
+
echo
|
|
184
|
+
warn "足りない物があります(★1バイトも書いていません)。上から順に直してから、もう一度打ってください:"
|
|
185
|
+
for m in "${NG[@]}"; do echo " ✗ $m" >&2; done
|
|
186
|
+
(( IS_WSL == 1 )) && windows_lines
|
|
187
|
+
exit 1
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
# ─── ①〜④ 入れる ───────────────────────────────────────────────────────────
|
|
191
|
+
if [[ "$APPLY" != "1" ]]; then
|
|
192
|
+
info "①(予定)鍵を置きます: $KEY"
|
|
193
|
+
info "②(予定)入口を置きます: $BIN_DIR/tyhld-autoupdate・tyhld-selfcheck(配布物 $DIST_ROOT から)"
|
|
194
|
+
info "③(予定)systemd: ${UNITS[*]}(起動時の1回・数分ごと・毎日の自己診断)"
|
|
195
|
+
info " (予定)常駐(${RELAY_UNITS[*]})を起動時の1回の後に起こす drop-in"
|
|
196
|
+
info "④(予定)$CLAUDE_SETTINGS の env に DISABLE_AUTOUPDATER=1(控えを取ってから)"
|
|
197
|
+
(( IS_WSL == 1 )) && windows_lines
|
|
198
|
+
echo
|
|
199
|
+
info "確認だけで終わりました。入れるには: APPLY=1 bash \"\$(npm root -g)/@tyhld/conductor/scripts/autoupdate-install.sh\""
|
|
200
|
+
exit 0
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
ledger_add() {
|
|
204
|
+
mkdir -p "$LEDGER_DIR"
|
|
205
|
+
touch "$LEDGER_FILE"
|
|
206
|
+
grep -qxF "$1" "$LEDGER_FILE" || echo "$1" >> "$LEDGER_FILE"
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
# ① 鍵
|
|
210
|
+
mkdir -p "$AU_DIR"
|
|
211
|
+
if [[ ! -f "$KEY" ]]; then
|
|
212
|
+
printf '%s installed-by=autoupdate-install.sh version=%s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z')" "$DIST_VERSION" > "$KEY"
|
|
213
|
+
fi
|
|
214
|
+
info "① 鍵: $KEY"
|
|
215
|
+
|
|
216
|
+
# ② 入口(常駐の入口一式+更新係・自己診断)
|
|
217
|
+
if ! dist_runtime_bundle_install "$DIST_ROOT"; then
|
|
218
|
+
warn "常駐の入口を置けませんでした: $BIN_DIR(★ここで止めます)"
|
|
219
|
+
exit 1
|
|
220
|
+
fi
|
|
221
|
+
dist_runtime_hint_write "$DIST_ROOT"
|
|
222
|
+
for pair in "scripts/tyhld-autoupdate.sh:tyhld-autoupdate" "scripts/tyhld-selfcheck.sh:tyhld-selfcheck"; do
|
|
223
|
+
from="$DIST_ROOT/${pair%%:*}"; to="$BIN_DIR/${pair##*:}"
|
|
224
|
+
cp -f "$from" "$to.tmp" && mv -f "$to.tmp" "$to" && chmod 755 "$to" && cmp -s "$from" "$to" || {
|
|
225
|
+
warn "入口を置けませんでした: $to(★ここで止めます)"; exit 1; }
|
|
226
|
+
done
|
|
227
|
+
info "② 入口: $BIN_DIR/tyhld-autoupdate・$BIN_DIR/tyhld-selfcheck(配布物 $DIST_ROOT・版 $DIST_VERSION)"
|
|
228
|
+
|
|
229
|
+
# ③ systemd
|
|
230
|
+
mkdir -p "$UNIT_DIR"
|
|
231
|
+
for u in "${UNITS[@]}"; do
|
|
232
|
+
cp -f "$DIST_ROOT/systemd/$u" "$UNIT_DIR/$u"
|
|
233
|
+
done
|
|
234
|
+
for r in "${RELAY_UNITS[@]}"; do
|
|
235
|
+
mkdir -p "$UNIT_DIR/$r.d"
|
|
236
|
+
printf '%s\n' \
|
|
237
|
+
'# tyhld: 常駐は、起動時の更新係(tyhld-autoupdate-boot)の後に起きる(autoupdate-install.sh が置いた)。' \
|
|
238
|
+
'# ★After= だけ(更新係が失敗しても常駐は起きる)。' \
|
|
239
|
+
'[Unit]' \
|
|
240
|
+
'After=tyhld-autoupdate-boot.service' > "$UNIT_DIR/$r.d/$ORDER_DROPIN_NAME"
|
|
241
|
+
done
|
|
242
|
+
systemctl --user daemon-reload
|
|
243
|
+
systemctl --user enable tyhld-autoupdate-boot.service >/dev/null 2>&1 || warn "enable に失敗しました: tyhld-autoupdate-boot.service"
|
|
244
|
+
for t in tyhld-autoupdate.timer tyhld-selfcheck.timer; do
|
|
245
|
+
systemctl --user enable --now "$t" >/dev/null 2>&1 || warn "enable --now に失敗しました: $t"
|
|
246
|
+
done
|
|
247
|
+
for u in "${UNITS[@]}"; do ledger_add "$u"; done
|
|
248
|
+
loginctl enable-linger "${USER:-$(id -un)}" >/dev/null 2>&1 || true
|
|
249
|
+
info "③ systemd: ${UNITS[*]}(常駐は起動時の1回の後に起きます)"
|
|
250
|
+
|
|
251
|
+
# ④ Claude Code 自身の自動更新を止める(★上げるのは更新係1つだけ)
|
|
252
|
+
mkdir -p "$(dirname "$CLAUDE_SETTINGS")"
|
|
253
|
+
if [[ -f "$CLAUDE_SETTINGS" ]]; then
|
|
254
|
+
bak="$CLAUDE_SETTINGS.bak.$(date '+%Y%m%d-%H%M%S')"
|
|
255
|
+
cp -p "$CLAUDE_SETTINGS" "$bak"
|
|
256
|
+
info "④ 控え: $bak"
|
|
257
|
+
fi
|
|
258
|
+
if ! python3 - "$CLAUDE_SETTINGS" <<'PY'
|
|
259
|
+
import json, os, sys
|
|
260
|
+
p = sys.argv[1]
|
|
261
|
+
d = {}
|
|
262
|
+
if os.path.exists(p):
|
|
263
|
+
with open(p, encoding='utf-8') as f:
|
|
264
|
+
d = json.load(f)
|
|
265
|
+
if not isinstance(d, dict):
|
|
266
|
+
sys.exit(3)
|
|
267
|
+
env = d.setdefault('env', {})
|
|
268
|
+
if not isinstance(env, dict):
|
|
269
|
+
sys.exit(3)
|
|
270
|
+
env['DISABLE_AUTOUPDATER'] = '1'
|
|
271
|
+
tmp = p + '.tmp'
|
|
272
|
+
with open(tmp, 'w', encoding='utf-8') as f:
|
|
273
|
+
json.dump(d, f, ensure_ascii=False, indent=2)
|
|
274
|
+
f.write('\n')
|
|
275
|
+
os.replace(tmp, p)
|
|
276
|
+
PY
|
|
277
|
+
then
|
|
278
|
+
warn "$CLAUDE_SETTINGS を読めない形なので、DISABLE_AUTOUPDATER を書けませんでした(控えは上)。CC1 に知らせてください"
|
|
279
|
+
exit 1
|
|
280
|
+
fi
|
|
281
|
+
info "④ Claude Code 自身の自動更新を止めました($CLAUDE_SETTINGS の env.DISABLE_AUTOUPDATER=1)"
|
|
282
|
+
|
|
283
|
+
(( IS_WSL == 1 )) && windows_lines
|
|
284
|
+
echo
|
|
285
|
+
info "入りました。続けて自己診断を1回流してください: ~/.tyhld/bin/tyhld-selfcheck --now"
|
package/scripts/conductor-run.sh
CHANGED
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
# systemd は Restart=always なので、入れ直せば次の起動で自動的に戻る。
|
|
31
31
|
#
|
|
32
32
|
# 【★公開 npm には問い合わせない】常駐は回線が切れても動き続ける必要がある。
|
|
33
|
-
#
|
|
33
|
+
# 版をそろえるのは、自動更新が入っている機(自社PC)では更新係(tyhld-autoupdate・便 29babc5d)、
|
|
34
|
+
# 入っていない機では毎時同期が見て知らせる(同じ見張りを2つ作らない)。
|
|
35
|
+
# ★ここで出す「人の手番」は、配布物そのものが無い・欠けている(=更新係も動けない)ときだけ。
|
|
34
36
|
#
|
|
35
37
|
# 【置き場】この入口は ~/.tyhld/bin/conductor-run として置かれ、隣に dist-source.sh と
|
|
36
38
|
# skill_sync_notify.py を置く(導入処理と毎時同期が配布物から写す)。
|
package/scripts/dist-source.sh
CHANGED
|
@@ -20,8 +20,16 @@
|
|
|
20
20
|
#
|
|
21
21
|
# 【この部品が決めること】★これだけ
|
|
22
22
|
# ・配ってよい中身の置き場(DIST_ROOT)=この機に入っている @tyhld/conductor の配布物
|
|
23
|
-
#
|
|
24
|
-
#
|
|
23
|
+
# ・それが「公開されている正規の版で、管制で不具合の印が無い版」か(★便 29babc5d で変えた)
|
|
24
|
+
# 以前は「入っている版 == 公開 npm のいまの版(latest)」だけを配っていた。自社PCの段階配布では
|
|
25
|
+
# 後続の機が数時間 latest より古い(正規の)版のまま動くので、そのあいだ番人が止まってしまう。
|
|
26
|
+
# 【えふさん確定(2026-10-02 夜・判断の窓1)】「入っている版=公開の最新」→
|
|
27
|
+
# 「公開されている正規の版で、管制で不具合の印が無い版」。
|
|
28
|
+
# 正規の版 … 公開 npm にその版が在り、「使わないで」の印(npm deprecate)が付いていない
|
|
29
|
+
# 管制の印 … 管制の目標の版の口に入っている版を添えて聞く(installedConductorFlag)。
|
|
30
|
+
# "suspect"・"bad" なら配らない。欄が無い・"unknown"・管制に届かないときは
|
|
31
|
+
# 「印が無い」として配る(★番人を配らないほうが危ない)
|
|
32
|
+
# ・違う/確かめられないときは【配らない】と言い切ること(公開 npm に聞けないときも配らない=今までどおり)
|
|
25
33
|
#
|
|
26
34
|
# 【この部品が決めないこと】
|
|
27
35
|
# ・番人の判定・禁止の中身・鉄則の中身(1文字も触らない)
|
|
@@ -36,6 +44,8 @@
|
|
|
36
44
|
# 【試験用の入口】★安全境界ではない(環境変数を置ける人は cp も打てる)。
|
|
37
45
|
# CONDUCTOR_DIST_ROOT … 配布物の置き場を名指しする(試験で偽の配布物を指すため)
|
|
38
46
|
# CONDUCTOR_DIST_PUBLISHED … 公開 npm への問い合わせの代わりに使う値(試験で網を切らないため)
|
|
47
|
+
# CONDUCTOR_DIST_PUBLISHED_VERSIONS … 同じく「公開 npm に在る版」の一覧(空白区切り・既定は上の1つだけ)
|
|
48
|
+
# CONDUCTOR_DIST_DEPRECATED_VERSIONS … 同じく「使わないで」の印が付いた版の一覧(空白区切り)
|
|
39
49
|
|
|
40
50
|
# ★取得先の宣言は install.sh / setup.sh の「registry-pin」正本と同じ形にする。
|
|
41
51
|
# (公開 npm 1本。手元の設定が何であってもここだけは公開 npm を見る=便 9858450b)
|
|
@@ -48,6 +58,7 @@ DIST_ROOT='' # 配ってよい中身の置き場(確かめられたと
|
|
|
48
58
|
DIST_VERSION='' # そこに入っている版
|
|
49
59
|
DIST_PUBLISHED='' # 公開 npm のいまの版
|
|
50
60
|
DIST_REASON='' # 配れないときの理由(1語・記録にそのまま載る)
|
|
61
|
+
DIST_CENTRAL_FLAG='' # 管制の印(ok / suspect / bad / unknown)。配り元を決めたときに入る
|
|
51
62
|
|
|
52
63
|
#: どの道で置き場を見つけたか(override / path / hint / scan / npm)。記録と画面に出す。
|
|
53
64
|
DIST_METHOD=''
|
|
@@ -176,8 +187,9 @@ DIST_NODE=''
|
|
|
176
187
|
# 常駐と耳を動かす置き場を決める(便 81a08d58)。★作業ツリーは選ばない。
|
|
177
188
|
# 返り 0 … DIST_ROOT / DIST_NODE で動かしてよい
|
|
178
189
|
# 返り 1 … 動かしてはいけない(DIST_REASON に理由)
|
|
179
|
-
# ★公開 npm
|
|
180
|
-
#
|
|
190
|
+
# ★公開 npm には問い合わせない。常駐は回線が切れても動き続ける必要がある。
|
|
191
|
+
# 版をそろえるのは、自動更新が入っている機(自社PC)では更新係(tyhld-autoupdate)、
|
|
192
|
+
# 入っていない機では毎時同期(dist_source_resolve)が見て知らせる=同じ見張りを2つ作らない。
|
|
181
193
|
dist_runtime_resolve() {
|
|
182
194
|
DIST_ROOT=''; DIST_NODE=''; DIST_VERSION=''; DIST_PUBLISHED=''; DIST_REASON=''
|
|
183
195
|
if ! dist_pkg_find; then
|
|
@@ -270,17 +282,108 @@ dist_published_version() {
|
|
|
270
282
|
printf '%s' "$DIST_PUBLISHED"
|
|
271
283
|
}
|
|
272
284
|
|
|
285
|
+
#: 入っている版が公開 npm に在るか(yes / no / 空=確かめられない)と、「使わないで」の印の文。
|
|
286
|
+
DIST_INSTALLED_PUBLISHED=''
|
|
287
|
+
DIST_DEPRECATED=''
|
|
288
|
+
|
|
289
|
+
# 入っている版が「公開 npm に在る正規の版」かを確かめる(★便 29babc5d・えふさん確定 2026-10-02 夜)。
|
|
290
|
+
# 公開 npm に版を名指しして聞く(npm view <pkg>@<版> version deprecated --json)。
|
|
291
|
+
# 在る → yes(「使わないで」の印があれば DIST_DEPRECATED に入る)
|
|
292
|
+
# E404 → no(公開されていない=正規の版ではない)
|
|
293
|
+
# ほかの失敗 → 空(確かめられない)
|
|
294
|
+
dist_version_published_query() {
|
|
295
|
+
local root="$1" ver="$2" out rc parsed v
|
|
296
|
+
DIST_INSTALLED_PUBLISHED=''; DIST_DEPRECATED=''
|
|
297
|
+
if [[ -n "${CONDUCTOR_DIST_PUBLISHED+x}" ]]; then
|
|
298
|
+
local list="${CONDUCTOR_DIST_PUBLISHED_VERSIONS:-$CONDUCTOR_DIST_PUBLISHED}"
|
|
299
|
+
[[ -n "$list" ]] || return 0
|
|
300
|
+
DIST_INSTALLED_PUBLISHED='no'
|
|
301
|
+
for v in $list; do [[ "$v" == "$ver" ]] && DIST_INSTALLED_PUBLISHED='yes'; done
|
|
302
|
+
for v in ${CONDUCTOR_DIST_DEPRECATED_VERSIONS:-}; do [[ "$v" == "$ver" ]] && DIST_DEPRECATED='使わないでの印があります'; done
|
|
303
|
+
return 0
|
|
304
|
+
fi
|
|
305
|
+
[[ -n "$DIST_NPM" ]] || DIST_NPM="$(dist_npm_for "$root")" || DIST_NPM=''
|
|
306
|
+
[[ -n "$DIST_NPM" ]] || return 0
|
|
307
|
+
out="$(PATH="$(dirname "$DIST_NPM"):${PATH:-}" "$DIST_NPM" view "$CONDUCTOR_PKG@$ver" version deprecated --json "$CONDUCTOR_REGISTRY_ARG" 2>/dev/null)"
|
|
308
|
+
rc=$?
|
|
309
|
+
parsed="$(printf '%s' "$out" | python3 -c 'import json, sys
|
|
310
|
+
ver, rc = sys.argv[1], int(sys.argv[2])
|
|
311
|
+
try:
|
|
312
|
+
d = json.loads(sys.stdin.read() or "null")
|
|
313
|
+
except Exception:
|
|
314
|
+
d = None
|
|
315
|
+
if rc == 0 and d == ver:
|
|
316
|
+
print("yes|")
|
|
317
|
+
elif rc == 0 and isinstance(d, dict) and d.get("version") == ver:
|
|
318
|
+
dep = d.get("deprecated") or ""
|
|
319
|
+
print("yes|" + (dep if isinstance(dep, str) else "使わないでの印があります").replace("\n", " ").replace("|", "/"))
|
|
320
|
+
elif isinstance(d, dict) and isinstance(d.get("error"), dict) and d["error"].get("code") == "E404":
|
|
321
|
+
print("no|")
|
|
322
|
+
else:
|
|
323
|
+
print("|")' "$ver" "$rc" 2>/dev/null)"
|
|
324
|
+
[[ "$parsed" == *'|'* ]] || return 0
|
|
325
|
+
DIST_INSTALLED_PUBLISHED="${parsed%%|*}"
|
|
326
|
+
DIST_DEPRECATED="${parsed#*|}"
|
|
327
|
+
return 0
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
# 管制に「入っている版に不具合の印が無いか」を聞く(★便 29babc5d)。結果は DIST_CENTRAL_FLAG。
|
|
331
|
+
# GET <管制>/api/conductor/machines/target?machine=<機体名>&conductorVersion=<入っている版>
|
|
332
|
+
# → 答えの installedConductorFlag が "ok" | "suspect" | "bad" | "unknown"。
|
|
333
|
+
# ★欄が無い・届かない・ログイン画面へ回される・接続設定が無い は、すべて unknown(=印が無い)。
|
|
334
|
+
# ★接続設定は環境変数、無ければ ~/.conductor.env の CONDUCTOR_URL・CONDUCTOR_TOKEN・CONDUCTOR_MACHINE
|
|
335
|
+
# の3つだけを読む(★値は画面にも控えにも出さない・この関数の外へ持ち出さない)。
|
|
336
|
+
dist_central_flag_query() {
|
|
337
|
+
local ver="$1" url="${CONDUCTOR_URL:-}" tok="${CONDUCTOR_TOKEN:-}" machine="${CONDUCTOR_MACHINE:-}"
|
|
338
|
+
local envf="${HOME:-}/.conductor.env" line key val got
|
|
339
|
+
DIST_CENTRAL_FLAG='unknown'
|
|
340
|
+
if [[ ( -z "$url" || -z "$tok" ) && -r "$envf" ]]; then
|
|
341
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
342
|
+
line="${line#export }"
|
|
343
|
+
key="${line%%=*}"; val="${line#*=}"
|
|
344
|
+
val="${val%\"}"; val="${val#\"}"; val="${val%\'}"; val="${val#\'}"
|
|
345
|
+
case "$key" in
|
|
346
|
+
CONDUCTOR_URL) [[ -n "$url" ]] || url="$val" ;;
|
|
347
|
+
CONDUCTOR_TOKEN) [[ -n "$tok" ]] || tok="$val" ;;
|
|
348
|
+
CONDUCTOR_MACHINE) [[ -n "$machine" ]] || machine="$val" ;;
|
|
349
|
+
esac
|
|
350
|
+
done < "$envf"
|
|
351
|
+
fi
|
|
352
|
+
[[ -n "$url" && -n "$tok" && -n "$ver" ]] || return 0
|
|
353
|
+
[[ -n "$machine" ]] || machine="$(hostname 2>/dev/null)"
|
|
354
|
+
got="$(DCF_URL="$url" DCF_KEY="$tok" DCF_MACHINE="$machine" DCF_VERSION="$ver" python3 -c 'import json, os, urllib.parse, urllib.request
|
|
355
|
+
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
|
356
|
+
def redirect_request(self, *a, **k):
|
|
357
|
+
return None
|
|
358
|
+
q = urllib.parse.urlencode({"machine": os.environ["DCF_MACHINE"], "conductorVersion": os.environ["DCF_VERSION"]})
|
|
359
|
+
req = urllib.request.Request(os.environ["DCF_URL"].rstrip("/") + "/api/conductor/machines/target?" + q,
|
|
360
|
+
headers={"Authorization": "Bearer " + os.environ["DCF_KEY"]})
|
|
361
|
+
try:
|
|
362
|
+
with urllib.request.build_opener(NoRedirect).open(req, timeout=15) as r:
|
|
363
|
+
if "application/json" in (r.headers.get("content-type") or ""):
|
|
364
|
+
f = json.loads(r.read().decode("utf-8")).get("installedConductorFlag")
|
|
365
|
+
print(f if f in ("ok", "suspect", "bad", "unknown") else "unknown")
|
|
366
|
+
except Exception:
|
|
367
|
+
pass' 2>/dev/null)" || got=''
|
|
368
|
+
case "$got" in ok|suspect|bad|unknown) DIST_CENTRAL_FLAG="$got" ;; *) DIST_CENTRAL_FLAG='unknown' ;; esac
|
|
369
|
+
return 0
|
|
370
|
+
}
|
|
371
|
+
|
|
273
372
|
# 配ってよい中身の置き場を決める。
|
|
274
373
|
# 返り 0 … DIST_ROOT を配り元にしてよい
|
|
275
374
|
# 返り 1 … 配ってはいけない(DIST_REASON に理由)
|
|
276
375
|
# 理由の語(記録にそのまま載る)
|
|
277
|
-
# not-installed
|
|
278
|
-
# no-version
|
|
279
|
-
# no-npm
|
|
280
|
-
# unverifiable
|
|
281
|
-
#
|
|
376
|
+
# not-installed … 配布物がこの機に入っていない
|
|
377
|
+
# no-version … 入っている配布物の版が読めない
|
|
378
|
+
# no-npm … 公開 npm に問い合わせる npm が見つからない(配布物の node の隣にも PATH にも無い)
|
|
379
|
+
# unverifiable … npm は在るが、公開 npm への問い合わせに失敗した(回線・取得先・npm の不調)
|
|
380
|
+
# unpublished … 入っている版が公開 npm に無い(正規の版ではない)
|
|
381
|
+
# deprecated … 入っている版に「使わないで」の印(npm deprecate)が付いている
|
|
382
|
+
# flagged-suspect … 管制で「疑い」の印が付いている版
|
|
383
|
+
# flagged-bad … 管制で「不具合」の印が付いている版
|
|
384
|
+
# ★2026-10-02 まで在った version-drift(公開の最新と違う)は、もう配らない理由ではない(便 29babc5d)。
|
|
282
385
|
dist_source_resolve() {
|
|
283
|
-
DIST_ROOT=''; DIST_VERSION=''; DIST_PUBLISHED=''; DIST_REASON=''
|
|
386
|
+
DIST_ROOT=''; DIST_VERSION=''; DIST_PUBLISHED=''; DIST_REASON=''; DIST_CENTRAL_FLAG=''
|
|
284
387
|
local root
|
|
285
388
|
# ★常駐・耳(dist_runtime_resolve)と同じ探し方を使う=同じ機で別の版を選ばない。
|
|
286
389
|
if ! dist_pkg_find; then
|
|
@@ -300,14 +403,60 @@ dist_source_resolve() {
|
|
|
300
403
|
DIST_REASON="${DIST_PUBLISHED_ERR:-unverifiable}"
|
|
301
404
|
return 1
|
|
302
405
|
fi
|
|
303
|
-
|
|
304
|
-
|
|
406
|
+
# ★公開の最新と同じなら、それだけで公開 npm に在る正規の版(問い合わせを1回で済ませる)。
|
|
407
|
+
if [[ "$DIST_VERSION" == "$DIST_PUBLISHED" ]]; then
|
|
408
|
+
DIST_INSTALLED_PUBLISHED='yes'; DIST_DEPRECATED=''
|
|
409
|
+
else
|
|
410
|
+
dist_version_published_query "$root" "$DIST_VERSION"
|
|
411
|
+
fi
|
|
412
|
+
case "$DIST_INSTALLED_PUBLISHED" in
|
|
413
|
+
yes) ;;
|
|
414
|
+
no) DIST_REASON='unpublished'; return 1 ;;
|
|
415
|
+
*) DIST_REASON='unverifiable'; return 1 ;;
|
|
416
|
+
esac
|
|
417
|
+
if [[ -n "$DIST_DEPRECATED" ]]; then
|
|
418
|
+
DIST_REASON='deprecated'
|
|
305
419
|
return 1
|
|
306
420
|
fi
|
|
421
|
+
dist_central_flag_query "$DIST_VERSION"
|
|
422
|
+
case "$DIST_CENTRAL_FLAG" in
|
|
423
|
+
suspect) DIST_REASON='flagged-suspect'; return 1 ;;
|
|
424
|
+
bad) DIST_REASON='flagged-bad'; return 1 ;;
|
|
425
|
+
esac
|
|
307
426
|
DIST_ROOT="$root"
|
|
308
427
|
return 0
|
|
309
428
|
}
|
|
310
429
|
|
|
430
|
+
# 自動更新が入っている機か(自社PCの鍵 ~/.tyhld/bin/autoupdate/enabled が在るか・便 29babc5d)。
|
|
431
|
+
dist_autoupdate_enabled() {
|
|
432
|
+
[[ -f "${HOME:-}/.tyhld/bin/autoupdate/enabled" ]]
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
# 版がそろっていないときに人が読む「次の1手」を1行ずつ出す(★文言の正本は1か所=ここ)。
|
|
436
|
+
# 【なぜ分けるか(設計 3ed9e2b2 の 3/4「いまの決まりが書いてある場所(11)」)】
|
|
437
|
+
# 自動更新が入っている機(自社PC)は、更新係が管制の決めた版へそろえる=人が npm で入れると
|
|
438
|
+
# 段階配布の順番が崩れる。入っていない機(お客さまの機・自動更新を入れる前の機)だけ、人が入れ直す。
|
|
439
|
+
# 引数: 理由の語(DIST_REASON)。
|
|
440
|
+
dist_human_step() {
|
|
441
|
+
local reason="${1:-}"
|
|
442
|
+
case "$reason" in
|
|
443
|
+
flagged-bad|flagged-suspect)
|
|
444
|
+
echo "入っている版 ${DIST_VERSION:-不明} に、管制で ${reason#flagged-} の印が付いています。"
|
|
445
|
+
if dist_autoupdate_enabled; then
|
|
446
|
+
echo "この機は自動更新が入っています。管制が直した版を決めれば、次の自動更新で入れ替わります(人の操作は要りません)。"
|
|
447
|
+
else
|
|
448
|
+
echo "人の手番: CC1 に知らせてください(直した版が公開されたら npm install -g @tyhld/conductor --@tyhld:registry=https://registry.npmjs.org)。"
|
|
449
|
+
fi
|
|
450
|
+
return 0 ;;
|
|
451
|
+
esac
|
|
452
|
+
if dist_autoupdate_enabled; then
|
|
453
|
+
echo "この機は自動更新が入っています。次の自動更新(管制が決めた版を、作業中の便が終わってから入れます)でそろいます。管制の「PCの状態」で確かめてください。"
|
|
454
|
+
echo " 自動更新が止まっているときだけ CC1 に知らせてください(人が npm で入れると段階配布の順番が崩れます)。"
|
|
455
|
+
else
|
|
456
|
+
echo "人の手番: npm install -g @tyhld/conductor --@tyhld:registry=https://registry.npmjs.org"
|
|
457
|
+
fi
|
|
458
|
+
}
|
|
459
|
+
|
|
311
460
|
# ─── 常駐・耳の入口(便 81a08d58)──────────────────────────────────────────
|
|
312
461
|
#: 入口を置く場所。★node の版が変わっても動かない場所(~/.tyhld の下・職人は書けない)。
|
|
313
462
|
DIST_RUNTIME_BIN_DIR="${DIST_RUNTIME_BIN_DIR:-${HOME:-}/.tyhld/bin}"
|
package/scripts/hooks/policy.py
CHANGED
|
@@ -2147,18 +2147,100 @@ def rm_path_risk(path: str, work_dir: str = None) -> str:
|
|
|
2147
2147
|
return 'safe'
|
|
2148
2148
|
|
|
2149
2149
|
|
|
2150
|
+
# 削除命令の語そのもの(字面を探すための形)。
|
|
2151
|
+
_RM_WORD_RE = re.compile(r'\brm\b')
|
|
2152
|
+
|
|
2153
|
+
# ★探すだけの道具(引数に書かれた「探す言葉」は実行されない)。
|
|
2154
|
+
# 【なぜ要るか(実物 2026-10-02)】職人が鉄則や台本を調べるとき、探す言葉に
|
|
2155
|
+
# 「rm -rf .git」のような字が入る。それを削除命令として読むと、調べる作業が毎回止まる。
|
|
2156
|
+
# ★ここで免じるのは【引用符で囲まれた中に在る字】だけ。囲みの外に在る削除命令は1件も免じない。
|
|
2157
|
+
_SEARCH_ONLY_VERBS = frozenset(_RECURSIVE_GREP_VERBS | _HIDDEN_SEARCH_VERBS)
|
|
2158
|
+
|
|
2159
|
+
# 引用符で囲まれた1かたまり(中に同じ引用符を含まない形)。
|
|
2160
|
+
# ★名前を _QUOTED_RE にしない(上の「プログラムの中の文字列リテラル」と別物)。
|
|
2161
|
+
_QUOTED_SPAN_RE = re.compile(r"'[^']*'|\"[^\"]*\"")
|
|
2162
|
+
|
|
2163
|
+
|
|
2164
|
+
def _blank_quoted(text: str) -> str:
|
|
2165
|
+
"""引用符で囲まれた中身を同じ長さの空白にする(★長さと位置を変えない)。"""
|
|
2166
|
+
return _QUOTED_SPAN_RE.sub(lambda m: ' ' * len(m.group(0)), text)
|
|
2167
|
+
|
|
2168
|
+
|
|
2169
|
+
def _is_search_only(toks) -> bool:
|
|
2170
|
+
"""このセグメントが「探すだけ」の呼び出しか(下位コマンドの形も見る)。"""
|
|
2171
|
+
verb = verb_of(toks)
|
|
2172
|
+
if verb in _SEARCH_ONLY_VERBS:
|
|
2173
|
+
return True
|
|
2174
|
+
# `git grep …`(版管理の道具の探す口)も探すだけ。
|
|
2175
|
+
return bool(toks) and verb == 'git' and any(t in _SEARCH_ONLY_VERBS for t in toks[1:3])
|
|
2176
|
+
|
|
2177
|
+
|
|
2178
|
+
def _rm_only_in_search_words(command: str) -> bool:
|
|
2179
|
+
"""削除命令の字が【探すだけの道具の引用符の中】にだけ在るか。
|
|
2180
|
+
|
|
2181
|
+
★免じるのはこれが True のときだけ。少しでも判断が付かなければ False(=止める側へ倒す)。
|
|
2182
|
+
・引用符の外に削除命令の字が在るセグメントが1つでも在れば False。
|
|
2183
|
+
・引用符の中に在っても、そのセグメントが探すだけの道具でなければ False。
|
|
2184
|
+
・どこにも見つからなければ False(ヒアドキュメントの本文など、
|
|
2185
|
+
引用符を割らない読み方では覆えない字面=安全側)。
|
|
2186
|
+
"""
|
|
2187
|
+
found_in_search_words = False
|
|
2188
|
+
for seg in split_commands(command, bodies=False):
|
|
2189
|
+
has_rm = bool(_RM_WORD_RE.search(seg))
|
|
2190
|
+
if not has_rm:
|
|
2191
|
+
continue
|
|
2192
|
+
if _RM_WORD_RE.search(_blank_quoted(seg)):
|
|
2193
|
+
return False # 囲みの外に在る=本物の削除命令として扱う
|
|
2194
|
+
if not _is_search_only(tokens_of(seg)):
|
|
2195
|
+
return False
|
|
2196
|
+
found_in_search_words = True
|
|
2197
|
+
return found_in_search_words
|
|
2198
|
+
|
|
2199
|
+
|
|
2200
|
+
def _rm_target_risk(token: str, work_dir: str = None) -> str:
|
|
2201
|
+
"""削除の対象1つぶんの危険度。★シェルの飾り付きの字面と素の字面の、危ない方を採る。
|
|
2202
|
+
|
|
2203
|
+
【なぜ両方見るか(実物 2026-10-02)】`$(rm -rf .git)` の対象は `.git)` として取り出され、
|
|
2204
|
+
閉じ括弧が付いているせいで保護対象(.git)に当たらず素通りしていた。
|
|
2205
|
+
飾りを剥がした字面も測り、危ない方を採る=今まで止めていた形は1件も緩まない。
|
|
2206
|
+
"""
|
|
2207
|
+
risks = {rm_path_risk(token, work_dir=work_dir)}
|
|
2208
|
+
bare = token.strip().strip(_REDIRECT_TRIM)
|
|
2209
|
+
if bare != token:
|
|
2210
|
+
risks.add(rm_path_risk(bare, work_dir=work_dir))
|
|
2211
|
+
if 'dangerous' in risks:
|
|
2212
|
+
return 'dangerous'
|
|
2213
|
+
if 'unknown' in risks:
|
|
2214
|
+
return 'unknown'
|
|
2215
|
+
return 'safe'
|
|
2216
|
+
|
|
2217
|
+
|
|
2150
2218
|
def _rm_danger(command: str, work_dir: str = None):
|
|
2219
|
+
"""削除命令の対象を全部見て、危険なものがあれば理由を返す。
|
|
2220
|
+
|
|
2221
|
+
★止める側の読み方(機械的な区切り)は変えない。探すだけの道具の「探す言葉」に
|
|
2222
|
+
当たったときだけ、最後に免じる(_rm_only_in_search_words)。
|
|
2223
|
+
"""
|
|
2224
|
+
hit = _rm_danger_scan(command, work_dir=work_dir)
|
|
2225
|
+
if hit is None:
|
|
2226
|
+
return None
|
|
2227
|
+
if _rm_only_in_search_words(command):
|
|
2228
|
+
return None
|
|
2229
|
+
return hit
|
|
2230
|
+
|
|
2231
|
+
|
|
2232
|
+
def _rm_danger_scan(command: str, work_dir: str = None):
|
|
2151
2233
|
"""再帰的 rm の対象パスを全部見て、危険なものがあれば理由を返す。"""
|
|
2152
2234
|
if work_dir is None:
|
|
2153
2235
|
work_dir = os.environ.get('CONDUCTOR_WORK_DIR') or None
|
|
2154
|
-
for m in
|
|
2236
|
+
for m in _RM_WORD_RE.finditer(command):
|
|
2155
2237
|
seg = _SPLIT_RE.split(command[m.end():], maxsplit=1)[0]
|
|
2156
2238
|
toks = seg.split()
|
|
2157
2239
|
flags = [t for t in toks if t.startswith('-')]
|
|
2158
2240
|
recursive = any(('r' in f.lstrip('-').lower()) or f in ('--recursive',) for f in flags)
|
|
2159
2241
|
paths = [t for t in toks if not t.startswith('-')]
|
|
2160
2242
|
for p in paths:
|
|
2161
|
-
risk =
|
|
2243
|
+
risk = _rm_target_risk(p, work_dir=work_dir)
|
|
2162
2244
|
if risk == 'dangerous':
|
|
2163
2245
|
return f'rm on dangerous path ({p})'
|
|
2164
2246
|
if risk == 'unknown' and recursive:
|