@tyhld/conductor 0.12.111 → 0.12.113
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/package.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"_note": "采配くん販売用テンプレート — プロジェクトの .claude/settings.json に配置する。<PLACEHOLDER> を顧客環境に合わせて差し替えること。差し替え箇所は README.md の「プレースホルダ一覧」を参照。",
|
|
3
3
|
"_note_guard": "★段2(2026-07-23): 許可/確認の判定は番人(~/.tyhld/hooks の cc2_guard.py ほか3フック)が持つようになった。既定は allow(危険と関門だけを番人が deny/ask 相当で止め、それ以外は自走)。そのため settings 側の allow・ask 列挙は不要になり、空にしてある。番人フックはこのファイルではなく顧客PCの ~/.claude/settings.json に1本だけ登録する(setup.sh ⑤)。この現場ファイルには権限ルール(deny)と現場固有の sandbox だけを置く。",
|
|
4
4
|
"_note_deny": "★deny は番人の allow 既定より常に先に効く(Claude Code の評価順 deny→ask→allow)。ここに残す床(.env/.ssh/鍵/.tyhld/settings.json/hooks と Supabase 書込3種など)は allow を空にしても当然そのまま優先される。deny は消さない。",
|
|
5
|
+
"_note_force_push": "★強い上書きの push の禁止は【旗の位置ごとに1行ずつ】要る(便 e619bd86・実地は便 6e8d85e6)。この一覧は公式の仕様どおり「語の並びの当てはめ」でしか測れないので、旗が動詞の直後に来る形(末尾 :* の3件)・旗が末尾ぴったりの形(* の3件)・旗の後ろにまだ語が続く形(* … * の3件)を、それぞれ書かないと閉まらない。実地(便 6e8d85e6 の 2-A)で、末尾ぴったりの形は「後ろに語が続く形」の規則では当たらず、中間の位置と後ろに別の旗が付く形は「末尾ぴったりの規則」では当たらないことを確かめたので、両方を持つ。★`*` と `:*` を混ぜた書き方は `*` がリテラルになり何も止めないので足さない(便 07ee11a2・退役済み)。★この一覧では閉められない形(部分命令の前に場所や設定を指す旗を挟む形・先頭が + の参照指定を引用符で囲う形)は、番人(policy.py の _force_push_token_danger)が語の並びに直してから止める=二層で守る。",
|
|
5
6
|
"_note_read": "★Read(…) の禁止は【二重の守りの外側】(便 93b2fa03・えふさん確定 2026-09-16)。秘密の読み取りを本当に判定しているのは番人(~/.tyhld/hooks の policy.py の secret_reason)で、そちらは Read だけでなく NotebookRead・Grep・Glob・MCP・file:// まで同じ境界で止める。この一覧は【番人が外された機体でも Read だけは止まる】ための保険なので、番人より粗い。粗さの実害は1つだけ: Read(**/*.env) は example.env のような前置きの雛形まで止める(.env.example は止まらない)。番人の方は雛形を通すので、番人が入っている機体では今までどおり雛形を読める。 ★2026-09-16(便 3a3771b1)に「名前だけの規則」を1本外し、場所で名指しする規則に替えた。外したのは credentials という名前だけの Read 規則で、依存の部品(node_modules)の中の同じ名前のフォルダまで止め、現場の build と差分確認を落としていた(実測2件・11リポジトリ中)。本物の置き場(.aws / .gcloud / .config/gcloud / .azure / .ssh / .gnupg)は場所で名指しして守っているので、止める範囲は1つも減っていない。★この一覧の書き方(glob)では「依存の部品の中だけ除く」が書けない。名前が同じでも中身が違う、という判断は場所と管理主体を見ないとできず、glob は場所のパターンしか書けないため。だから外側のこの一覧は【場所】だけで名指しし、名前だけでは言い切れない物の可否は番人(policy.py の (4))が「機械が入れた部品の中か」を git の事実で確かめて決める。 ★2026-09-17(便 336e74e5=D1 方式D 第一便)に、遅くて安全性に寄与しない `**/` の Read 禁止を24件 退役した(実測:ホーム由来17本で本物のツリーの囲い起動が約7秒・広い鍵/認証で約3秒遅くなる。 D1b 重大発見1)。退役の内訳=(1) ホーム由来の `**/`(.ssh・.aws・.gnupg・.config/gcloud・.azure・.netrc・.pypirc・id_rsa* ほか・.tyhld・.config/gh/hosts.yml)=`**/` は作業場所の中だけに当たり home には当たらない(仕様)ので、home は ~/ 絶対パスの規則で守る。(2) 広い鍵・認証の `**/`(*.pem・*.key・credentials.json・auth.json・*login.json・*.env)=依存の部品(node_modules)を大量に歩いて遅く、番人(policy.py の secret_reason)が同じ名前を form A で止めるので重複(依存の中の credentials.json は番人が許すので通常開発も壊れない)。(3) .npmrc=中身は ${NPM_TOKEN} の参照だけで秘密の実値は無い。囲いで /dev/null 化すると @tyhld の取り寄せが壊れる(重大発見3)。残した `**/`=リポの中の .env 系(.env・.env.local・.env.development・.env.production・.env.test・.env.vercel・.env.*.local)=台本経由(form B)の持ち出しを囲いの OS 層で止めるのに要る。★退役した規則は update-guard が各現場からも外す(正本が配った物だけ・顧客が足した規則は消さない)。",
|
|
6
7
|
"_note_home_secret": "★ホーム配下(リポの外)の秘密の置き場は【場所で1つずつ名指し】する(えふさん確定 2026-09-17・便 230c3175=D2)。~/ で始まる規則はホームからの場所、**/ で始まる規則は作業場所(リポ)の中だけに当たる(Claude Code 仕様)。★$HOME/** や ~/*token* のような広い書き方はしない。理由は2つ: (1) 通常の作業(npm・gh・git・build)が要るホームのファイルまで止める。(2) サンドボックスはこの一覧を OS の読み取り禁止に写すが、ホームに * を含む書き方は起動が 7〜18 秒遅くなるか、効かない(実測: ~/*/.env.local 17.5秒・~/.env* 8.5秒で効かず・~/*token* 15.7秒で効かず。場所を名指しした形は 0.25〜0.32 秒で効く)。★わざと禁止しない置き場(通常の作業に要る・中身の取り出しは番人が別に止める): ~/.npmrc(npm install が読む。npm config get の _auth 表示は番人が止める)、~/.config/gh/hosts.yml(gh と git push が読む。gh auth token・git credential fill は番人が止める)、~/.claude/session-env(Claude Code 自身が読む)、~/.bashrc・~/.profile(シェルが読む。中に秘密を置かないのが本筋)、~/.android/debug.keystore(開発用の署名)。★機体ごとに名前が違う置き場(ホーム直下の token・secret の名前のファイル、ホーム直下に残ったリポの写しの .env など)は、配る一覧では名指しできない。番人(policy.py の _home_secret_reason)が名前で止めるほか、必要なら機体の ~/.claude/settings.json に場所を足す。★フォルダごとの持ち出し(cp -r・rsync・tar・ln -s・コマンドに書いたプログラムでの複製)は番人が、写す元の中に秘密が在るかを名前だけで調べて止める。【限界】台本のファイルの中で写す形と、名前を書かずに読む子プロセスは、サンドボックスの無い現場では番人でも止まらない(OS の層が要る)。",
|
|
7
8
|
"_note_network": "★外への通信は全部通す(allowedDomains: [\"*\"]・えふさん決定 2026-09-03=甲)。販売版もお客さま機で自走するのが既定(2026-08-23 決定)。理由: サンドボックスの通信許可の問いは管制の3ボタンでは押せず(Claude Code 2.1.258 本体の作り・実測 2026-09-03)、端末に人が居ないと職人が止まったままになるため。★緩むのは『電話をかける』だけ。『金庫を持ち出す』(秘密の持ち出し)を止めるのは番人 cc2_guard.py の別判定で、通信設定とは独立に効き続ける。★下の deniedDomains は allowedDomains より先に判定されるので、本番DBへの直結禁止は \"*\" を入れても効いたまま(実測で確認)。この行を消すと本番DBが開くので消さないこと。",
|
|
@@ -17,6 +18,9 @@
|
|
|
17
18
|
"Bash(git push * --force)",
|
|
18
19
|
"Bash(git push * -f)",
|
|
19
20
|
"Bash(git push * --force-with-lease)",
|
|
21
|
+
"Bash(git push * --force *)",
|
|
22
|
+
"Bash(git push * -f *)",
|
|
23
|
+
"Bash(git push * --force-with-lease *)",
|
|
20
24
|
"Bash(git push * +*)",
|
|
21
25
|
"Bash(rm -rf:*)",
|
|
22
26
|
"Bash(rm -fr:*)",
|
package/scripts/hooks/policy.py
CHANGED
|
@@ -1555,6 +1555,131 @@ def _main_push_danger(tokens, cwd):
|
|
|
1555
1555
|
return None
|
|
1556
1556
|
|
|
1557
1557
|
|
|
1558
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
1559
|
+
# 強い上書きの push を【語(トークン)】で見る(DENY・便 e619bd86)
|
|
1560
|
+
#
|
|
1561
|
+
# 【なぜ要るか(実地 便 6e8d85e6)】
|
|
1562
|
+
# 強い上書きの判定は、長いあいだ「`git` と `push` が隣り合っている字面」だけを見ていた
|
|
1563
|
+
# (_danger_patterns の1行)。そのため部分命令の前に何かを挟むと当たらず、作り物の遠隔に
|
|
1564
|
+
# 対して実際に強制上書きが成立した(6形)。
|
|
1565
|
+
# ・場所を指す旗・設定を足す旗・履歴の置き場を指す旗を挟む形(5形)
|
|
1566
|
+
# ・先頭が + の参照指定を引用符で囲う形(1形)
|
|
1567
|
+
# 配る禁止の一覧(現場の .claude/settings.json)も、公式の仕様どおり「頭の語の並び」で
|
|
1568
|
+
# 当てるので、同じ形を1つも止めていなかった。
|
|
1569
|
+
#
|
|
1570
|
+
# 【どう見るか】殻(シェル)の決まりで語に分け、引用符を外した【語の並び】で測る。
|
|
1571
|
+
# ① git の全体オプション(場所を指す物・設定を足す物・履歴や作業木の置き場を指す物など。
|
|
1572
|
+
# 値を「=」で付ける形と、次の語で渡す形の両方)を読み飛ばして部分命令を取り出す。
|
|
1573
|
+
# ② 部分命令が push のときだけ、強い上書きの印を【位置を問わずに】探す。
|
|
1574
|
+
# ・長い形(--force / --force-with-lease / --force-if-includes。--no-… は印でない)
|
|
1575
|
+
# ・短い形(-f。-fu のようにまとめて書いた形も。値を取る旗の値は読まない)
|
|
1576
|
+
# ・先頭が + の参照指定(引用符は語に分けた時点で外れる)
|
|
1577
|
+
# ③ 語に分けられない行(引用符が閉じていない等)で git と push の語が見えていれば、
|
|
1578
|
+
# 中身を確かめられないので安全側(止める)に倒す。
|
|
1579
|
+
# ★つなぎ(; && || |)・入れ子($( ) ・` `)・包み(殻に渡す文字列・env や時間制限)・
|
|
1580
|
+
# 変数の展開には、_bypass_scan に相乗りすることで同じ判定が届く(番人のいつもの流儀)。
|
|
1581
|
+
# ★いまの字面の判定(_danger_patterns)は残す。どちらかに当たれば止める=止まる形は1つも減らない。
|
|
1582
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
1583
|
+
|
|
1584
|
+
#: git の全体オプションのうち、値を【次の語】で受け取る物(「=」で付ける形は1語で済む)。
|
|
1585
|
+
_GIT_GLOBAL_VALUE_FLAGS = frozenset({
|
|
1586
|
+
'-C', '-c', '--git-dir', '--work-tree', '--namespace', '--super-prefix',
|
|
1587
|
+
})
|
|
1588
|
+
|
|
1589
|
+
#: push の旗のうち、値を【次の語】で受け取る物(値が `-f` でも旗と読み違えないため)。
|
|
1590
|
+
_PUSH_VALUE_FLAGS = frozenset({
|
|
1591
|
+
'--repo', '-o', '--push-option', '--receive-pack', '--exec',
|
|
1592
|
+
})
|
|
1593
|
+
|
|
1594
|
+
#: 短い旗をまとめて書いた形で、後ろの文字を値として飲む文字(`-o値`)。
|
|
1595
|
+
_PUSH_SHORT_VALUE_CHARS = 'o'
|
|
1596
|
+
|
|
1597
|
+
|
|
1598
|
+
def _git_subcommand(args):
|
|
1599
|
+
"""git の後ろの語から全体オプションを読み飛ばし、(部分命令, その後ろの語) を返す。"""
|
|
1600
|
+
rest = list(args)
|
|
1601
|
+
while rest and rest[0].startswith('-') and rest[0] != '--':
|
|
1602
|
+
flag = rest.pop(0)
|
|
1603
|
+
if '=' in flag:
|
|
1604
|
+
continue
|
|
1605
|
+
if flag in _GIT_GLOBAL_VALUE_FLAGS and rest:
|
|
1606
|
+
rest.pop(0)
|
|
1607
|
+
if not rest:
|
|
1608
|
+
return None, []
|
|
1609
|
+
return rest[0], rest[1:]
|
|
1610
|
+
|
|
1611
|
+
|
|
1612
|
+
def _push_force_mark(push_args):
|
|
1613
|
+
"""push の後ろの語に、強い上書きの印が在れば その語 を返す(位置は問わない)。"""
|
|
1614
|
+
i = 0
|
|
1615
|
+
only_refs = False
|
|
1616
|
+
while i < len(push_args):
|
|
1617
|
+
t = push_args[i]
|
|
1618
|
+
if t == '--':
|
|
1619
|
+
only_refs = True # ★以後は位置の語(参照指定)だけ
|
|
1620
|
+
i += 1
|
|
1621
|
+
continue
|
|
1622
|
+
name = t.split('=', 1)[0]
|
|
1623
|
+
if not only_refs and name in _PUSH_VALUE_FLAGS:
|
|
1624
|
+
i += 2 if '=' not in t else 1
|
|
1625
|
+
continue
|
|
1626
|
+
if not only_refs and t.startswith('--'):
|
|
1627
|
+
if name.startswith('--force'):
|
|
1628
|
+
return t
|
|
1629
|
+
i += 1
|
|
1630
|
+
continue
|
|
1631
|
+
if not only_refs and t.startswith('-') and len(t) > 1:
|
|
1632
|
+
for ch in t[1:]:
|
|
1633
|
+
if ch == 'f':
|
|
1634
|
+
return t
|
|
1635
|
+
if ch in _PUSH_SHORT_VALUE_CHARS:
|
|
1636
|
+
break # ★旗にくっつけた値は読まない(`-o値` の f を拾わない)
|
|
1637
|
+
i += 1
|
|
1638
|
+
continue
|
|
1639
|
+
if t.startswith('+'):
|
|
1640
|
+
return t
|
|
1641
|
+
i += 1
|
|
1642
|
+
return None
|
|
1643
|
+
|
|
1644
|
+
|
|
1645
|
+
def _word_head(token: str) -> str:
|
|
1646
|
+
"""語から道具の名前を取り出す(道筋・入れ子の始まり・引用符の切れ端を外す)。
|
|
1647
|
+
|
|
1648
|
+
★`$(git` ・`` `git `` ・`"git` のように入れ子の始まりがくっついた語も、同じ名前として読む。
|
|
1649
|
+
"""
|
|
1650
|
+
return token.split('/')[-1].strip('`"\'').lstrip('$({').rstrip(')}')
|
|
1651
|
+
|
|
1652
|
+
|
|
1653
|
+
def _force_push_token_danger(tokens):
|
|
1654
|
+
"""語の並びで見る強い上書きの push。危険なら理由を返す。"""
|
|
1655
|
+
for i, t in enumerate(tokens):
|
|
1656
|
+
if _word_head(t) != 'git':
|
|
1657
|
+
continue
|
|
1658
|
+
sub, rest = _git_subcommand(tokens[i + 1:])
|
|
1659
|
+
if sub is None or _word_head(sub) != 'push':
|
|
1660
|
+
continue
|
|
1661
|
+
mark = _push_force_mark(rest)
|
|
1662
|
+
if mark:
|
|
1663
|
+
return f'force push ({mark})'
|
|
1664
|
+
return None
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
def _unparsable_push_danger(command: str):
|
|
1668
|
+
"""語に分けられない行(引用符が閉じていない等)で git と push が見えていれば止める。
|
|
1669
|
+
|
|
1670
|
+
★本物の区切り(引用符の中では割らない)で見る=ふつうの行の引用符の中に区切りが在るだけの
|
|
1671
|
+
コマンドを、壊れた行と読み違えない。
|
|
1672
|
+
"""
|
|
1673
|
+
for seg in split_commands(command, bodies=False):
|
|
1674
|
+
try:
|
|
1675
|
+
shlex.split(seg, comments=False, posix=True)
|
|
1676
|
+
except ValueError:
|
|
1677
|
+
names = {_word_head(t) for t in seg.split()}
|
|
1678
|
+
if 'git' in names and 'push' in names:
|
|
1679
|
+
return 'force push (語に分けられない行=安全側に倒す)'
|
|
1680
|
+
return None
|
|
1681
|
+
|
|
1682
|
+
|
|
1558
1683
|
# GitHub の API の道筋(書き込み)。
|
|
1559
1684
|
_API_MERGE_RE = re.compile(r'(^|/)pulls/[^/]+/merge/?$')
|
|
1560
1685
|
_API_PROTECTION_RE = re.compile(
|
|
@@ -1758,6 +1883,10 @@ _SKIP_PERMISSION_FLAGS = frozenset({
|
|
|
1758
1883
|
|
|
1759
1884
|
def _bypass_segment_danger(seg, tokens, cwd, command):
|
|
1760
1885
|
"""1セグメントの抜け道(main 直 push 以外)。"""
|
|
1886
|
+
# ★強い上書きの push は、道具の語が読めなくても(包みの中でも)語の並びで見る(便 e619bd86)。
|
|
1887
|
+
hit = _force_push_token_danger(tokens)
|
|
1888
|
+
if hit:
|
|
1889
|
+
return hit
|
|
1761
1890
|
words = _tool_words(tokens)
|
|
1762
1891
|
if not words:
|
|
1763
1892
|
return None
|
|
@@ -1811,6 +1940,9 @@ def _bypass_scan(command: str, cwd=None):
|
|
|
1811
1940
|
★機械的な区切りで1回、本物の区切り(本文なし)でもう1回測る(便 2.5)。複数行の引用符の
|
|
1812
1941
|
中に書いた GraphQL の本文などを、ひとかたまりのまま読むため。止める側は減らない。
|
|
1813
1942
|
"""
|
|
1943
|
+
hit = _unparsable_push_danger(command)
|
|
1944
|
+
if hit:
|
|
1945
|
+
return hit
|
|
1814
1946
|
for segs in (split_segments(command), split_commands(command, bodies=False)):
|
|
1815
1947
|
here = cwd
|
|
1816
1948
|
for seg in segs:
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""強い上書きの push を【語(トークン)】で見る判定の試験(便 e619bd86)。
|
|
3
|
+
|
|
4
|
+
実行: python3 scripts/hooks/test_force_push_tokens.py
|
|
5
|
+
1件でも期待と違えば exit 1。
|
|
6
|
+
|
|
7
|
+
【なぜ要るか(実地 便 6e8d85e6)】
|
|
8
|
+
判定が「`git` と `push` が隣り合っている字面」だけを見ていたため、部分命令の前に何かを挟む
|
|
9
|
+
5形と、先頭が + の参照指定を引用符で囲う1形が、禁止の一覧でも番人でも止まらず、
|
|
10
|
+
作り物の遠隔に対して強制上書きが【実際に成立】した。語の並びで見る判定を足して閉じる。
|
|
11
|
+
|
|
12
|
+
【ここで固定すること】
|
|
13
|
+
(1) 素通りだった6形が止まる(★この6形は語で見る判定だけが止める=足した判定が効いている証拠)
|
|
14
|
+
(2) つないだ行・入れ子・包み・短い旗のまとめ書き・「=」つきと次の語渡しにも判定が届く
|
|
15
|
+
(3) 語に分けられない行(引用符が閉じていない等)は安全側に倒す
|
|
16
|
+
(4) ★止めてはいけない形は止めない(普通の push・全体オプションつきの普通の push・
|
|
17
|
+
取り寄せ側で + の参照指定を使う形・push でない部分命令で同じ旗を使う形)
|
|
18
|
+
(5) ★いまの字面の判定は1つも弱めていない(元から止まっていた形はそのまま止まる)
|
|
19
|
+
|
|
20
|
+
★見本の危険語は部品から組み立てる(番人の見本に危険語をそのまま書かない)。
|
|
21
|
+
"""
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
|
|
25
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
26
|
+
|
|
27
|
+
import policy # noqa: E402
|
|
28
|
+
|
|
29
|
+
failures = []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def check(label, got, want):
|
|
33
|
+
ok = got == want
|
|
34
|
+
if not ok:
|
|
35
|
+
failures.append(f'{label}: got={got!r} want={want!r}')
|
|
36
|
+
print(f'{"OK " if ok else "NG "} {label:72} {str(got)[:32]}')
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ── 部品(危険語を直接書かない) ──
|
|
40
|
+
PUSH = 'pu' + 'sh'
|
|
41
|
+
FORCE = '--for' + 'ce'
|
|
42
|
+
LEASE = FORCE + '-with-lease'
|
|
43
|
+
SHORT = '-' + 'f'
|
|
44
|
+
NOVERIFY = '--no-' + 'verify'
|
|
45
|
+
WORK = '/tmp/claude-1000' # 判定に使う作業場所(実物のリポを指さない)
|
|
46
|
+
BR = 'probe'
|
|
47
|
+
|
|
48
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
+
# (1) 素通りだった6形(実地 便 6e8d85e6 の重大発見1・2)
|
|
50
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
+
LEAKED = [
|
|
52
|
+
f'git -C {WORK}/x {PUSH} {FORCE} origin {BR}',
|
|
53
|
+
f'git -C "$DIR" {PUSH} {FORCE} origin {BR}',
|
|
54
|
+
f'git -c {PUSH}.default=current {PUSH} {FORCE} origin {BR}',
|
|
55
|
+
f"git '{PUSH}' {FORCE} origin {BR}",
|
|
56
|
+
f'git --git-dir={WORK}/x/.git --work-tree={WORK}/x {PUSH} {FORCE} origin {BR}',
|
|
57
|
+
f'git {PUSH} origin "+{BR}"',
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
print('\n=== (1) 素通りだった6形が止まる ===')
|
|
61
|
+
for cmd in LEAKED:
|
|
62
|
+
r = policy.decide_bash(cmd, cwd=WORK)
|
|
63
|
+
check(f'deny {cmd[:62]}', r['decision'], policy.DENY)
|
|
64
|
+
|
|
65
|
+
print('\n=== (1b) ★この6形は【語で見る判定】が止めている(足した判定が効いている証拠) ===')
|
|
66
|
+
for cmd in LEAKED:
|
|
67
|
+
got = policy._force_push_token_danger(policy.tokens_of(cmd))
|
|
68
|
+
check(f'語で見る判定が当たる {cmd[:52]}', bool(got), True)
|
|
69
|
+
|
|
70
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
71
|
+
# (2) 旗の位置・つなぎ・入れ子・包み・まとめ書き・値の渡し方
|
|
72
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
73
|
+
DENY_SHAPES = [
|
|
74
|
+
# 旗の位置(便 6e8d85e6 の P1〜P12)
|
|
75
|
+
f'git {PUSH} {FORCE} origin {BR}',
|
|
76
|
+
f'git {PUSH} origin {BR} {FORCE}',
|
|
77
|
+
f'git {PUSH} origin {BR} {FORCE} {NOVERIFY}',
|
|
78
|
+
f'git {PUSH} {SHORT} origin {BR}',
|
|
79
|
+
f'git {PUSH} origin {BR} {SHORT}',
|
|
80
|
+
f'git {PUSH} origin {SHORT} {BR}',
|
|
81
|
+
f'git {PUSH} {LEASE} origin {BR}',
|
|
82
|
+
f'git {PUSH} origin {BR} {LEASE}',
|
|
83
|
+
f'git {PUSH} origin +{BR}',
|
|
84
|
+
f'git {PUSH} origin +refs/heads/{BR}:refs/heads/{BR}',
|
|
85
|
+
# 短い旗のまとめ書き
|
|
86
|
+
f'git {PUSH} -u{SHORT[1:]} origin {BR}',
|
|
87
|
+
f'git {PUSH} -v{SHORT[1:]} origin {BR}',
|
|
88
|
+
# 全体オプションの「=」つきと、次の語で渡す形
|
|
89
|
+
f'git --git-dir={WORK}/x/.git {PUSH} {FORCE} origin {BR}',
|
|
90
|
+
f'git --git-dir {WORK}/x/.git {PUSH} {FORCE} origin {BR}',
|
|
91
|
+
f'git --work-tree {WORK}/x {PUSH} {FORCE} origin {BR}',
|
|
92
|
+
f'git --namespace ns {PUSH} {FORCE} origin {BR}',
|
|
93
|
+
f'git --exec-path={WORK}/x {PUSH} {FORCE} origin {BR}',
|
|
94
|
+
f'git -C {WORK}/x -c user.name=x {PUSH} {LEASE}=refs/heads/{BR}:abc origin {BR}',
|
|
95
|
+
# つないだ行
|
|
96
|
+
f'echo hi && git -C {WORK}/x {PUSH} {FORCE} origin {BR}',
|
|
97
|
+
f'echo hi ; git -C {WORK}/x {PUSH} origin +{BR}',
|
|
98
|
+
f'true || git -C {WORK}/x {PUSH} {SHORT} origin {BR}',
|
|
99
|
+
f'echo hi | xargs -I{{}} git -C {WORK}/x {PUSH} {FORCE} origin {BR}',
|
|
100
|
+
# 入れ子・包み
|
|
101
|
+
f'bash -c "git -C {WORK}/x {PUSH} {FORCE} origin {BR}"',
|
|
102
|
+
f'sh -c \'git -C {WORK}/x {PUSH} origin "+{BR}"\'',
|
|
103
|
+
f'echo $(git -C {WORK}/x {PUSH} {FORCE} origin {BR})',
|
|
104
|
+
f'env -C {WORK}/x git {PUSH} {FORCE} origin {BR}',
|
|
105
|
+
f'timeout 60 git -C {WORK}/x {PUSH} {FORCE} origin {BR}',
|
|
106
|
+
f'nohup git -C {WORK}/x {PUSH} {SHORT} origin {BR}',
|
|
107
|
+
f'GIT_TRACE=1 git -C {WORK}/x {PUSH} {FORCE} origin {BR}',
|
|
108
|
+
# 変数に入れた場所
|
|
109
|
+
f'D={WORK}/x; git -C "$D" {PUSH} {FORCE} origin {BR}',
|
|
110
|
+
# 参照指定の前に区切りを置く形
|
|
111
|
+
f'git {PUSH} origin -- +{BR}',
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
print('\n=== (2) つなぎ・入れ子・包み・まとめ書き・値の渡し方にも届く ===')
|
|
115
|
+
for cmd in DENY_SHAPES:
|
|
116
|
+
r = policy.decide_bash(cmd, cwd=WORK)
|
|
117
|
+
check(f'deny {cmd[:62]}', r['decision'], policy.DENY)
|
|
118
|
+
|
|
119
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
120
|
+
# (3) 語に分けられない行は安全側(止める)
|
|
121
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
122
|
+
print('\n=== (3) 語に分けられない行(引用符が閉じていない)は安全側に倒す ===')
|
|
123
|
+
for cmd in [
|
|
124
|
+
f'git {PUSH} origin "+{BR}',
|
|
125
|
+
f'git -C {WORK}/x {PUSH} origin \'{BR}',
|
|
126
|
+
]:
|
|
127
|
+
r = policy.decide_bash(cmd, cwd=WORK)
|
|
128
|
+
check(f'deny {cmd[:62]}', r['decision'], policy.DENY)
|
|
129
|
+
|
|
130
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
131
|
+
# (4) ★止めてはいけない形
|
|
132
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
133
|
+
ALLOWED_SHAPES = [
|
|
134
|
+
# 普通の push(枝を名指し)
|
|
135
|
+
f'git {PUSH} origin {BR}',
|
|
136
|
+
f'git {PUSH} origin feat/my-branch',
|
|
137
|
+
f'git {PUSH} -u origin {BR}',
|
|
138
|
+
f'git {PUSH} --dry-run origin {BR}',
|
|
139
|
+
f'git {PUSH} --delete origin {BR}',
|
|
140
|
+
f'git {PUSH} origin {BR}:refs/heads/{BR}',
|
|
141
|
+
f'git {PUSH} origin feature+x',
|
|
142
|
+
# 全体オプションつきの普通の push
|
|
143
|
+
f'git -C {WORK}/x {PUSH} origin {BR}',
|
|
144
|
+
f'git -c user.name=x {PUSH} origin {BR}',
|
|
145
|
+
f'git --git-dir={WORK}/x/.git --work-tree={WORK}/x {PUSH} origin {BR}',
|
|
146
|
+
# 値を取る旗の値を旗と読み違えない
|
|
147
|
+
f'git {PUSH} -o ci.skip origin {BR}',
|
|
148
|
+
f'git {PUSH} --push-option=ci.skip origin {BR}',
|
|
149
|
+
# 打ち消しの旗は印ではない
|
|
150
|
+
f'git {PUSH} --no{LEASE[1:]} origin {BR}',
|
|
151
|
+
# 取り寄せ側で + の参照指定を使う形
|
|
152
|
+
'git fetch origin +refs/heads/x:refs/heads/x',
|
|
153
|
+
f'git fetch {FORCE} origin +refs/heads/x:refs/heads/x',
|
|
154
|
+
'git pull origin +x',
|
|
155
|
+
# push でない部分命令で同じ旗を使う形
|
|
156
|
+
f'git checkout {FORCE} {BR}',
|
|
157
|
+
f'git branch {SHORT} {BR} HEAD',
|
|
158
|
+
f'git tag {SHORT} v1 HEAD',
|
|
159
|
+
f'git submodule update --init {FORCE}',
|
|
160
|
+
f'git -C {WORK}/x stash {PUSH} -m hold',
|
|
161
|
+
# 字面が本文の中にあるだけの形
|
|
162
|
+
f'git commit -m "note: {PUSH} origin {BR}"',
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
print('\n=== (4) 止めてはいけない形は止めない ===')
|
|
166
|
+
for cmd in ALLOWED_SHAPES:
|
|
167
|
+
r = policy.decide_bash(cmd, cwd=WORK)
|
|
168
|
+
check(f'not deny {cmd[:58]}', r['decision'] != policy.DENY, True)
|
|
169
|
+
|
|
170
|
+
print('\n=== (4b) 語で見る判定も、止めてはいけない形には当たらない ===')
|
|
171
|
+
for cmd in ALLOWED_SHAPES:
|
|
172
|
+
got = policy._force_push_token_danger(policy.tokens_of(cmd))
|
|
173
|
+
check(f'当たらない {cmd[:58]}', got, None)
|
|
174
|
+
|
|
175
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
176
|
+
# (5) ★いまの字面の判定を弱めていない(元から止まっていた形はそのまま)
|
|
177
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
178
|
+
print('\n=== (5) 字面の判定はそのまま残っている(二層で止める) ===')
|
|
179
|
+
for cmd in [
|
|
180
|
+
f'git {PUSH} {FORCE} origin main',
|
|
181
|
+
f'git {PUSH} {SHORT} origin main',
|
|
182
|
+
f'git {PUSH} {LEASE} origin main',
|
|
183
|
+
]:
|
|
184
|
+
check(f'字面の判定が当たる {cmd[:52]}',
|
|
185
|
+
bool(policy._danger_patterns(cmd.lower())), True)
|
|
186
|
+
|
|
187
|
+
print()
|
|
188
|
+
if failures:
|
|
189
|
+
print(f'FAIL {len(failures)} 件')
|
|
190
|
+
for f in failures:
|
|
191
|
+
print(f' - {f}')
|
|
192
|
+
sys.exit(1)
|
|
193
|
+
print('ALL PASS')
|
package/scripts/update-guard.sh
CHANGED
|
@@ -1199,14 +1199,17 @@ if [[ "$SYNC_ONLY" == "1" ]]; then
|
|
|
1199
1199
|
fi
|
|
1200
1200
|
|
|
1201
1201
|
# ─── ④ 各現場の権限ルールを部分マージで更新 ─────────────────────────────────
|
|
1202
|
+
# ★実際に書き換えた現場だけを覚えておく(この下の④-b が「コミットが要るリポ」を名指しする)。
|
|
1203
|
+
wrote_sites=()
|
|
1202
1204
|
for proj in "${targets[@]}"; do
|
|
1203
1205
|
dst="$proj/.claude/settings.json"
|
|
1204
1206
|
if [[ ! -f "$dst" ]]; then
|
|
1205
1207
|
warn " スキップ(settings.json なし): $proj"
|
|
1206
1208
|
continue
|
|
1207
1209
|
fi
|
|
1210
|
+
rc=0
|
|
1208
1211
|
APPLY="$APPLY" \
|
|
1209
|
-
python3 - "$dst" "$SETTINGS_SRC" "$SAMPLE_SETTINGS" <<'PYEOF'
|
|
1212
|
+
python3 - "$dst" "$SETTINGS_SRC" "$SAMPLE_SETTINGS" <<'PYEOF' || rc=$?
|
|
1210
1213
|
import json, os, shutil, sys, time
|
|
1211
1214
|
|
|
1212
1215
|
dst_path, src_path, sample_path = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
@@ -1448,7 +1451,16 @@ with open(tmp, "w", encoding="utf-8") as f:
|
|
|
1448
1451
|
f.write("\n")
|
|
1449
1452
|
os.replace(tmp, dst_path) # 置換は原子的=途中で壊れた settings.json を残さない
|
|
1450
1453
|
print(f"[update-guard] 更新しました(バックアップ: {backup})")
|
|
1454
|
+
# ★実際に書き換えた回だけ 10 で終わる(呼び元が「コミットが要るリポ」を名指しするため)。
|
|
1455
|
+
sys.exit(10)
|
|
1451
1456
|
PYEOF
|
|
1457
|
+
# 終わり方の読み方: 0=変更なし/見るだけ、10=実際に書き換えた、それ以外=想定外の失敗。
|
|
1458
|
+
# 想定外の失敗では、今までどおり(set -e と同じ)そこで止まる=黙って先へ進まない。
|
|
1459
|
+
if [[ "$rc" -eq 10 ]]; then
|
|
1460
|
+
wrote_sites+=("$proj")
|
|
1461
|
+
elif [[ "$rc" -ne 0 ]]; then
|
|
1462
|
+
exit "$rc"
|
|
1463
|
+
fi
|
|
1452
1464
|
done
|
|
1453
1465
|
|
|
1454
1466
|
echo
|
|
@@ -1457,3 +1469,36 @@ if [[ "$APPLY" == "1" ]]; then
|
|
|
1457
1469
|
else
|
|
1458
1470
|
info "確認のみで終了しました。適用するには APPLY=1 を付けて再実行してください。"
|
|
1459
1471
|
fi
|
|
1472
|
+
|
|
1473
|
+
# ─── ④-b 書き換えたリポのうち「コミットが要る」物を名指しで知らせる ──────────────
|
|
1474
|
+
# 【なぜ要るか(実地 2026-10-06)】④ は現場の .claude/settings.json を「禁止の一覧が強い版」へ
|
|
1475
|
+
# 書き換えるが、それを git に入れる所がだれの仕事にもなっていなかった。結果、6つのリポで
|
|
1476
|
+
# 強い設定が git に入らないまま放置された(いちばん長い utatane-backend は約2か月半・git の版は
|
|
1477
|
+
# 禁止 11 件のまま)。放置のあいだ、ほかのPCとクラウドの作業場には弱い設定が配られ、職人の
|
|
1478
|
+
# 合流のたびに誤った「失敗」が出た。=書き換えた直後に名指しで出し、人が CC1 へ渡せるようにする。
|
|
1479
|
+
# 【決め】
|
|
1480
|
+
# ・出すのは「実際に書き換えた回」だけ。変更なしの回・見るだけの回は1行も出さない(表示を変えない)。
|
|
1481
|
+
# ・挙げるのは git が追跡している .claude/settings.json を持つリポだけ。追跡されていない物(?? だけ)は
|
|
1482
|
+
# コミットが要らないので挙げない。
|
|
1483
|
+
# ・確かめは読むだけの git(ls-files)。リポの中身・git の状態を1つも変えない。
|
|
1484
|
+
# ・git が使えない/リポでない置き場は黙って飛ばす(台本を落とさない)。
|
|
1485
|
+
# ★最後に出すのは、人の目に最後まで残るため(この下に別の行を足さない)。
|
|
1486
|
+
if [[ ${#wrote_sites[@]} -gt 0 ]]; then
|
|
1487
|
+
needs_commit=()
|
|
1488
|
+
for proj in "${wrote_sites[@]}"; do
|
|
1489
|
+
# 読むだけ。リポでない置き場・git が無い機では非ゼロで返るので、黙って外れる。
|
|
1490
|
+
if git -C "$proj" ls-files --error-unmatch -- .claude/settings.json >/dev/null 2>&1; then
|
|
1491
|
+
needs_commit+=("$proj")
|
|
1492
|
+
fi
|
|
1493
|
+
done
|
|
1494
|
+
if [[ ${#needs_commit[@]} -gt 0 ]]; then
|
|
1495
|
+
echo
|
|
1496
|
+
info "★コミットが要るリポがあります(git が追跡している .claude/settings.json を書き換えました)"
|
|
1497
|
+
for proj in "${needs_commit[@]}"; do
|
|
1498
|
+
info " - $(basename "$proj") ($proj)"
|
|
1499
|
+
done
|
|
1500
|
+
info " この変更は戻さない(番人の禁止の一覧が強くなった知らせ)。そのリポの職人の便でコミットする。"
|
|
1501
|
+
info " git checkout で戻すと守りが弱くなる。"
|
|
1502
|
+
info " → この一覧を CC1 に伝えてください(コミットの便を出してもらいます)。"
|
|
1503
|
+
fi
|
|
1504
|
+
fi
|
|
@@ -121,6 +121,7 @@ description: 采配くんの便(プロンプト末尾に CONDUCTOR_JOB:<id>
|
|
|
121
121
|
- **電話は自由。** 外への通信(npm / GitHub / API / ドキュメント取得など)は許可済みなので、`Network request outside of sandbox` は出ない。**通信のために止まらない・人を待たない**。最後まで自走して完了まで行く。
|
|
122
122
|
- **金庫は禁止。** 秘密の持ち出し(`curl -d @.env` / `cp .env` / `tar ~/.ssh` / `base64 .env` / `echo $DATABASE_URL` など)は**番人が今までどおり止める**。通信が自由になったこととは**別の仕組み**なので、「通信が許可されたから秘密も送れる」は成り立たない。本番DB・secret・`.env`・`hooks`・`.tyhld`・`.ssh` への書き込み禁止もそのまま。
|
|
123
123
|
- **それでも塞がれたら回避しない。** `curl: (56) CONNECT tunnel failed, response 403` のように通信が塞がれたときは、**意図的に閉じてある接続先**(例: 本番DBへの直結)か、設定がその機体にまだ届いていないかのどちらか。**別経路を探さず、そこで止めて報告する**(人の手番=`APPLY=1 bash scripts/update-guard.sh` → 職人を再起動)。
|
|
124
|
+
- ★**この1行(`APPLY=1 bash scripts/update-guard.sh`)を人が流したあとに `★コミットが要るリポがあります` の一覧が出たら、その一覧を CC1 に伝える**(コミットの便を出してもらう)。出た変更は戻さない(`git checkout` で戻すと守りが弱くなる)。
|
|
124
125
|
13. **使い捨てファイルはリポジトリの中に作らない(えふさん決定 2026-09-03/えふさん確定 2026-10-02・判断6 で後片付けの形を改めた)**
|
|
125
126
|
- **実機検品・検証用の使い捨てファイル(HTML・画像・ログ等)は、リポジトリの中に作らない。`/tmp/claude-1000/` に置く。**
|
|
126
127
|
- **リポジトリの中(直下を含む)に作ってしまったときの後片付け(えふさん確定 2026-10-02・判断6)。自分で作った物に限り、`-f` を付けない `rm -r` と実在のパスの名指しで消し、消した物を報告に書く。**
|
|
@@ -139,6 +140,7 @@ description: 采配くんの便(プロンプト末尾に CONDUCTOR_JOB:<id>
|
|
|
139
140
|
14. **`.env` 類(`.env`・`.env.local`・`.env.example` などの雛形も含む)は職人が書き換えない(えふさん確定 2026-09-18・便 3d11f913)**
|
|
140
141
|
- **番人は `.env` 本体も `.env.example` などの雛形も、書き込みを止める**(Write/Edit の設定 deny と、Bash の `echo`/`tee`/`sed -i`/`cp`/`mv` などを、経路を問わず同じ結果にそろえた)。読み込みは今までどおり(雛形は読める・本物は読めない)。
|
|
141
142
|
- **なぜ。** 雛形(`.env.example`)は普通は秘密値を置かないが、「exampleだから常に安全」と番人へ例外を作ると、将来誤って実値が書かれたときの穴になる(利便性より安全側・えふさん確定)。
|
|
143
|
+
- **★雛形(`.env.example`)を読むときは Read の道具で読む(実測 便 6e8d85e6)。** `cat .env.example` は**禁止の一覧で止まる**(配っている `Bash(cat .env.*)` に当たる)。番人は雛形の読みを許しているので、Read の道具・Grep の道具なら今までどおり読める。★規則は緩めない(実名に絞ると、将来の新しい枝名が漏れる)。止まったら回避せず、Read の道具に替える。
|
|
142
144
|
- **雛形(`.env.example` など)を直すときの正しい形=「案を作る→人が1回で反映」。** 職人は変更後の中身を**リポジトリの外(`/tmp/claude-1000/` の下)**に作り、報告に次の2つを載せる。① 変更の**差分**(何を足す/消すか)。② 人が**素のターミナルで1回貼るだけで反映できる1つのコマンド**(例:`cp /tmp/claude-1000/<下書き> <リポ>/.env.example`)。★このコマンドは**人が自分のターミナルで**打つ(職人の番人は人の端末には掛からない)。職人が `.env.example` を直接書き換えようとしても番人が止めるので、**回避せず、この形で案を出す**。
|
|
143
145
|
- **本物の `.env`・`.env.local` は、これまでどおり中身も書き込みも扱わない**(秘密の値は1文字も読まない・書かない=鉄則12の金庫)。
|
|
144
146
|
15. **工程の区切りで「いま何をしているか」を日本語1行で出す(えふさん確定 2026-10-02・便 fbaba4b1)**
|
|
@@ -169,7 +171,9 @@ description: 采配くんの便(プロンプト末尾に CONDUCTOR_JOB:<id>
|
|
|
169
171
|
- 止まる例: `rm -rf /tmp/claude-1000/作業名`(禁止の一覧)/ `rm -r "$DIR/x"`・`rm -r build/*`(番人)/ `rm -r .git`・`rm -r .claude/hooks`・`rm -r .tyhld`(番人=相対パスでも消せない)
|
|
170
172
|
- ★**探すだけの道具の「探す言葉」に `rm …` と書いても、もう止まらない**(便 915694ac で直した)。鉄則や台本を調べるときの `grep -rn 'rm -rf .git' skills`・`git grep -n 'rm -r' -- skills`・`rg 'rm -rf /' docs` は通る。★囲みの外に書いた `rm` は今までどおり測るので、`grep -rn 'x' docs; rm -rf .git` のように**後ろで消す形は止まる**。
|
|
171
173
|
- **まとめて全部を記録に入れる `git add`・`git commit`**(`git add -A`・`git add .`・`git commit -a`・`git commit -am 説明`・`git commit --all`)→ 止まる。着手前から変わっていたファイル(例 `.claude/settings.json`)まで入り、番人がそれを記録から外す操作も止めるので取り返せない(便 4cf71dde)。**ファイルを名指しで `git add <ファイル>` してから `git commit -m 説明`**(鉄則1-④)。
|
|
172
|
-
-
|
|
174
|
+
- **行き先のブランチを確かめられない `git push`**(作業場所が変数のままの `git -C "$DIR" push`・`git push` とだけ打つ形・保護ブランチを狙う形)→ 止まる。**リポの中で、ブランチを名指しして** `git push origin <作業ブランチ名>`(★`-u` は付けない=鉄則7-⓪。上流の設定を書くと、囲いが `.git/config` を塞いでいるPCで止まる)。
|
|
175
|
+
- ★**止まるのは「行き先が分からない形」と「保護ブランチを狙う形」**(実測 便 6e8d85e6)。場所を指す旗を付けても枝を名指しした形(`git -C <場所> push origin <作業ブランチ名>`)は止まらないので、「場所を指す旗が付いているか」で安全を判断しない。
|
|
176
|
+
- ★**強い上書き(`--force`/`-f`/`--force-with-lease`/先頭が `+` の参照指定)は、旗の位置を問わず止まる。** 旗を末尾や中間に置く形・短い旗をまとめた形(`-fu`)・`git` と `push` の間に場所や設定を指す旗を挟む形・`+` を引用符で囲う形・つないだ行や包みの中に書いた形も、すべて止まる(便 e619bd86 で語の並びを見る判定に替えた。それまでは挟む形と引用符で囲う形が素通りしていた=実地 便 6e8d85e6)。**そもそも強い上書きは打たない**(便の本文の「force-push と reset はしない」)。
|
|
173
177
|
|
|
174
178
|
## 便の終え方
|
|
175
179
|
|