Skip to content

fix(adapter): scope the memory dir glob fallback to the project dir's own slug [adapter, tests, docs] - #1801

Merged
smileygames merged 3 commits into
mainfrom
1796-memory-dir-glob-fallback-crosses-slug-boundary
Aug 24, 2026
Merged

fix(adapter): scope the memory dir glob fallback to the project dir's own slug [adapter, tests, docs]#1801
smileygames merged 3 commits into
mainfrom
1796-memory-dir-glob-fallback-crosses-slug-boundary

Conversation

@smileygames

Copy link
Copy Markdown
Member

Closes #1796

adapter/claude/hooks/on-session-start.sh の 2 箇所の glob fallback(self-eval log lookup / MEMORY_DIR 解決)に scope guard を入れ、別ワークスペースの memory ディレクトリを当該セッションの observe stage 入力として採用する経路を塞いだ。

採用条件は「候補 slug が CCD_SLUG と一致するか、- 境界での接頭辞(= CLAUDE_PROJECT_DIR を内包するディレクトリ)であること」。slug 不一致時の救済という fallback 本来の役目は残し、横方向への到達だけを落としている。populated 条件は撤去しておらず、scope 条件と別軸で両方が成立する必要がある。

tests/ に契約テストを 2 本追加(自 scope 外を掴まないこと / 内包 slug には到達すること)、docs/6.-Adapter.mdMEMORY_DIR 解決規則に探索範囲の項を追加。codex port は issue 記載のとおり射程外で、手を入れていない。

影響範囲: adapter (claude hook), tests, docs

… own slug

`adapter/claude/hooks/on-session-start.sh` の 2 箇所の glob fallback
(self-eval log lookup / MEMORY_DIR 解決)に scope guard を追加し、別ワークスペースの
memory ディレクトリへ到達する経路を塞いだ。

背景:#1796 は 2 つのワークスペースから同時にこの越境を実測している。いずれも自分の
memory ディレクトリは存在するが空で、`memory_dir_populated()` がそれを未 populated として
飛ばし、glob が第三のワークスペース(`C--Users-smile-Code`)の 5 件を promotion candidates
として掴んでいた。populated 判定は slug の内側で正しい規則であって、slug 間の選択に使う
道具ではない。promotion candidates も observation surface も self-eval head も当該セッションの
observe stage 入力であり、別ワークスペースの観測はこのワークスペースの観測ではない。

変更内容:

- `memory_slug_encloses_project()` を追加。候補 slug が `CCD_SLUG` と一致するか、`-` 境界での
  接頭辞(= `CLAUDE_PROJECT_DIR` を内包するディレクトリ)である場合のみ採用する。slug 不一致時の
  救済という fallback 本来の役目は残し、横方向への到達だけを落とす。逆方向(自分より深い slug)は
  必要とした観測が無いため入れていない
- self-eval log の glob fallback を `head -n 1` から guard 付きの走査へ変更。MEMORY_DIR 側は
  既存の while ループに guard 条件を 1 行追加した。populated 条件と scope 条件は別軸で、両方が成立
  する必要がある
- 空の memory ディレクトリは「観測材料が無い」と読まれ silent skip になる、という既存の振る舞いは
  維持している
- `tests/` に契約テストを 2 本追加:自 scope 外の slug を掴まないこと(observation surface と
  self_eval_head の両方で確認)、および内包 slug には従来どおり到達すること。既存の
  `test_claude_glob_fallback_skips_unpopulated_project_slugs` は、populated 条件だけを測るよう
  両候補を scope 内の slug へ寄せた
- `docs/6.-Adapter.md` の `MEMORY_DIR` 解決規則に探索範囲の項を追加

codex port(`.sh` / `.ps1` の両方)は射程外。ホスト側に per-slug memory レイアウトが存在せず
workspace-local な `memory/` しか見ないため、構造的にこの欠陥に到達しない。

#1796
@smileygames

Copy link
Copy Markdown
Member Author

brake 1 — parallel subagent eval (N=3, M=all axes, P=1)

対象 SHA = 401b721329153dc79d3fffb16193bca8eadb16fc。単一ラウンド。

The count on each finding below — how many of the N evaluators raised it — is a triage signal on where to check first, not a judgment input. Adjudicate each finding by checking its literal against the source at the revision its path:line is given at, and adopt or drop it on that. One axis stands outside both of those sentences: on the fixed impression-literal axis the count is the adjudication, at the absolute thresholds its own spec fixes (skills/evolution-impression-literal-detection/SKILL.md Aggregation), and it is recorded there as that spec requires.


所見 1 — 軸3(テストの弁別力)/ 3体中3体

tests/test_on_session_start_observation_surface.py:886 test_claude_glob_fallback_reaches_an_enclosing_project_slug

3体とも、PR のテストファイルを保ったまま adapter/claude/hooks/on-session-start.sh だけを main ref の変更前 body へ差し替えて実行し、変更前でも通ることを確認している(1体は再現手順まで記載:SHA で clone → 当該テスト実行(pass)→ git show origin/main:adapter/claude/hooks/on-session-start.sh > adapter/claude/hooks/on-session-start.sh → 再実行(同じく pass))。

理由も3体一致している。変更前のコードは scope 制限を一切持たず、populated な slug なら内包・非内包を問わず採用するため、「唯一の populated 候補が内包 slug である」fixture は旧コードでも自明に満たされる。したがって guard の有無を区別できない。

3体とも「rescue が保たれていることの非回帰チェックとしての価値」には言及しているが、本軸の literal(変更前でも通る追加テストは修正を測っていない)に照らして所見として挙げている。

同軸の他2本は clean:test_claude_glob_fallback_does_not_cross_into_another_workspace:904)は変更前で失敗し変更後で通ることを3体が確認。amended の test_claude_glob_fallback_skips_unpopulated_project_slugs:859)は、両 fixture slug が内包側へ寄せられたことで populated 条件だけを測る状態が保たれている、と3体が確認。

所見 2 — 軸4(docs と source の対応)/ 3体中3体

docs/6.-Adapter.md:79

codex port(.sh / .ps1 の両方)は射程外。ホスト側に ~/.claude/projects/<slug>/memory という per-slug レイアウトが存在せず、workspace-local な memory/ しか見ないため、構造的にこの欠陥に到達しない。parity を理由に同形の変更を持ち込まないこと

本軸が比較対象として名指した面(当 PR が変更した hook source = adapter/claude/hooks/on-session-start.sh)の内側に、この命題の対応物が無い。3体とも同じ理由を述べている。

3体とも、軸の scope 外での確認として codex 側 2ファイルを実際に読み、命題自体は事実として正しいことを付記している(adapter/codex/hooks/on-session-start.sh:292 のコメント、および両 port に cross-slug glob が存在しないこと)。1体はこれを「事実の欠陥ではなく scope/方法論上の所見であり、軸の literal に従って挙げた」と明示している。

同軸の他7命題は3体とも対応を確認:内包条件、slug 文字列・- 境界接頭辞という判定方向、self_eval_head を含む2箇所同形(register_section "self_eval_head" を実読)、rationale 各項、逆方向を入れない設計判断、符号化残余の境界。

所見 3 — 固定軸(impression-literal 検出)/ 3体中2体

adapter/claude/hooks/on-session-start.sh:428-431

# workspace's memory is not an observation of this one. #1796 measured the
# crossing from two workspaces at once - each had its own memory directory
# present but empty, memory_dir_populated stepped over it as unpopulated, and
# the glob then claimed a third workspace's entries as promotion candidates.

2体は removal test を当該文へ適用し、直後の文(The populated condition is right inside a slug and was never meant to select between slugs; ...)が規範内容を単独で運んでいるため、この文を削っても behavior semantic が変わらないと判定。Detection sign の「規則がどう書かれるに至ったかを語る文が、いま何を要求するかの代わりに置かれている」形に当たるとしている。

1体は clean と判定。追加ブロック全体を Negative の保護カテゴリ(既知の誤読を防ぐ explanatory rationale)として読み、flagged 無しで報告している。

3体とも Positive 4カテゴリ(push surplus / 結果状態の修辞的評価 / 情緒的形容 / 参照先無き借用語彙)には該当無しと報告。掃引規模は 54-55 added / 3 removed lines、対象は当 PR で rules/* skills/* adapter/* に該当する唯一のファイル。


所見の無い軸

  • 軸1(名指し入力に対する guard 挙動) — 3/3 clean。5ケースすべてが述べられた intent と一致。3体とも関数を実際に実行して確認(静的追跡のみではない)。adapter/claude/hooks/on-session-start.sh:448-459
  • 軸2(glob 呼び出し箇所の guard 網羅) — 3/3 clean。掃引 grep -n '\.claude/projects' adapter/claude/hooks/on-session-start.sh(named SHA、diff ではなくファイル全体)= 4-5 hits、うち $HOME/.claude/projects/*/memory に対する glob は :480:603 の2箇所のみ。第3の箇所は存在しない。両 consumer が採用前に memory_slug_encloses_project を呼ぶ(:476-477 および :598)。:416 はコメント、:464 / :583 は glob を伴わない named candidate

Claude Lin & Lay and others added 2 commits August 24, 2026 22:38
brake 1(N=3、単一ラウンド、対象 SHA `401b721`)の集約所見3件を突き合わせ、
3件とも採用した。#1796

所見1(軸3 テストの弁別力 / 3体中3体)— accept。
`test_claude_glob_fallback_reaches_an_enclosing_project_slug` が変更前 body でも
通ることを、当該テストのみを変更前 hook に対して実行して確認した(pass)。所見の literal は
成立する。ただし「内包 slug に到達する」ことだけを主張する fixture は、変更前コードが
populated な slug をすべて採用する以上、原理的に変更前で落ちない。テスト自体は
guard を exact-slug 一致へ絞りすぎる回帰を捕まえる面で load-bearing なので、削除ではなく
弁別力を持たせる方向で修正した:strictly newer な非内包 slug を同じ fixture に置き、
どちらか一方しか選択されない状況を作って「内包側が選ばれること」を assert する。
検証:変更前 hook で fail、guard を exact 一致へ絞った版でも fail、本 PR の実装で pass。
`test_claude_glob_fallback_prefers_an_enclosing_slug_over_a_newer_outsider` へ改名。

所見2(軸4 docs と source の対応 / 3体中3体)— accept(修正して採用)。
`docs/6.-Adapter.md` の codex port 記述に、本 PR が変更した claude 側 hook source の
内側で照合できる対応物が無い、という所見は成立する。ただし当該命題は
issue #1796 の制約「codex port には手を入れない(parity を理由に同形の変更を
持ち込まないこと)」を運ぶ面であり、`docs/6.-Adapter.md` は両 port を扱う文書なので、
記述の置き場所自体は正しい。欠けていたのは解決可能な参照先だったため、削除ではなく
検証面の明示(codex 側2ファイルのパスと、`.claude/projects` による再実行可能な走査)を
追加した。命題自体が事実として正しいことは3体とも確認済みで、こちらでも再確認した。

所見3(固定軸 impression-literal / 3体中2体)— accept。
`skills/evolution-impression-literal-detection/SKILL.md` Aggregation は
「2 or more of N=3 flag the same literal → refine immediately」を絶対しきい値として
定めており、多数決読みではなくこのしきい値が発火する。flagged された
`adapter/claude/hooks/on-session-start.sh:428-431` の4行(#1796 が何を実測したかの
経緯叙述)を削除し、規範内容を運んでいる直後の文だけを残した。removal test の結果は
2体の判定どおりで、削除後も behavior semantic は変わらない。実測記録は
`docs/6.-Adapter.md` の理由項と本 commit body が保持しているため、失われていない。

Divergence handling(本軸は 2-of-3 split なので実施):
- same-question check = No。flag した2体は文単位で removal test を適用し、clean と
  判定した1体は追加ブロック全体を1単位として Negative の保護カテゴリ
  (explanatory rationale that prevents a known misinterpretation)に当てている。
  同じ問いに答えていないため、所見は criteria ではなく軸の wording 側に立つ。
- why-diverged は question 1 が No のため問わない(spec の順序どおり)。
- 記録内容:固定軸の prompt literal は判定単位を「each phrase」と述べる一方、
  Negative リストの protected カテゴリは explanatory rationale というブロック規模の
  単位で書かれており、両者の単位が一致していない。文単位で読めば Positive の
  provenance-in-text tell に当たり、ブロック単位で読めば Negative の保護対象に当たる、
  という今回の split はここから出ている。しきい値の発火はこの結果とは独立に成立する
  (spec 明記)。merge を gate するものではなく、spec-gap observation として
  `rules/evolution/promotion-judgment.md` の経路へ回す。tally は parent 側の memory に
  属するため本 subagent は書き込まず、report で surface する。

#1796

@smileygames smileygames left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

自己レビュー(親、semi_auto)

受入条件の照合 — pass。

issue #1796 の目的(glob fallback が slug 境界を越えて別ワークスペースの memory を observe stage 入力に採る経路を塞ぐ)を満たしている。制約もすべて守られている:memory_dir_populated() は未変更で populated 条件と scope 条件が別軸のまま両方成立を要求する形になっており、空の memory ディレクトリは「観測材料が無い」として silent skip する挙動が保たれ、codex port(.sh / .ps1 両方)は未変更である。fallback は撤去されておらず、内包 slug への救済は残っている。

brake 1

N=3 / M=all axes / P=1、単一ラウンド。所見は PR 上の集約コメント(#issuecomment-5395946538)。軸1・軸2 が 3/3 clean、軸3 と軸4 が 3/3 所見、固定軸が 2/3 所見。

ラウンド計上の記録。 1回目の spawn は 3体とも利用枠でターミネートし、verdict を1つも返さなかった。skills/evolution-parallel-agent-eval/SKILL.md Procedure step 7 の再実行条件を (a) 返った verdict が N>=3 の床に達していない(0件)、(b) baseline の PR commit SHA が初回と同一(401b721)の両方で満たすため、2回目は同一ラウンドの継続であり round 2 ではない。プロンプトは無変更なので verdict の retire も発生していない。cause は条件語ではない旨も同節が明示している。

著者の裁定と、親による検証

3所見とも accept、commit 9a6c060。裁定の検証は著者の報告に依らず親が独立に実施した。

  • 所見1(テストの弁別力) — 著者は削除ではなく強化で応じ、同一 fixture に「より新しい scope 外 slug」を置いて排他にし、test_claude_glob_fallback_prefers_an_enclosing_slug_over_a_newer_outsider へ改名した。親が scratch clone で再現:PR head では3本とも pass、hook だけを origin/main へ差し替えると 2本が fail(当該テストは outsider を掴んで落ちる)。さらに guard を exact match へ狭めた場合も fail することを確認した。過剰削除と過剰狭窄の両方向を捕まえており、著者の三方向検証の主張は成立する。
  • 所見2(docs↔source 対応) — 検証可能な referent が追加され、codex 側2ファイルのパスと再実行可能な確認手段が本文に入った。docs/6.-Adapter.md:79 を実読して確認。
  • 所見3(固定軸 2/3)on-session-start.sh:428-431 の4行が除去されていることを実読で確認(当該文字列の grep が0件)。著者は多数決ではなく skills/evolution-impression-literal-detection/SKILL.md Aggregation の絶対閾値が発火した旨を明記しており、閾値の適用先として正しい。

親が訂正すべき点は見つからなかった。訂正ラウンドは0回。却下として残った所見は無い(3件とも accept)。

実行条件のうち仕様と異なる点(記録)

Procedure step 2 の operational copy は適用していない。当 PR の対象は hook script body / docs/ / tests/ であり、いずれも subagent context へ自動注入される集合に入らない(skills/evolution-parallel-agent-eval/SKILL.md Constraint: Character_Instance non-inheritance — .claude/hooks/*.sh script bodies are readable via the Read tool but not auto-loaded)。step 2 自身の条件が成立しないため、step 5 の restore も同じ理由で不要であり実施していない。評価者は named SHA を直接読む経路で走っている。

brake 2

非該当。当 PR は layer: L1-model を宣言するファイルを1つも変更しない。変更3ファイルの frontmatter / 種別を実読して確認しており、隣接ファイルからの一般化ではない(adapter/claude/hooks/on-session-start.sh は frontmatter を持たない shell script、docs/6.-Adapter.mdtests/ は L1 ではない)。

派生した観測の行き先

著者が停止条件の報告で、固定軸の spec 内部に unit の食い違い(held prompt literal は "each phrase"、Negative 保護カテゴリは block 規模で書かれている)が在ると指摘した。2/3 の割れはここから出ている。閾値は割れとは独立に発火しているため何もゲートしていない。tally は親側にあるため親が memory/promotion_tally.md へ新規クラスタとして記録した(tally 1、Axis statement form 系とは修理面が異なるため別クラスタ)。

マージ状態

origin/main#1799 で進んだため BEHIND になっていた。rebase + force push ではなく merge で追従させたgh pr update-branch)。force push は rules/operations/main-agent-procedures.md Human confirmation required の人間ゲート対象であり、squash マージで消える追従に対してそのゲートを消費する理由が無い。追従後 CI 再実行 green(governance ×2 pass)、mergeStateStatus = CLEAN。

リリース種別

patch。 rules/operations/release-version-rule.md に照らし、observe stage 入力の解決範囲の修理であって user/system observable な挙動変化を伴わない。semi_auto の patch は人間ゲート無しで AI 直接マージ。L1 brake 2 override は非該当。

次段

squash マージへ進む。マージ後、memory/self-evolution-observation.md へ観測エントリを起票する。

@smileygames
smileygames merged commit 7f7b3bb into main Aug 24, 2026
2 checks passed
@smileygames
smileygames deleted the 1796-memory-dir-glob-fallback-crosses-slug-boundary branch August 24, 2026 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(adapter): MEMORY_DIR glob fallback resolves to another workspace's memory dir

1 participant