diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md
index 11068df3..e8ecb84b 100644
--- a/docs/development/current/main/10-Now.md
+++ b/docs/development/current/main/10-Now.md
@@ -3,7 +3,7 @@
## Current Focus
- Phase: `docs/development/current/main/phases/phase-29ao/README.md`
-- Next: TBD (see `docs/development/current/main/phases/phase-29ao/README.md`)
+- Next: TBD (P45 done; see `docs/development/current/main/phases/phase-29ao/README.md`)
## Gate (SSOT)
diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md
index 014cff7a..51831a33 100644
--- a/docs/development/current/main/30-Backlog.md
+++ b/docs/development/current/main/30-Backlog.md
@@ -5,7 +5,7 @@ Scope: 「次にやる候補」を短く列挙するメモ。入口は `docs/dev
## Active
-- CorePlan migration: `docs/development/current/main/phases/phase-29ao/README.md`(Next: TBD)
+- CorePlan migration: `docs/development/current/main/phases/phase-29ao/README.md`(Next: TBD, P45 done)
## Near-Term Candidates
diff --git a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md
index 20c4c6cb..de94d39f 100644
--- a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md
+++ b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md
@@ -34,7 +34,7 @@ Related:
## 1.1 Current (active)
- Active phase: `docs/development/current/main/phases/phase-29ao/README.md`
-- Next step: TBD
+- Next step: TBD (P45 done)
## 2. すでに固めた SSOT(再発防止の土台)
diff --git a/docs/development/current/main/phases/phase-29ao/README.md b/docs/development/current/main/phases/phase-29ao/README.md
index c6fdf963..5e4c5472 100644
--- a/docs/development/current/main/phases/phase-29ao/README.md
+++ b/docs/development/current/main/phases/phase-29ao/README.md
@@ -246,6 +246,11 @@ Gate(SSOT):
- 指示書: `docs/development/current/main/phases/phase-29ao/P44-CORELOOPCOMPOSER-V0-PATTERN1-MINIMAL-COMPOSITION-INSTRUCTIONS.md`
- ねらい: Pattern1 skeleton の最小合成を v0 で開始し、Facts→CorePlan の責務を composer 側へ寄せる(仕様不変)
+## P45: CoreLoopComposer v0 — Pattern6 (ScanWithInit) minimal composition ✅
+
+- 指示書: `docs/development/current/main/phases/phase-29ao/P45-CORELOOPCOMPOSER-V0-PATTERN6-SCANWITHINIT-INSTRUCTIONS.md`
+- ねらい: Pattern6 planner subset の最小合成を v0 で開始し、composer に合成のSSOTを寄せる(仕様不変)
+
## Next(planned)
- Next: TBD
diff --git a/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs b/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs
index 361761d2..e0e53c57 100644
--- a/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs
+++ b/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs
@@ -1,14 +1,21 @@
//! Phase 29ao P43: CoreLoopComposer v0 scaffold (unconnected).
+use crate::ast::{ASTNode, Span};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
+use crate::mir::builder::control_flow::plan::facts::feature_facts::ExitKindFacts;
+use crate::mir::builder::control_flow::plan::facts::scan_shapes::{
+ ConditionShape, StepShape,
+};
use crate::mir::builder::control_flow::plan::facts::skeleton_facts::SkeletonKind;
use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts;
-use crate::mir::builder::control_flow::plan::normalizer::build_pattern1_coreloop;
-use crate::mir::builder::control_flow::plan::CorePlan;
+use crate::mir::builder::control_flow::plan::normalizer::{
+ build_pattern1_coreloop, PlanNormalizer,
+};
+use crate::mir::builder::control_flow::plan::{CorePlan, ScanDirection, ScanWithInitPlan};
use crate::mir::builder::MirBuilder;
#[allow(dead_code)]
-pub(in crate::mir::builder) fn try_compose_core_loop_v0(
+pub(in crate::mir::builder) fn try_compose_core_loop_v0_scan_with_init(
builder: &mut MirBuilder,
facts: &CanonicalLoopFacts,
ctx: &LoopPatternContext,
@@ -19,10 +26,80 @@ pub(in crate::mir::builder) fn try_compose_core_loop_v0(
if facts.value_join_needed {
return Ok(None);
}
+ if !facts.cleanup_kinds_present.is_empty() {
+ return Ok(None);
+ }
- let Some(pattern1) = facts.facts.pattern1_simplewhile.as_ref() else {
+ let Some(scan) = facts.facts.scan_with_init.as_ref() else {
return Ok(None);
};
+
+ if !facts.exit_kinds_present.is_empty()
+ && !(facts.exit_kinds_present.len() == 1
+ && facts.exit_kinds_present.contains(&ExitKindFacts::Return))
+ {
+ return Ok(None);
+ }
+
+ let scan_direction = match scan.step_lit {
+ 1 => ScanDirection::Forward,
+ -1 => ScanDirection::Reverse,
+ _ => return Ok(None),
+ };
+
+ let shapes_match = match (
+ &facts.facts.condition_shape,
+ &facts.facts.step_shape,
+ ) {
+ (
+ ConditionShape::VarLessLength {
+ idx_var,
+ haystack_var,
+ ..
+ },
+ StepShape::AssignAddConst { var, k },
+ ) => idx_var == &scan.loop_var
+ && haystack_var == &scan.haystack
+ && var == &scan.loop_var
+ && *k == scan.step_lit,
+ _ => false,
+ };
+ if !shapes_match {
+ return Ok(None);
+ }
+
+ let plan = ScanWithInitPlan {
+ loop_var: scan.loop_var.clone(),
+ haystack: scan.haystack.clone(),
+ needle: scan.needle.clone(),
+ step_lit: scan.step_lit,
+ early_return_expr: ASTNode::Variable {
+ name: scan.loop_var.clone(),
+ span: Span::unknown(),
+ },
+ not_found_return_lit: -1,
+ scan_direction,
+ dynamic_needle: false,
+ };
+ let core = PlanNormalizer::normalize_scan_with_init(builder, plan, ctx)?;
+ Ok(Some(core))
+}
+
+#[allow(dead_code)]
+pub(in crate::mir::builder) fn try_compose_core_loop_v0(
+ builder: &mut MirBuilder,
+ facts: &CanonicalLoopFacts,
+ ctx: &LoopPatternContext,
+) -> Result