diff --git a/apps/tests/phase29aq_string_index_of_min.hako b/apps/tests/phase29aq_string_index_of_min.hako
new file mode 100644
index 00000000..6ee6dc0a
--- /dev/null
+++ b/apps/tests/phase29aq_string_index_of_min.hako
@@ -0,0 +1,3 @@
+using "apps/lib/json_native/utils/string.hako" as StringUtils
+
+print(StringUtils.index_of("hello", "l"))
diff --git a/apps/tests/phase29aq_string_last_index_of_min.hako b/apps/tests/phase29aq_string_last_index_of_min.hako
new file mode 100644
index 00000000..18c63c0d
--- /dev/null
+++ b/apps/tests/phase29aq_string_last_index_of_min.hako
@@ -0,0 +1,3 @@
+using "apps/lib/json_native/utils/string.hako" as StringUtils
+
+print(StringUtils.last_index_of("hello", "l"))
diff --git a/apps/tests/phase29aq_string_parse_integer_min.hako b/apps/tests/phase29aq_string_parse_integer_min.hako
new file mode 100644
index 00000000..c7f62803
--- /dev/null
+++ b/apps/tests/phase29aq_string_parse_integer_min.hako
@@ -0,0 +1,3 @@
+using "apps/lib/json_native/utils/string.hako" as StringUtils
+
+print(StringUtils.parse_integer("12345"))
diff --git a/apps/tests/phase29aq_string_split_min.hako b/apps/tests/phase29aq_string_split_min.hako
new file mode 100644
index 00000000..1e0d8f8e
--- /dev/null
+++ b/apps/tests/phase29aq_string_split_min.hako
@@ -0,0 +1,9 @@
+using "apps/lib/json_native/utils/string.hako" as StringUtils
+
+static box Main {
+ main(args) {
+ local result = StringUtils.split("a,b,c", ",")
+ print(result.length())
+ return 0
+ }
+}
diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md
index 1c8fd42b..0f2bbf1e 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-29aq/README.md`
-- Next: Phase 29aq P1 (stdlib subset additions)
+- Next: Phase 29aq P2 (stdlib scan subset extensions)
## Gate (SSOT)
diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md
index 1a4cae7f..5ffa5bd3 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
-- Phase 29aq: `docs/development/current/main/phases/phase-29aq/README.md` (Next: P1 subsets)
+- Phase 29aq: `docs/development/current/main/phases/phase-29aq/README.md` (Next: P2 scan subsets)
- JoinIR regression gate SSOT: `docs/development/current/main/phases/phase-29ae/README.md`
- CorePlan hardening (docs-first): `docs/development/current/main/phases/phase-29al/README.md`
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 1b27a8cf..a92d8ea7 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-29aq/README.md`
-- Next step: Phase 29aq P1 (stdlib subset additions)
+- Next step: Phase 29aq P2 (stdlib scan subset extensions)
## 2. すでに固めた SSOT(再発防止の土台)
diff --git a/docs/development/current/main/phases/phase-29ae/README.md b/docs/development/current/main/phases/phase-29ae/README.md
index bb5bfb56..dd5ec09b 100644
--- a/docs/development/current/main/phases/phase-29ae/README.md
+++ b/docs/development/current/main/phases/phase-29ae/README.md
@@ -15,6 +15,10 @@ Goal: JoinIR の最小回帰セットを SSOT として固定する。
- Pattern1 (subset reject, VM): `phase29ao_pattern1_subset_reject_extra_stmt_vm`
- Pattern1 (stdlib to_lower, VM): `phase29ap_stringutils_tolower_vm`
- Pattern1 (stdlib join, VM): `phase29ap_stringutils_join_vm`
+- ScanWithInit (stdlib index_of, VM): `phase29aq_string_index_of_min_vm`
+- ScanWithInit (stdlib last_index_of, VM): `phase29aq_string_last_index_of_min_vm`
+- Pattern2 (stdlib parse_integer, VM): `phase29aq_string_parse_integer_min_vm`
+- SplitScan (stdlib split, VM): `phase29aq_string_split_min_vm`
- Pattern5 (Break, VM): `phase286_pattern5_break_vm`
- Pattern5 (strict shadow, VM): `phase29ao_pattern5_strict_shadow_vm`
- Pattern5 (release adopt, VM): `phase29ao_pattern5_release_adopt_vm`
diff --git a/docs/development/current/main/phases/phase-29aq/P1-INDEXOF-PARSEINT-SPLIT-SUBSET-INSTRUCTIONS.md b/docs/development/current/main/phases/phase-29aq/P1-INDEXOF-PARSEINT-SPLIT-SUBSET-INSTRUCTIONS.md
index b87f0d74..b4ff3703 100644
--- a/docs/development/current/main/phases/phase-29aq/P1-INDEXOF-PARSEINT-SPLIT-SUBSET-INSTRUCTIONS.md
+++ b/docs/development/current/main/phases/phase-29aq/P1-INDEXOF-PARSEINT-SPLIT-SUBSET-INSTRUCTIONS.md
@@ -1,5 +1,5 @@
---
-Status: Planned
+Status: Done
Scope: stdlib subsets (index_of/last_index_of, parse_integer, split)
Related:
- docs/development/current/main/phases/phase-29aq/README.md
diff --git a/docs/development/current/main/phases/phase-29aq/README.md b/docs/development/current/main/phases/phase-29aq/README.md
index 0b1ee35a..36b20730 100644
--- a/docs/development/current/main/phases/phase-29aq/README.md
+++ b/docs/development/current/main/phases/phase-29aq/README.md
@@ -54,4 +54,10 @@ Plan/Composer subsets (or mark unsupported) before adding new subsets.
## Next (planned)
+## Progress
+
- P1: Add stdlib subsets in priority order (index_of/last_index_of → parse_integer → split).
+
+## Next (planned)
+
+- P2: Extend stdlib scan subsets (candidate: index_of_string, to_upper).
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 fa0f4ee1..d50906b2 100644
--- a/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs
+++ b/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs
@@ -59,6 +59,10 @@ pub(super) fn try_compose_core_loop_v0_scan_with_init(
&& haystack_var == &scan.haystack
&& var == &scan.loop_var
&& *k == scan.step_lit,
+ (
+ ConditionShape::VarGreaterEqualZero { idx_var },
+ StepShape::AssignAddConst { var, k },
+ ) => idx_var == &scan.loop_var && var == &scan.loop_var && *k == scan.step_lit,
_ => false,
};
if !shapes_match {
diff --git a/src/mir/builder/control_flow/plan/facts/loop_facts.rs b/src/mir/builder/control_flow/plan/facts/loop_facts.rs
index 6d30f1a3..a3186842 100644
--- a/src/mir/builder/control_flow/plan/facts/loop_facts.rs
+++ b/src/mir/builder/control_flow/plan/facts/loop_facts.rs
@@ -207,7 +207,7 @@ fn try_build_loop_facts_inner(
fn try_extract_condition_shape(condition: &ASTNode) -> Result