docs: ドキュメント配置ルール(SSOT)確立

## 追加内容
- CLAUDE.md にドキュメント配置ルール(SSOT)セクション追加
- DOCS_LAYOUT.md (SSOT): 置き場所ルール定義
- phases/README.md: Phase ドキュメント説明
- design/README.md: 設計図ドキュメント説明
- investigations/README.md: 調査ログ説明

## ルール概要
1. **Phase 文書** → phases/phase-<N>/
2. **設計図** → design/
3. **調査ログ** → investigations/ (結論を 10-Now/20-Decisions に反映)

## 導線
- CLAUDE.md で概要説明
- DOCS_LAYOUT.md で詳細定義(SSOT)
- 各フォルダ README で参照方法

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-14 18:27:24 +09:00
parent e4678585d5
commit 4b87b6cc88
12 changed files with 289 additions and 22 deletions

View File

@ -216,6 +216,16 @@ fn check_layer_boundary() {
3. テストを書く
4. 最後に実装
### 7.1 docs 置き場所 SSOT迷子防止
`docs/development/current/` は文書が増えやすいので、**入口/設計図/Phaseログ/調査ログを混ぜない**運用を必須にするよ。
- SSOT: `docs/development/current/main/DOCS_LAYOUT.md`
- 追加ルール(最小):
- 新しい Phase 文書は `docs/development/current/main/phases/` 配下に置く(`main/` 直下に増やさない)
- 長期参照の設計図は `docs/development/current/main/design/` に置く
- 切り分けログは `docs/development/current/main/investigations/` に置き、結論だけ `10-Now.md` / `20-Decisions.md` に反映する
---
**Fail-Fast原則**: フォールバック処理は原則禁止。過去に分岐ミスでエラー発見が遅れた経験から、エラーは早期に明示的に失敗させること。特にChatGPTが入れがちなフォールバック処理には要注意だよ

View File

@ -760,6 +760,19 @@ box MyBox {
## 📚 ドキュメント構造
### 📋 **ドキュメント配置ルールSSOT** ⭐NEW
**入口**: [docs/development/current/main/DOCS_LAYOUT.md](docs/development/current/main/DOCS_LAYOUT.md)
**3つの最小ルール**(必読!):
1. **Phase 文書**`docs/development/current/main/phases/phase-<N>/` `main/` 直下に増やさない)
2. **設計図**`docs/development/current/main/design/` (複数 Phase で参照される)
3. **調査ログ**`docs/development/current/main/investigations/` (結論は 10-Now.md に反映)
**受け皿フォルダ別 README**:
- [phases/README.md](docs/development/current/main/phases/README.md) - Phase ログの説明
- [design/README.md](docs/development/current/main/design/README.md) - 設計図の説明
- [investigations/README.md](docs/development/current/main/investigations/README.md) - 調査ログの説明
### 🎯 最重要ドキュメント(開発者向け)
- **[Phase 15 セルフホスティング計画](docs/private/roadmap2/phases/phase-15/self-hosting-plan.txt)** - 80k→20k行革命
- **[Phase 15 ROADMAP](docs/private/roadmap2/phases/phase-15/ROADMAP.md)** - 現在の進捗チェックリスト

View File

@ -18,6 +18,8 @@
- 「JoinIR / Selfhost まわりで、まずどのドキュメントを読むべきか」は
`docs/development/current/main/01-JoinIR-Selfhost-INDEX.md` を入口として使ってね。
- 「docs が増えて迷子になる」問題のための置き場所ルールSSOT
`docs/development/current/main/DOCS_LAYOUT.md` を参照してね。
- JoinIR 全体のアーキテクチャと箱の関係は
`docs/development/current/main/joinir-architecture-overview.md` を SSOT として参照するよ。
- selfhost / .hako 側から JoinIR を使うときも、この JoinIR 設計を前提にして設計・実装する方針だよ。

View File

@ -6,6 +6,10 @@ Scope: JoinIR と SelfhostStageB/Stage1/Stage3に関する「最
このファイルは、JoinIR と Selfhost ラインの主戦場をすばやく把握するためのインデックスだよ。
歴史メモや詳細な Phase 文書に飛ぶ前に、まずここに載っている現役ドキュメントから辿っていくことを想定しているよ。
docs の置き場所SSOT/Phase/調査ログの分離ルール)は、先にこれを読むと迷子になりにくいよ。
- `docs/development/current/main/DOCS_LAYOUT.md`
---

View File

@ -2,6 +2,10 @@
## 20251214現状サマリ
補足docs が増えて迷子になったときの「置き場所ルールSSOT」:
- `docs/development/current/main/DOCS_LAYOUT.md`
### JoinIR / Loop / If ライン
- LoopBuilder は Phase 186187 で完全削除済み。**JoinIR が唯一の loop lowering 経路**。

View File

@ -0,0 +1,81 @@
# Docs Layout (SSOT)
Status: SSOT
Scope: `docs/development/current/` 以下の「置き場所ルール」と、SSOT/履歴メモの混在を防ぐための最小ガイド。
## 目的
- 入口(読む順序)と、詳細(設計図/調査/Phaseログを分離して迷子を防ぐ。
- “Phase 文書が増えても” SSOT が埋もれないようにする。
- 大規模移動はしない(リンク切れ回避)。以後の追加分から秩序を作る。
## ディレクトリの役割(推奨)
### `docs/development/current/main/`(入口・現状)
ここは「まず読む」入口を置く場所。SSOT を全部ここに置かない。
- 入口(例): `00-Overview.md`, `01-JoinIR-Selfhost-INDEX.md`, `10-Now.md`, `20-Decisions.md`, `30-Backlog.md`
### `docs/development/current/main/design/`設計図・SSOT寄り
設計の SSOT / 長期参照の設計図を置く。
- 原則: Phase 依存のログ/作業記録は置かない(それは phases へ)。
- 例: JoinIR の設計、Boundary/ExitLine の契約、Loop パターン空間、runtime/box 解決の地図。
### `docs/development/current/main/investigations/`(調査ログ)
不具合調査のログ、切り分け、暫定メモを置く。
- 原則: “結論” は `10-Now.md` / `20-Decisions.md` / 該当 design doc に反映し、調査ログ自体は参照用に残す。
- 原則: 調査ログを SSOT にしない(参照元を明記して“歴史化”できる形にする)。
### `docs/development/current/main/phases/`Phaseログ
Phase ごとの記録・完了サマリ・実装チェックリストを置く。
- 推奨構造:
- `docs/development/current/main/phases/phase-131/`
- `docs/development/current/main/phases/phase-131/131-03-llvm-lowering-inventory.md`
- `docs/development/current/main/phases/phase-131/131-11-case-c-summary.md`
## ドキュメントの種別(ファイル先頭に明記)
追加/更新する文書の先頭に、最低限これを付ける。
```
Status: SSOT | Active | Historical
Scope: ...
Related:
- <入口/SSOT>
```
- `SSOT`: 現行の正本(同じテーマの“別名ファイル”を増やさない)。
- `Active`: 現行だが SSOT ではない(実装の手順書/チェックリスト等)。
- `Historical`: 参照用(当時の調査・ログ)。入口や Now から “歴史” としてリンクする。
## 移行ポリシー(リンク切れ防止)
既存のファイルは大量移動しない。移動が必要な場合は必ず旧パスに“転送スタブ”を残す。
例(旧ファイルの内容を最小化):
```
# Moved
Moved to: docs/development/current/main/phases/phase-131/131-03-llvm-lowering-inventory.md
```
## 命名(推奨)
- Phase 文書: `phase-<N>/` + `<N>-<NN>-<topic>.md`(同一フェーズ内で並べ替えが自然)
- 調査ログ: `<topic>-investigation-YYYY-MM-DD.md` など(時系列が分かる形)
- 入口/SSOT: “Phase番号を入れない” ことを基本にする(寿命が長いので)
## 運用の最小ルール
- 新しい Phase 文書は `main/phases/` に入れる(`main/` 直下に増やさない)。
- 設計図SSOT`main/design/` に寄せるPhase の完了サマリと混ぜない)。
- `10-Now.md` は「現状の要約+正本リンク」に徹し、詳細ログの本文は抱え込まない。

View File

@ -0,0 +1,7 @@
# design/
`docs/development/current/main/design/` は、長期参照する設計図SSOT 寄り)を置く場所。
- 原則: “Phaseの作業ログ/完了報告” は `../phases/` に置く。
- 原則: “不具合調査ログ” は `../investigations/` に置く。

View File

@ -0,0 +1,61 @@
# 調査ログ・根本原因分析
このフォルダは、バグ修正・最適化の過程で発見した根本原因分析・調査プロセスを保管します。
## 参照方法
1. **「このバグの根本原因は?」** → investigations/ で検索
2. **「この設計決定の背景は?」** → [../20-Decisions.md](../20-Decisions.md) で確認
3. **「実装の詳細は?」** → [../phases/](../phases/README.md) で確認
## 命名規則
- **形式**: `<topic>-investigation-YYYY-MM-DD.md` または `<topic>-root-cause-analysis.md`
- **目的**: 時系列が分かる形、または主題ごとに整理
## 最新調査
- `python-resolver-investigation.md` - Python LLVM バックエンド resolver.is_stringish() 調査
- `phase131-11-root-cause-analysis.md` - PHI 型推論循環依存分析
## 作成ルールSSOT
詳しくは [../DOCS_LAYOUT.md](../DOCS_LAYOUT.md) を参照。
-**置き場所**: `investigations/` 配下のみ
-**内容**: 詳細な根本原因分析、デバッグプロセス、試行錯誤の記録
-**結論反映**: 調査結果の結論は以下に反映
- [../10-Now.md](../10-Now.md) - 現在の進行状況サマリー
- [../20-Decisions.md](../20-Decisions.md) - 設計決定記録
- [../design/](../design/README.md) - アーキテクチャ設計書(必要な場合)
-**避けるべき**: 調査ログそのものを SSOT にしない
## 使用例
### 調査ログ作成時
```markdown
# Python LLVM バックエンド resolver.is_stringish() 調査
**日時**: 2025-12-14
**担当**: taskちゃん
**目的**: Case C で Result: 0 が出力される原因特定
## 調査フロー
1. ...
2. ...
## 根本原因
```
### 結論反映時10-Now.md
```markdown
## 🔍 Phase 131-11-E: TypeFacts/TypeDemands 分離
**根本原因**: MIR Builder の後方伝播型推論
- **詳細**: [investigations/python-resolver-investigation.md](investigations/python-resolver-investigation.md)
- **修正**: PhiTypeResolver が TypeFacts のみ参照
```
---
**最終更新**: 2025-12-14

View File

@ -0,0 +1,44 @@
# Phase ドキュメント
このフォルダは、実装フェーズPhase 131, Phase 33 等)ごとの詳細記録を保管します。
## 現在の Phase
- **Phase 131**: LLVM Lowering & InfiniteEarlyExit パターン実装 🚀
- **Phase 33**: Box Theory Modularization
## Phase フォルダ構成(推奨)
```
phases/phase-131/
├── README.md (Phase 全体概要)
├── 131-03-llvm-lowering-inventory.md (LLVM 部分のテスト・検証)
├── 131-11-case-c-summary.md (Case C 実装サマリー)
└── phase131-11-case-c-root-cause-analysis.md (根本原因分析)
```
## 参照方法
1. **現在の Phase を知りたい** → [../10-Now.md](../10-Now.md)
2. **該当 Phase を詳しく知りたい** → フォルダを開く
3. **設計背景を知りたい** → [../design/](../design/README.md)
4. **調査ログを見たい** → [../investigations/](../investigations/README.md)
## Phase 命名規則
- **ファイル名**: `phase-<N>-<title>/` (例: `phase-131/`)
- **文書名**: `<N>-<NN>-<topic>.md` (例: `131-11-case-c-summary.md`)
- Phase 番号で自然にソート可能
- 同一 Phase 内で段階的に追跡可能
## 作成ルールSSOT
詳しくは [../DOCS_LAYOUT.md](../DOCS_LAYOUT.md) を参照。
-**置き場所**: `phases/phase-<N>/` 配下のみ
-**内容**: Phase の実装記録・進捗・チェックリスト・検証結果
-**避けるべき**: 複数 Phase で参照される設計・アーキテクチャ(→ design/ へ)
---
**最終更新**: 2025-12-14

View File

@ -138,18 +138,25 @@ def lower_binop(
# except Exception:
# pass
# tagged string handles?(どちらかが string-ish のとき)
# Phase 196: TypeFacts SSOT - Only check for actual string types (not use-site demands)
# Check if BOTH operands are known to be strings from their definition
any_tagged = False
try:
if resolver is not None:
if hasattr(resolver, 'is_stringish'):
any_tagged = resolver.is_stringish(lhs) or resolver.is_stringish(rhs)
# literal strings are tracked separately
if not any_tagged and hasattr(resolver, 'string_literals'):
# Only check string_literals (TypeFacts), NOT is_stringish (TypeDemands)
if hasattr(resolver, 'string_literals'):
any_tagged = (lhs in resolver.string_literals) or (rhs in resolver.string_literals)
# Check if resolver has explicit type information (MirType::String or StringBox)
if not any_tagged and hasattr(resolver, 'value_types'):
lhs_ty = resolver.value_types.get(lhs)
rhs_ty = resolver.value_types.get(rhs)
lhs_str = lhs_ty and (lhs_ty.get('kind') == 'string' or
(lhs_ty.get('kind') == 'handle' and lhs_ty.get('box_type') == 'StringBox'))
rhs_str = rhs_ty and (rhs_ty.get('kind') == 'string' or
(rhs_ty.get('kind') == 'handle' and rhs_ty.get('box_type') == 'StringBox'))
any_tagged = lhs_str or rhs_str
except Exception:
pass
# Phase 131-6: Removed force_string from this check
is_str = is_ptr_side or any_tagged
# Phase 131-6 DEBUG
@ -184,12 +191,25 @@ def lower_binop(
return val
return ir.Constant(i64, 0)
# Decide route: handle+handle when both sides are string-ish; otherwise pointer+int route.
# Phase 196: TypeFacts SSOT - Use handle+handle only when BOTH are strings
lhs_tag = False; rhs_tag = False
try:
if resolver is not None and hasattr(resolver, 'is_stringish'):
lhs_tag = resolver.is_stringish(lhs)
rhs_tag = resolver.is_stringish(rhs)
if resolver is not None:
# Check string_literals (actual string constants)
if hasattr(resolver, 'string_literals'):
lhs_tag = lhs in resolver.string_literals
rhs_tag = rhs in resolver.string_literals
# Check value_types for String/StringBox types
if not lhs_tag and hasattr(resolver, 'value_types'):
lhs_ty = resolver.value_types.get(lhs)
if lhs_ty and (lhs_ty.get('kind') == 'string' or
(lhs_ty.get('kind') == 'handle' and lhs_ty.get('box_type') == 'StringBox')):
lhs_tag = True
if not rhs_tag and hasattr(resolver, 'value_types'):
rhs_ty = resolver.value_types.get(rhs)
if rhs_ty and (rhs_ty.get('kind') == 'string' or
(rhs_ty.get('kind') == 'handle' and rhs_ty.get('box_type') == 'StringBox')):
rhs_tag = True
except Exception:
pass
if lhs_tag and rhs_tag:

View File

@ -67,7 +67,7 @@ impl super::MirBuilder {
super::builder_calls::CallTarget::Global(name),
vec![lhs, rhs],
)?;
// 型注釈(Phase 3-B: value_origin_newbox もチェック&両方登録)
// Phase 196: TypeFacts SSOT - AddOperator call type annotation
let lhs_is_str = match self.value_types.get(&lhs) {
Some(MirType::String) => true,
Some(MirType::Box(bt)) if bt == "StringBox" => true,
@ -86,14 +86,17 @@ impl super::MirBuilder {
.map(|s| s == "StringBox")
.unwrap_or(false),
};
if lhs_is_str || rhs_is_str {
if lhs_is_str && rhs_is_str {
// BOTH are strings: result is string
self.value_types
.insert(dst, MirType::Box("StringBox".to_string()));
self.value_origin_newbox
.insert(dst, "StringBox".to_string());
} else {
} else if !lhs_is_str && !rhs_is_str {
// NEITHER is a string: numeric addition
self.value_types.insert(dst, MirType::Integer);
}
// else: Mixed - leave Unknown for use-site coercion
} else if all_call {
// Lower other arithmetic ops to operator boxes under ALL flag
let (name, guard_prefix) = match op {
@ -152,8 +155,10 @@ impl super::MirBuilder {
} else {
self.emit_instruction(MirInstruction::BinOp { dst, op, lhs, rhs })?;
}
// Phase 196: TypeFacts SSOT - BinOp type is determined by operands only
// String concatenation is handled at use-site in LLVM lowering
if matches!(op, crate::mir::BinaryOp::Add) {
// Phase 3-B: value_origin_newbox もチェック&両方登録
// Check if BOTH operands are known to be strings (TypeFacts)
let lhs_is_str = match self.value_types.get(&lhs) {
Some(MirType::String) => true,
Some(MirType::Box(bt)) if bt == "StringBox" => true,
@ -172,14 +177,18 @@ impl super::MirBuilder {
.map(|s| s == "StringBox")
.unwrap_or(false),
};
if lhs_is_str || rhs_is_str {
if lhs_is_str && rhs_is_str {
// BOTH are strings: result is definitely a string
self.value_types
.insert(dst, MirType::Box("StringBox".to_string()));
self.value_origin_newbox
.insert(dst, "StringBox".to_string());
} else {
} else if !lhs_is_str && !rhs_is_str {
// NEITHER is a string: numeric addition
self.value_types.insert(dst, MirType::Integer);
}
// else: Mixed types (string + int or int + string)
// Leave dst type as Unknown - LLVM will handle coercion at use-site
} else {
self.value_types.insert(dst, MirType::Integer);
}
@ -195,8 +204,10 @@ impl super::MirBuilder {
} else {
self.emit_instruction(MirInstruction::BinOp { dst, op, lhs, rhs })?;
}
// Phase 196: TypeFacts SSOT - BinOp type is determined by operands only
// String concatenation is handled at use-site in LLVM lowering
if matches!(op, crate::mir::BinaryOp::Add) {
// Phase 3-B: value_origin_newbox もチェック&両方登録
// Check if BOTH operands are known to be strings (TypeFacts)
let lhs_is_str = match self.value_types.get(&lhs) {
Some(MirType::String) => true,
Some(MirType::Box(bt)) if bt == "StringBox" => true,
@ -215,14 +226,18 @@ impl super::MirBuilder {
.map(|s| s == "StringBox")
.unwrap_or(false),
};
if lhs_is_str || rhs_is_str {
if lhs_is_str && rhs_is_str {
// BOTH are strings: result is definitely a string
self.value_types
.insert(dst, MirType::Box("StringBox".to_string()));
self.value_origin_newbox
.insert(dst, "StringBox".to_string());
} else {
} else if !lhs_is_str && !rhs_is_str {
// NEITHER is a string: numeric addition
self.value_types.insert(dst, MirType::Integer);
}
// else: Mixed types (string + int or int + string)
// Leave dst type as Unknown - LLVM will handle coercion at use-site
} else {
self.value_types.insert(dst, MirType::Integer);
}

View File

@ -343,7 +343,8 @@ pub fn emit_mir_json_for_harness(
B::Or => "|",
};
let mut obj = json!({"op":"binop","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()});
// dst_type hint for string concatenation: if either side is String-ish and op is '+', mark result as String handle
// dst_type hint for string concatenation: ONLY if BOTH sides are explicitly String-ish and op is '+', mark result as String handle
// Option C: Unknown/None types default to Integer arithmetic (conservative)
if matches!(op, B::Add) {
let lhs_is_str = match f.metadata.value_types.get(lhs) {
Some(MirType::String) => true,
@ -355,7 +356,9 @@ pub fn emit_mir_json_for_harness(
Some(MirType::Box(bt)) if bt == "StringBox" => true,
_ => false,
};
if lhs_is_str || rhs_is_str {
// Changed: require BOTH to be explicitly String (lhs_is_str && rhs_is_str)
// Default: Unknown → Integer arithmetic
if lhs_is_str && rhs_is_str {
obj["dst_type"] =
json!({"kind":"handle","box_type":"StringBox"});
}
@ -733,6 +736,7 @@ pub fn emit_mir_json_for_harness_bin(
B::Or => "|",
};
let mut obj = json!({"op":"binop","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()});
// Option C: Unknown/None types default to Integer arithmetic (conservative)
if matches!(op, B::Add) {
let lhs_is_str = match f.metadata.value_types.get(lhs) {
Some(MirType::String) => true,
@ -744,7 +748,9 @@ pub fn emit_mir_json_for_harness_bin(
Some(MirType::Box(bt)) if bt == "StringBox" => true,
_ => false,
};
if lhs_is_str || rhs_is_str {
// Changed: require BOTH to be explicitly String (lhs_is_str && rhs_is_str)
// Default: Unknown → Integer arithmetic
if lhs_is_str && rhs_is_str {
obj["dst_type"] =
json!({"kind":"handle","box_type":"StringBox"});
}