@tyhld/conductor 0.12.23 → 0.12.25
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
package/scripts/hooks/policy.py
CHANGED
|
@@ -812,6 +812,9 @@ def _wrapper_danger(command: str, depth: int = 0, cwd=None):
|
|
|
812
812
|
if hit:
|
|
813
813
|
return f'{hit} ({where})'
|
|
814
814
|
hit = _guard_write_scan(inner, cwd)
|
|
815
|
+
if hit:
|
|
816
|
+
return f'{hit} ({where})'
|
|
817
|
+
hit = _env_write_scan(inner, cwd)
|
|
815
818
|
if hit:
|
|
816
819
|
return f'{hit} ({where})'
|
|
817
820
|
for seg in _real_segments(inner):
|
|
@@ -2421,6 +2424,115 @@ def _git_write_targets(args, cwd):
|
|
|
2421
2424
|
return where, []
|
|
2422
2425
|
|
|
2423
2426
|
|
|
2427
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
2428
|
+
# .env 類への書き込み(DENY・便 3d11f913=便2.8)
|
|
2429
|
+
#
|
|
2430
|
+
# 【なぜ要るか(調査 55283164 の重大発見1)】Write/Edit ツールは設定 deny
|
|
2431
|
+
# (sales-template/settings.json の Write/Edit(**/.env) と Write/Edit(**/.env.*))で
|
|
2432
|
+
# .env.example への書き込みが止まるのに、Bash(echo・tee・sed -i・cp/mv)は番人の雛形除外で
|
|
2433
|
+
# 通っていた=中身を見ずに .env.example に任意の中身を書ける抜け道。書き込みの結果を
|
|
2434
|
+
# Write/Edit の設定 deny とそろえる。
|
|
2435
|
+
# 【範囲】設定 deny と同じ=書き込む【先】のファイル名が `.env`(本体)か `.env.` で始まるもの
|
|
2436
|
+
# (.env.local・.env.production・.env.example など雛形も含む)。★読み込みは変えない(雛形は
|
|
2437
|
+
# 今までどおり読める・本物は読めない=secret_reason のまま)。
|
|
2438
|
+
# 【誤検知を避ける】書き込む【先】だけを見る。`cp .env.example backup.txt`(雛形を別名へ写す)は
|
|
2439
|
+
# 先が backup.txt なので通す。フォルダ宛て(末尾 / ・-t・複数元)のときだけ、元の名前が先の
|
|
2440
|
+
# フォルダに .env.* として書かれるかを見る。
|
|
2441
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
2442
|
+
|
|
2443
|
+
def _is_env_family(name: str) -> bool:
|
|
2444
|
+
"""設定 deny `**/.env`・`**/.env.*` と同じ範囲か(書き込み先の名前にだけ使う)。"""
|
|
2445
|
+
return name == '.env' or name.startswith('.env.')
|
|
2446
|
+
|
|
2447
|
+
|
|
2448
|
+
def _env_base(path: str) -> str:
|
|
2449
|
+
"""書き込み先の字面から、飾りを外した末尾の名前(basename)。"""
|
|
2450
|
+
p = normalize_redirect_target(path).replace('\\', '/').rstrip('/')
|
|
2451
|
+
return p.split('/')[-1]
|
|
2452
|
+
|
|
2453
|
+
|
|
2454
|
+
# 引数ぜんぶが書き込み先になる道具(設定 deny と同じ結果へそろえるための最小集合)。
|
|
2455
|
+
_ENV_WRITE_ALL_VERBS = frozenset({'tee', 'sponge', 'truncate', 'touch'})
|
|
2456
|
+
# 書き込み先が dest(末尾の位置引数か -t の値)になる道具。
|
|
2457
|
+
_ENV_WRITE_DEST_VERBS = frozenset({'cp', 'install', 'mv', 'rsync', 'ditto', 'ln', 'scp'})
|
|
2458
|
+
|
|
2459
|
+
|
|
2460
|
+
def _env_write_dests(verb: str, args, tokens, pos):
|
|
2461
|
+
"""1コマンドが .env 類へ書き込む可能性のある「先の名前」を集める(書き込みにだけ使う)。"""
|
|
2462
|
+
paths = [a for a in args if not a.startswith('-')]
|
|
2463
|
+
if verb == 'dd':
|
|
2464
|
+
return [a[3:] for a in args if a.startswith('of=')]
|
|
2465
|
+
if verb in _ENV_WRITE_ALL_VERBS:
|
|
2466
|
+
return paths
|
|
2467
|
+
if verb in _INPLACE_VERBS and (
|
|
2468
|
+
is_inplace_edit('sed' if verb in ('gsed', 'yq') else verb, tokens[pos:])
|
|
2469
|
+
or any(re.match(r'^-[a-zA-Z]*i', a) for a in args if not a.startswith('--'))
|
|
2470
|
+
or (verb in _AWK_VERBS and 'inplace' in args)):
|
|
2471
|
+
return paths # その場書き換え=その名前へ書く
|
|
2472
|
+
if verb in _ENV_WRITE_DEST_VERBS:
|
|
2473
|
+
tdir = None
|
|
2474
|
+
# ★値を1つ取る旗(-m 644・-o user 等)を飛ばして、本当の位置引数だけを数える。
|
|
2475
|
+
pos_args, skip = [], False
|
|
2476
|
+
for k, a in enumerate(args):
|
|
2477
|
+
if skip:
|
|
2478
|
+
skip = False
|
|
2479
|
+
continue
|
|
2480
|
+
if a in ('-t', '--target-directory') and k + 1 < len(args):
|
|
2481
|
+
tdir = args[k + 1]
|
|
2482
|
+
skip = True
|
|
2483
|
+
continue
|
|
2484
|
+
if a.startswith('--target-directory='):
|
|
2485
|
+
tdir = a.split('=', 1)[1]
|
|
2486
|
+
continue
|
|
2487
|
+
if a in _ENV_DEST_VALUE_FLAGS and k + 1 < len(args):
|
|
2488
|
+
skip = True
|
|
2489
|
+
continue
|
|
2490
|
+
if not a.startswith('-'):
|
|
2491
|
+
pos_args.append(a)
|
|
2492
|
+
if verb == 'install' and '-d' in args:
|
|
2493
|
+
return pos_args # ディレクトリ作成
|
|
2494
|
+
if tdir is not None:
|
|
2495
|
+
return [_env_base(s) for s in pos_args] # 各元がそのフォルダへ同じ名前で入る
|
|
2496
|
+
if not pos_args:
|
|
2497
|
+
return []
|
|
2498
|
+
dest, srcs = pos_args[-1], pos_args[:-1]
|
|
2499
|
+
if ':' in dest.split('/')[0] and verb in ('scp', 'rsync'):
|
|
2500
|
+
return [] # 別の機械への書き込み(ここは見ない)
|
|
2501
|
+
# ★書き込む先はまず dest 自身。フォルダ宛て(末尾 / ・複数元)なら元の名前も入る。
|
|
2502
|
+
out = [dest]
|
|
2503
|
+
if dest.endswith('/') or len(srcs) > 1:
|
|
2504
|
+
out += [_env_base(s) for s in srcs]
|
|
2505
|
+
return out
|
|
2506
|
+
return []
|
|
2507
|
+
|
|
2508
|
+
|
|
2509
|
+
# cp/install/mv などで値を1つ取る旗(この値は元でも先でもない)。
|
|
2510
|
+
_ENV_DEST_VALUE_FLAGS = frozenset({
|
|
2511
|
+
'-m', '--mode', '-o', '--owner', '-g', '--group', '-S', '--suffix', '--backup',
|
|
2512
|
+
})
|
|
2513
|
+
|
|
2514
|
+
|
|
2515
|
+
def _env_write_scan(command: str, cwd=None):
|
|
2516
|
+
"""コマンド全体で、.env 類(雛形を含む)への書き込みを探す(本物の区切り・書き込み先だけ)。"""
|
|
2517
|
+
if not command:
|
|
2518
|
+
return None
|
|
2519
|
+
for seg in split_commands(command, bodies=False):
|
|
2520
|
+
s = _normalize_segment(seg)
|
|
2521
|
+
for t in _real_redirect_targets(s):
|
|
2522
|
+
if _is_env_family(_env_base(t)):
|
|
2523
|
+
return f'env file write (redirect into {_env_base(t)})'
|
|
2524
|
+
head = s.split('\n', 1)[0] if '<<' in s else s
|
|
2525
|
+
toks = tokens_of(head)
|
|
2526
|
+
p, _ = _unwrap(toks)
|
|
2527
|
+
if p >= len(toks):
|
|
2528
|
+
continue
|
|
2529
|
+
verb = toks[p].split('/')[-1]
|
|
2530
|
+
for d in _env_write_dests(verb, _plain_args(toks, p + 1), toks, p):
|
|
2531
|
+
if _is_env_family(_env_base(d)):
|
|
2532
|
+
return f'env file write ({_env_base(d)} by {verb})'
|
|
2533
|
+
return None
|
|
2534
|
+
|
|
2535
|
+
|
|
2424
2536
|
def _guard_write_segment(segment: str, cwd=None, command='', depth=0, env=None):
|
|
2425
2537
|
"""本物の1コマンドが番人の設定を書き換えるなら理由。"""
|
|
2426
2538
|
segment = _normalize_segment(segment)
|
|
@@ -2941,6 +3053,11 @@ def decide_bash(command: str, cwd=None):
|
|
|
2941
3053
|
if hit:
|
|
2942
3054
|
return _r(DENY, f'danger: {hit}')
|
|
2943
3055
|
|
|
3056
|
+
# ---- 1c'') .env 類への書き込み(便 3d11f913・Write/Edit の設定 deny と結果をそろえる) ----
|
|
3057
|
+
hit = _env_write_scan(command, cwd)
|
|
3058
|
+
if hit:
|
|
3059
|
+
return _r(DENY, f'danger: {hit}')
|
|
3060
|
+
|
|
2944
3061
|
# ---- 1d) 抜け道(便 fc22bc34・main 直 push・保護の迂回と変更・秘密の値・検査の省略) ----
|
|
2945
3062
|
hit = _bypass_scan(command, cwd)
|
|
2946
3063
|
if hit:
|
|
@@ -3002,6 +3119,10 @@ def decide_write(file_path: str, cwd=None):
|
|
|
3002
3119
|
comps = [c for c in path.split('/') if c]
|
|
3003
3120
|
if '.tyhld' in comps:
|
|
3004
3121
|
return _r(DENY, 'danger: write to guard directory (.tyhld)')
|
|
3122
|
+
# ★.env 類(雛形を含む)への書き込みは、Bash と同じく止める(便 3d11f913・番人でも結果を一致)。
|
|
3123
|
+
# 読み込みは変えない(secret_reason は雛形を通す)。
|
|
3124
|
+
if _is_env_family(_env_base(path)):
|
|
3125
|
+
return _r(DENY, f'danger: env file write ({_env_base(path)})')
|
|
3005
3126
|
return _r(ALLOW, 'no gate, no danger')
|
|
3006
3127
|
|
|
3007
3128
|
|
|
@@ -186,11 +186,13 @@ SECRET_READS = [
|
|
|
186
186
|
]
|
|
187
187
|
for cmd in SECRET_READS:
|
|
188
188
|
expect('(2)', cmd, policy.DENY)
|
|
189
|
-
for cmd in [f'grep -n "x|y" {E}.example', f'cat {E}.example',
|
|
190
|
-
f'grep -n "{DBURL}|API_KEY" src/a.ts',
|
|
189
|
+
for cmd in [f'grep -n "x|y" {E}.example', f'cat {E}.example',
|
|
190
|
+
f'grep -n "{DBURL}|API_KEY" src/a.ts',
|
|
191
191
|
f'grep -rn "{DBURL}|API_KEY" src', f'git grep -n "{DBURL}|process\\.env"',
|
|
192
192
|
f'git ls-files | grep "\\{E}"', f'grep -n "x|y" README.md', f'rg -n "a|b" src']:
|
|
193
193
|
expect('(2) 雛形・名前を探すだけは allow', cmd, policy.ALLOW)
|
|
194
|
+
# ★雛形への【書き込み】は便 3d11f913 で止める(読みは通るが書きは止まる)。
|
|
195
|
+
expect('(2) 雛形への書き込みは deny', f'echo "NEW_KEY=" >> {E}.example', policy.DENY)
|
|
194
196
|
|
|
195
197
|
print('\n── (3) GitHub の API で main の先端を書き換える形 ' + '─' * 20)
|
|
196
198
|
API_DENY = [
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""便2.8(便 3d11f913): .env 類への書き込みで、Write/Edit と Bash の守りをそろえる試験。
|
|
3
|
+
|
|
4
|
+
実行: python3 scripts/hooks/test_env_write_parity.py
|
|
5
|
+
1件でも期待と違えば exit 1。
|
|
6
|
+
|
|
7
|
+
【背景(調査 55283164 の重大発見1)】Write/Edit ツールは設定 deny(**/.env・**/.env.*)で
|
|
8
|
+
.env.example への書き込みが止まるのに、Bash(echo・tee・sed -i・cp/mv)は番人の雛形除外で
|
|
9
|
+
中身を見ずに .env.example に書けた=抜け道。この便で Bash も止め、結果をそろえた。
|
|
10
|
+
|
|
11
|
+
【固定すること】
|
|
12
|
+
① Bash で .env.example・.env・.env.local への書き込みが3つとも止まる。
|
|
13
|
+
② Write/Edit と Bash の結果が一致(番人の判定で、書き込みは3経路とも deny)。
|
|
14
|
+
③ 雛形の読み込みは今までどおり通る・本物は止まる(★読み込みは変えない)。
|
|
15
|
+
④ は既存の D1 試験(他ファイル)で担保。⑤ 空振り確認は spot スクリプト(報告に記載)。
|
|
16
|
+
|
|
17
|
+
★見本は作り物の場所で行い、本物の .env・.env.example に1バイトも書かない。危険語は組み立てる。
|
|
18
|
+
"""
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
23
|
+
|
|
24
|
+
import policy # noqa: E402
|
|
25
|
+
|
|
26
|
+
failures = []
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def check(label, got, want):
|
|
30
|
+
ok = got == want
|
|
31
|
+
if not ok:
|
|
32
|
+
failures.append(f'{label}: got={got!r} want={want!r}')
|
|
33
|
+
print(f'{"OK " if ok else "NG "} {label:66} {str(got)[:24]}')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
ENV = '.e' + 'nv' # 本体
|
|
37
|
+
EX = ENV + '.example' # 雛形
|
|
38
|
+
LOCAL = ENV + '.local' # 本物(値が入る)
|
|
39
|
+
CWD = '/home/fshim/projects/x'
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def dbash(cmd):
|
|
43
|
+
return policy.decide_bash(cmd, CWD)['decision']
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def dwrite(name):
|
|
47
|
+
return policy.decide_write('/home/fshim/projects/x/' + name, CWD)['decision']
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def dread(name):
|
|
51
|
+
return policy.decide_read('/home/fshim/projects/x/' + name, CWD)['decision']
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
print('── ① Bash で .env.example・.env・.env.local への書き込みが3つとも止まる ' + '─' * 4)
|
|
55
|
+
# 手段を問わず(リダイレクト・tee・sed -i・cp・mv・dd・truncate・touch・install・ln・包み)。
|
|
56
|
+
for target in (EX, ENV, LOCAL):
|
|
57
|
+
for cmd in [f'echo x > {target}', f'echo x >> {target}', f'printf x > {target}', f': > {target}',
|
|
58
|
+
f'tee {target} < a', f'tee -a {target}', f'sed -i s/a/b/ {target}',
|
|
59
|
+
f'sed -i.bak -e s/a/b/ {target}', f'perl -pi -e s/a/b/ {target}',
|
|
60
|
+
f'cp a {target}', f'cp -f a {target}', f'install -m 644 a {target}', f'mv a {target}',
|
|
61
|
+
f'dd if=a of={target}', f'truncate -s0 {target}', f'touch {target}',
|
|
62
|
+
f'ln -sf /tmp/x {target}', f'cp a src/{target}', f'cp a dir/ && cp b {target}',
|
|
63
|
+
f'bash -c "echo x > {target}"', f"sh -c 'sed -i s/a/b/ {target}'",
|
|
64
|
+
f'env FOO=1 tee {target}', f'cat a > {target} 2>/dev/nu' + 'll']:
|
|
65
|
+
check(f'① deny: {cmd[:50]}', dbash(cmd), policy.DENY)
|
|
66
|
+
|
|
67
|
+
print('\n── ② Write/Edit と Bash の結果が一致(3経路とも番人が deny) ' + '─' * 6)
|
|
68
|
+
for target in (EX, ENV, LOCAL):
|
|
69
|
+
check(f'② Write deny {target}', dwrite(target), policy.DENY)
|
|
70
|
+
check(f'② Edit deny {target}',
|
|
71
|
+
policy.decide_event({'tool_name': 'Edit', 'tool_input': {'file_path': CWD + '/' + target}})['decision'],
|
|
72
|
+
policy.DENY)
|
|
73
|
+
check(f'② Bash deny {target}', dbash(f'echo x > {target}'), policy.DENY)
|
|
74
|
+
# 雛形の派生(.env.example.md も **/.env.* に当たる=設定 deny と一致)。
|
|
75
|
+
check('② Write deny .env.example.md', dwrite(EX + '.md'), policy.DENY)
|
|
76
|
+
|
|
77
|
+
print('\n── ③ 読み込みは今までどおり(雛形は読める・本物は読めない)★変えない ' + '─' * 2)
|
|
78
|
+
for cmd in [f'cat {EX}', f'less {EX}', f'grep -n NAME {EX}', f'diff {EX} /tmp/a', f'head {EX}',
|
|
79
|
+
f'bash -c "cat {EX}"']:
|
|
80
|
+
check(f'③ 雛形は読める: {cmd[:44]}', dbash(cmd), policy.ALLOW)
|
|
81
|
+
check('③ Read 雛形は allow', dread(EX), policy.ALLOW)
|
|
82
|
+
for cmd in [f'cat {ENV}', f'cat {LOCAL}', f'grep TOKEN {ENV}', f'bash -c "cat {ENV}"']:
|
|
83
|
+
check(f'③ 本物は読めない: {cmd[:44]}', dbash(cmd), policy.DENY)
|
|
84
|
+
check('③ Read 本物は deny', dread(ENV), policy.DENY)
|
|
85
|
+
|
|
86
|
+
print('\n── ★誤検知しない(雛形を別名ファイルへ写す・字面だけ・普通の開発) ' + '─' * 4)
|
|
87
|
+
for cmd in [f'cp {EX} backup.txt', f'cp {EX} /tmp/claude-1000/x', f'mv {EX} notes.txt',
|
|
88
|
+
f'cat {EX} | grep X', f'git commit -m "docs: {EX} の項目"', f'echo "{EX} を直す" >> notes.md',
|
|
89
|
+
'sed -i s/a/b/ src/app.ts', 'cp dist/a.js build/a.js', 'echo done > report.md',
|
|
90
|
+
'cp a b/', 'tee out.log < a']:
|
|
91
|
+
check(f'誤検知しない allow: {cmd[:44]}', dbash(cmd), policy.ALLOW)
|
|
92
|
+
|
|
93
|
+
print('\n' + '=' * 72)
|
|
94
|
+
if failures:
|
|
95
|
+
print(f'NG {len(failures)} 件:')
|
|
96
|
+
for f in failures:
|
|
97
|
+
print(' -', f)
|
|
98
|
+
sys.exit(1)
|
|
99
|
+
print('すべて期待どおり')
|
|
100
|
+
sys.exit(0)
|
|
@@ -418,13 +418,19 @@ print('\n=== (b5) 経路を問わず同じ結論(書き込みツール・Bash
|
|
|
418
418
|
for fp in ['/home/fshim/projects/x/.env', '/home/fshim/projects/x/.env.local']:
|
|
419
419
|
r = policy.decide_write(fp, '/home/fshim/projects/x')
|
|
420
420
|
check(f'write deny 維持 {fp[:48]}', r['decision'], policy.DENY)
|
|
421
|
+
# ★雛形(.env.example)は【読める】が【書けない】(便 3d11f913・Write/Edit の設定 deny と結果を一致)。
|
|
421
422
|
for fp in ['/home/fshim/projects/x/.env.example', '/home/fshim/projects/x/.env.local.example']:
|
|
422
423
|
r = policy.decide_write(fp, '/home/fshim/projects/x')
|
|
423
|
-
check(f'write
|
|
424
|
+
check(f'write deny 雛形も書けない {fp[:44]}', r['decision'], policy.DENY)
|
|
424
425
|
for cmd in ['cat .env', 'cat .env.local', 'sed -i "s/a/b/" .env']:
|
|
425
426
|
check(f'bash deny 維持 {cmd[:48]}', policy.decide_bash(cmd)['decision'], policy.DENY)
|
|
426
|
-
|
|
427
|
-
|
|
427
|
+
# 読み込みは今までどおり雛形を通す(本文の除外は変えない)。
|
|
428
|
+
for cmd in ['cat .env.example', 'cat config.sample', 'grep -n NAME .env.example']:
|
|
429
|
+
check(f'bash 雛形は読める {cmd[:48]}', policy.decide_bash(cmd)['decision'], policy.ALLOW)
|
|
430
|
+
# ★書き込みは雛形でも止める(Bash 経路の抜け道を塞ぐ・便 3d11f913)。
|
|
431
|
+
for cmd in ['sed -i "s/a/b/" .env.example', 'echo x > .env.example', 'tee .env.example < a',
|
|
432
|
+
'cp a .env.example']:
|
|
433
|
+
check(f'bash 雛形への書き込みは deny {cmd[:44]}', policy.decide_bash(cmd)['decision'], policy.DENY)
|
|
428
434
|
|
|
429
435
|
# ★包んだ書き方でも同じ結論にする。`cat .env` は止まるのに `bash -c "cat .env"` は
|
|
430
436
|
# 素通りしていた(調査 d7a1c150 で実測)。ラッパの中身を取り出して測り直す。
|
|
@@ -1606,8 +1612,11 @@ for cmd in ['cat README.md', 'grep -rn relayTick src', 'head -20 package.json',
|
|
|
1606
1612
|
# (g-6) ★書き込みの禁止が1つも変わっていない(判定点を1つにしても後退しない)
|
|
1607
1613
|
for p in SECRET_PATHS:
|
|
1608
1614
|
check(f'write deny {p[len(FAKE):][:48]}', ev('Write', {'file_path': p})['decision'], policy.DENY)
|
|
1615
|
+
# ★.env 類(雛形含む)への書き込みは deny(便 3d11f913)。ほかの普通のファイルは allow。
|
|
1609
1616
|
for p in PLAIN_PATHS:
|
|
1610
|
-
|
|
1617
|
+
want = policy.DENY if policy._is_env_family(os.path.basename(p)) else policy.ALLOW
|
|
1618
|
+
label = 'write deny .env系' if want == policy.DENY else 'write allow'
|
|
1619
|
+
check(f'{label} {p[len(FAKE):][:41]}', ev('Write', {'file_path': p})['decision'], want)
|
|
1611
1620
|
|
|
1612
1621
|
# (g-7) ★空振り確認=判定点(secret_reason)を外すと、上の deny が全部 allow に落ちる
|
|
1613
1622
|
# これが落ちないなら「読み取りの禁止が secret_reason を通っていない」=写しの一覧を
|
|
@@ -1619,7 +1628,10 @@ try:
|
|
|
1619
1628
|
for p in SECRET_PATHS:
|
|
1620
1629
|
if ev('Read', {'file_path': p})['decision'] != policy.ALLOW:
|
|
1621
1630
|
broke.append(f'Read {p}')
|
|
1622
|
-
|
|
1631
|
+
# ★.env 類の書き込みは secret_reason とは別の専用の判定(便 3d11f913)で止まるので、
|
|
1632
|
+
# secret_reason を外しても allow に落ちない=ここでは数えない(別の空振りで固定)。
|
|
1633
|
+
if not policy._is_env_family(os.path.basename(p)) \
|
|
1634
|
+
and ev('Write', {'file_path': p})['decision'] != policy.ALLOW:
|
|
1623
1635
|
broke.append(f'Write {p}')
|
|
1624
1636
|
if ev('Grep', {'pattern': 'x', 'path': p})['decision'] != policy.ALLOW:
|
|
1625
1637
|
broke.append(f'Grep {p}')
|
|
@@ -8,9 +8,9 @@ description: 采配くんの便(プロンプト末尾に CONDUCTOR_JOB:<id>
|
|
|
8
8
|
采配くんの便(プロンプト末尾に `CONDUCTOR_JOB:<id>` の印が付いた作業指示)は、
|
|
9
9
|
非エンジニアのお客さまの現場で、人が横に付かないまま実行される。
|
|
10
10
|
だから「速さ」より「戻せること・止まれること・報せること」を優先する。
|
|
11
|
-
以下の
|
|
11
|
+
以下の14項を、着手前に読み、作業中も判断の基準にすること。
|
|
12
12
|
|
|
13
|
-
## 鉄則
|
|
13
|
+
## 鉄則14項
|
|
14
14
|
|
|
15
15
|
1. **着手時の確認と枝分かれ** — `git status && git log --oneline -3 && git stash list` を確認してから main を最新化し、新ブランチを切る。
|
|
16
16
|
- ★**リポ全体の差分が「読めないファイル」で落ちたとき**(`unsupported file type` / `cannot hash <名前>` が出る/`.npmrc` のような設定が身に覚えなく `M` で出る)。**これは壊れていない。**
|
|
@@ -37,6 +37,9 @@ description: 采配くんの便(プロンプト末尾に CONDUCTOR_JOB:<id>
|
|
|
37
37
|
- ★**保護が理由で合流できないときは、回避せずそこで止めて報告する**(人の手番)。別の旗を探さない。
|
|
38
38
|
- ★CI が赤いときは、直してから合流する。直せない理由が分かったら、状態を報告して止まる。
|
|
39
39
|
- ★「この便のマージは人間が行う」と指示に書かれていれば、**その指示が優先**(ADR-007)。
|
|
40
|
+
- ★**合流したかの確かめ方(題名では判定しない・えふさん確定 2026-09-18)。** 合流完了の判定はコミット題名では行わない。GitHub上で対象PRが MERGED であること、merge先が意図したbase branchであること、merge後のbase branchに対象PRの変更内容が含まれることを確認する。(squash merge / merge commit / rebase merge の方式に依存しない確認にする)
|
|
41
|
+
- ★これは共通正本 `claude-code-operations.md §11.3` と、この鉄則7 の2か所を正本にする(各リポの CLAUDE.md へは複製しない)。
|
|
42
|
+
- 確かめ方の例(値は実際の番号・ブランチ名に置き換える):`gh pr view 番号 --json state,baseRefName,mergeCommit` で、`state` が `MERGED`、`baseRefName` が意図した base branch(ふつうは `main`)であることを見る。`git fetch origin` のあと `git merge-base --is-ancestor mergeCommitのoid origin/base` が成功する(=合流でできたコミットが base に含まれる)ことを見る。`gh pr diff 番号` の変更が、`git diff` や `git show origin/base:ファイル` で base branch に入っていることを、変更したファイルごとに見比べる。
|
|
40
43
|
8. **ブラウザ検品(Playwright)の作法** — ★取りに行かない。PCに1回だけ入れた共有の道具を指すだけ(ADR-010)。
|
|
41
44
|
- ① **起動は必ず `chromium_sandbox=False`**(`--no-sandbox`)。付けないと起動できない。
|
|
42
45
|
- ② **道具は共有の場所を使う。自分でダウンロードしない。** python は `$HOME/.tyhld/pw-venv/bin/python`、`PLAYWRIGHT_BROWSERS_PATH` は `$HOME/.tyhld/pw-browsers`。**無ければそこで止めて「人の手番=`APPLY=1 bash scripts/pw-browser-install.sh`」と報告する。** `playwright install` も `pip install playwright` も職人は実行しない(毎便まるごとダウンロードが走り、便が遅くなる。通信自体は鉄則12のとおり許可済みだが、取りに行かないのが本筋=ADR-010)。
|
|
@@ -56,3 +59,8 @@ description: 采配くんの便(プロンプト末尾に CONDUCTOR_JOB:<id>
|
|
|
56
59
|
- **実機検品・検証用の使い捨てファイル(HTML・画像・ログ等)は、リポジトリの中に作らない。`/tmp/claude-1000/` に置く。リポジトリ直下に作ってしまった場合は自分で消そうとせず、報告に「残骸あり・場所」と書く。**
|
|
57
60
|
- **なぜ。** 実機 2026-09-03: ai-saku の画面便(PR#258・PR#269)で、検品用の使い捨てページ `_layout-check.html` をリポジトリ直下に作り、消そうとして番人(`rm` on dangerous path)に止められ、人が2回 `rm` した。**番人の `rm` 制限は正しい(緩めない)。悪いのは置き場。**
|
|
58
61
|
- 置き場を `/tmp/claude-1000/` にすれば、番人に止められることも、残骸がコミットに混ざることも起きない(根治=鉄則10)。Playwright の検品ページも同じ(鉄則8-④)。
|
|
62
|
+
14. **`.env` 類(`.env`・`.env.local`・`.env.example` などの雛形も含む)は職人が書き換えない(えふさん確定 2026-09-18・便 3d11f913)**
|
|
63
|
+
- **番人は `.env` 本体も `.env.example` などの雛形も、書き込みを止める**(Write/Edit の設定 deny と、Bash の `echo`/`tee`/`sed -i`/`cp`/`mv` などを、経路を問わず同じ結果にそろえた)。読み込みは今までどおり(雛形は読める・本物は読めない)。
|
|
64
|
+
- **なぜ。** 雛形(`.env.example`)は普通は秘密値を置かないが、「exampleだから常に安全」と番人へ例外を作ると、将来誤って実値が書かれたときの穴になる(利便性より安全側・えふさん確定)。
|
|
65
|
+
- **雛形(`.env.example` など)を直すときの正しい形=「案を作る→人が1回で反映」。** 職人は変更後の中身を**リポジトリの外(`/tmp/claude-1000/` の下)**に作り、報告に次の2つを載せる。① 変更の**差分**(何を足す/消すか)。② 人が**素のターミナルで1回貼るだけで反映できる1つのコマンド**(例:`cp /tmp/claude-1000/<下書き> <リポ>/.env.example`)。★このコマンドは**人が自分のターミナルで**打つ(職人の番人は人の端末には掛からない)。職人が `.env.example` を直接書き換えようとしても番人が止めるので、**回避せず、この形で案を出す**。
|
|
66
|
+
- **本物の `.env`・`.env.local` は、これまでどおり中身も書き込みも扱わない**(秘密の値は1文字も読まない・書かない=鉄則12の金庫)。
|