phase29ai(p8): try planner before legacy pattern6

This commit is contained in:
2025-12-29 08:55:17 +09:00
parent a2ec7bb480
commit 82b0a87599
5 changed files with 138 additions and 8 deletions

View File

@ -2,7 +2,12 @@
## Current Focus: Phase 29aiPlan/Frag single-planner
Next: `docs/development/current/main/phases/phase-29ai/P8-WIRE-PLANNER-INTO-SINGLE_PLANNER-PATTERN6-INSTRUCTIONS.md`
Next: `docs/development/current/main/phases/phase-29ai/P9-PLANNER-PATTERN7-SPLITSCAN-WIRE-INSTRUCTIONS.md`
**2025-12-29: Phase 29ai P8 完了**
- 目的: Facts→Planner を実行経路へ 1 歩だけ接続し、Pattern6scan-with-initの subset から吸収を開始(仕様不変)
- 実装: `src/mir/builder/control_flow/plan/single_planner/rules.rs`Pattern6 rule の先頭で planner を試す)
- 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` PASS
**2025-12-29: Phase 29ag P1 完了**
- 目的: coordinator の ValueId(idx) 前提を撤去し、boundary.join_inputs を SSOT 化(仕様不変)

View File

@ -19,7 +19,7 @@ Related:
- **Phase 29aicandidate: Plan/Frag single-plannerFacts SSOT**
- 入口: `docs/development/current/main/phases/phase-29ai/README.md`
- Next: P8Wire planner into single_planner: Pattern6 subset
- Next: P9Planner: Pattern7 split-scan subset
- **Phase 29ae P1✅ COMPLETE: JoinIR Regression Pack (SSOT固定)**
- 入口: `docs/development/current/main/phases/phase-29ae/README.md`

View File

@ -0,0 +1,112 @@
# Phase 29ai P9: Planner support + wiringPattern7 split-scan subset
Date: 2025-12-29
Status: Ready for execution
Scope: Facts→Planner→DomainPlan を Pattern7SplitScanへ拡張し、single_planner で planner-first を開始する
Goal: 「pattern名で入口分岐」ではなく「Facts→Plan」の 1 本導線に寄せつつ、既定挙動は不変のまま前進する
## Objective
Pattern7split-scanの最小ケースについて、Facts が `Ok(Some(...))` を返し、planner が `Ok(Some(DomainPlan::SplitScan))`
まで到達できるようにする。そのうえで `single_planner` の Pattern7 rule でも planner-first を開始し、legacy へは `Ok(None)`
時のみフォールバックする。
## Non-goalsこの P9 ではやらない)
- 仕様変更(挙動変更・ログ増加・エラーメッセージ変更)
- Pattern2 などの大物の移植Phase 29ai P10+ へ)
- Freeze を「実行経路で出す」こと(この P9 は `Ok(None)` 保守で進める)
- plan/extractors の削除legacy の受け口は残す)
## Target FixtureSSOT
以下の既存 fixture/smoke を “SplitScan subset” の SSOT として使う:
- Fixture: `apps/tests/phase29ab_pattern7_splitscan_ok_min.hako`
- Smoke: `tools/smokes/v2/profiles/integration/apps/phase29ab_pattern7_splitscan_ok_min_vm.sh`
- Regression pack: `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh`
## Implementation StepsCritical Order
### Step 1: Facts の最小対応SplitScanFacts
ファイル:
- `src/mir/builder/control_flow/plan/facts/loop_facts.rs`
- `src/mir/builder/control_flow/plan/facts/scan_shapes.rs`(必要なら)
やること:
- `LoopFacts``split_scan: Option<SplitScanFacts>` を追加。
- `try_build_loop_facts()` は Pattern6 と同様に “保守的に Some を返す” 方針で、SplitScan 最小形だけ `Some` を返す。
- 最小形の抽出は **AST の変数名が確定できる場合のみ**(推測や固定名は禁止)。
最低限抽出したい変数(`SplitScanPlan` に必要):
- `s_var`, `sep_var`, `result_var`, `i_var`, `start_var`
注意:
- 抽出が不完全なら `Ok(None)`Freeze は出さない)。
- 既存 Pattern7 extractor`plan/extractors/pattern7_split_scan.rs`)の形と齟齬が出そうなら、まずはその extractor が要求する
最小セットに合わせる(この P9 では新たな shape 語彙を増やしすぎない)。
### Step 2: Planner の候補生成DomainPlan::SplitScan
ファイル:
- `src/mir/builder/control_flow/plan/planner/build.rs`
やること:
- `facts.facts.split_scan``Some` のときに `DomainPlan::SplitScan(SplitScanPlan { ... })` を CandidateSet に push する。
- 0/1/2+ の境界は CandidateSet の finalize に委譲し、planner 側は “候補を積むだけ” に徹する。
注意:
- P9 時点では `SplitScanFacts` は “最小 OK fixture のみ Some” に抑えて、誤マッチで 2+ にならないようにする。
### Step 3: unit test で Facts/Planner の境界を固定
ファイル:
- `src/mir/builder/control_flow/plan/facts/loop_facts.rs``#[cfg(test)]`
- (必要なら)`src/mir/builder/control_flow/plan/planner/build.rs`unit test
やること:
- Facts: “OK minimal っぽい AST” で `Ok(Some(...))` を返すテスト。
- Facts: “明らかに違う AST” で `Ok(None)` を返すテスト。
- Planner: Facts→Planner で `Ok(Some(DomainPlan::SplitScan))` になるテスト(直で `build_plan_from_facts` を呼ぶ形式で OK
### Step 4: single_planner の Pattern7 も planner-first に
ファイル:
- `src/mir/builder/control_flow/plan/single_planner/rules.rs`
やること:
- Pattern6 と同様に、Pattern7 rule の先頭で `planner::build_plan(ctx.condition, ctx.body)` を試す。
- `Ok(Some(plan))` なら採用。
- `Ok(None)` なら `legacy_rules::pattern7::extract(ctx)` にフォールバック。
- planner の `Ok(None)` では新規ログは出さない(観測差分を抑える)。
### Step 5: Docs / Tracking 更新SSOT
ファイル:
- `docs/development/current/main/phases/phase-29ai/README.md`
- `docs/development/current/main/10-Now.md`
- `docs/development/current/main/30-Backlog.md`
やること:
- P9 完了の記録(目的/影響/検証コマンド)。
- Next を P10例: Pattern2 extractor → Facts/Planner への段階移植)に向けて 1 行だけ書く。
## Verification ChecklistAcceptance
- `cargo build --release`
- `./tools/smokes/v2/run.sh --profile quick`
- `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh`
期待:
- 0 errors
- quick 154/154 PASS
- regression pack PASS
- 既定挙動は不変planner が `Ok(None)` を返す領域は legacy 経路へ落ちる)
## Risk Notes
- **誤マッチ**: Facts は “最小形だけ Some” を徹底し、疑わしい場合は `Ok(None)` に倒す。
- **観測差分**: planner `Ok(None)` でログを出さない。採用時も既存の pattern 名ログを維持する。
- **Freeze**: P9 は Freeze を実行経路に出さない。Freeze taxonomy の投入は P10+ で段階的に行う。

View File

@ -49,6 +49,11 @@ Goal: pattern 名による分岐を外部APIから消し、Facts事実
- 指示書: `docs/development/current/main/phases/phase-29ai/P8-WIRE-PLANNER-INTO-SINGLE_PLANNER-PATTERN6-INSTRUCTIONS.md`
- ねらい: Facts→Planner を実行経路へ1歩だけ接続し、Pattern6最小ケースから吸収を開始仕様不変
## P9: Planner support + wiringPattern7 split-scan subset
- 指示書: `docs/development/current/main/phases/phase-29ai/P9-PLANNER-PATTERN7-SPLITSCAN-WIRE-INSTRUCTIONS.md`
- ねらい: Pattern7split-scanの最小ケースを Facts→Planner で `Ok(Some(DomainPlan::SplitScan))` まで到達させ、single_planner で planner-first を開始(仕様不変を維持しつつ段階吸収)
## Verification (SSOT)
- `cargo build --release`

View File

@ -7,6 +7,7 @@ use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternCont
use crate::mir::loop_pattern_detection::LoopPatternKind;
use super::legacy_rules;
use crate::mir::builder::control_flow::plan::planner;
use crate::mir::builder::control_flow::plan::DomainPlan;
pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result<Option<DomainPlan>, String> {
@ -62,11 +63,18 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result<Option<D
continue;
}
let plan_opt = match entry.kind {
RuleKind::Simple(extract) => extract(ctx),
RuleKind::Pattern6 => legacy_rules::pattern6::extract(ctx),
RuleKind::Pattern7 => legacy_rules::pattern7::extract(ctx),
}?;
let (plan_opt, log_none) = match entry.kind {
RuleKind::Simple(extract) => (extract(ctx)?, true),
RuleKind::Pattern6 => {
let from_planner = planner::build_plan(ctx.condition, ctx.body)
.map_err(|freeze| freeze.to_string())?;
match from_planner {
Some(plan) => (Some(plan), false),
None => (legacy_rules::pattern6::extract(ctx)?, true),
}
}
RuleKind::Pattern7 => (legacy_rules::pattern7::extract(ctx)?, true),
};
if let Some(domain_plan) = plan_opt {
// Phase 286 P3: Pattern8 static box filtering
@ -87,7 +95,7 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result<Option<D
let log_msg = format!("route=plan strategy=extract pattern={}", entry.name);
trace::trace().pattern("route", &log_msg, true);
return Ok(Some(domain_plan));
} else if ctx.debug {
} else if log_none && ctx.debug {
let debug_msg =
format!("{} extraction returned None, trying next pattern", entry.name);
trace::trace().debug("route", &debug_msg);