diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md
index ddd45bfc..178986e0 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 (P46 done; see `docs/development/current/main/phases/phase-29ao/README.md`)
+- Next: TBD (P47 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 87ee7bc0..c5b39a8b 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, P46 done)
+- CorePlan migration: `docs/development/current/main/phases/phase-29ao/README.md`(Next: TBD, P47 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 a4abfa33..c0f2598c 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 (P46 done)
+- Next step: TBD (P47 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 33ec96d5..19ee8bb9 100644
--- a/docs/development/current/main/phases/phase-29ao/README.md
+++ b/docs/development/current/main/phases/phase-29ao/README.md
@@ -256,6 +256,11 @@ Gate(SSOT):
- 指示書: `docs/development/current/main/phases/phase-29ao/P46-CORELOOPCOMPOSER-V0-PATTERN7-SPLITSCAN-INSTRUCTIONS.md`
- ねらい: Pattern7 planner subset の最小合成を v0 で開始し、composer に合成のSSOTを寄せる(仕様不変)
+## P47: CoreLoopComposer v1 — Pattern7 value-join minimal composition ✅
+
+- 指示書: `docs/development/current/main/phases/phase-29ao/P47-CORELOOPCOMPOSER-V1-SPLITSCAN-VALUEJOIN-INSTRUCTIONS.md`
+- ねらい: Pattern7 value-join を v1 で受理し、block_params/EdgeArgs 経由の PHI 一本化を維持(仕様不変)
+
## Next(planned)
- Next: TBD
diff --git a/src/mir/builder/control_flow/plan/composer/coreloop_gates.rs b/src/mir/builder/control_flow/plan/composer/coreloop_gates.rs
new file mode 100644
index 00000000..726d958b
--- /dev/null
+++ b/src/mir/builder/control_flow/plan/composer/coreloop_gates.rs
@@ -0,0 +1,18 @@
+use crate::mir::builder::control_flow::plan::facts::feature_facts::ExitKindFacts;
+use crate::mir::builder::control_flow::plan::facts::skeleton_facts::SkeletonKind;
+use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts;
+
+pub(super) fn coreloop_base_gate(facts: &CanonicalLoopFacts) -> bool {
+ matches!(facts.skeleton_kind, SkeletonKind::Loop)
+ && facts.cleanup_kinds_present.is_empty()
+}
+
+pub(super) fn exit_kinds_allow_return_only(facts: &CanonicalLoopFacts) -> bool {
+ facts.exit_kinds_present.is_empty()
+ || (facts.exit_kinds_present.len() == 1
+ && facts.exit_kinds_present.contains(&ExitKindFacts::Return))
+}
+
+pub(super) fn exit_kinds_empty(facts: &CanonicalLoopFacts) -> bool {
+ facts.exit_kinds_present.is_empty()
+}
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 8b0369f7..14a225c4 100644
--- a/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs
+++ b/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs
@@ -2,11 +2,12 @@
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, SplitScanShape, StepShape,
};
-use crate::mir::builder::control_flow::plan::facts::skeleton_facts::SkeletonKind;
+use crate::mir::builder::control_flow::plan::composer::coreloop_gates::{
+ coreloop_base_gate, exit_kinds_allow_return_only, exit_kinds_empty,
+};
use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts;
use crate::mir::builder::control_flow::plan::normalizer::{
build_pattern1_coreloop, PlanNormalizer,
@@ -16,29 +17,16 @@ use crate::mir::builder::control_flow::plan::{
};
use crate::mir::builder::MirBuilder;
-fn coreloop_v0_base_gate(facts: &CanonicalLoopFacts) -> bool {
- matches!(facts.skeleton_kind, SkeletonKind::Loop)
- && !facts.value_join_needed
- && facts.cleanup_kinds_present.is_empty()
-}
-
-fn exit_kinds_allow_return_only(facts: &CanonicalLoopFacts) -> bool {
- facts.exit_kinds_present.is_empty()
- || (facts.exit_kinds_present.len() == 1
- && facts.exit_kinds_present.contains(&ExitKindFacts::Return))
-}
-
-fn exit_kinds_empty(facts: &CanonicalLoopFacts) -> bool {
- facts.exit_kinds_present.is_empty()
-}
-
#[allow(dead_code)]
pub(in crate::mir::builder) fn try_compose_core_loop_v0_scan_with_init(
builder: &mut MirBuilder,
facts: &CanonicalLoopFacts,
ctx: &LoopPatternContext,
) -> Result