Status: Completed (2025-12-20)
Scope: `StringUtils.index_of_string/2`（dynamic window scan）を JoinIR で受理して `--profile quick` を進める。
Related:
- Now: `docs/development/current/main/10-Now.md`
- Phase 257: `docs/development/current/main/phases/phase-257/README.md`
- Phase 259 (Next): `docs/development/current/main/phases/phase-259/README.md`
- Design goal: `docs/development/current/main/design/join-explicit-cfg-construction.md`

# Phase 258: `StringUtils.index_of_string/2` (dynamic window scan)

## Current Status (SSOT)

- ✅ **P0 完了**（2025-12-21）
- Former first FAIL: `json_lint_vm / StringUtils.index_of_string/2`
- **New first FAIL**: `json_lint_vm / StringUtils.is_integer/1`（Phase 259へ移行）
- Shape summary (from quick log):
  - prelude: `if substr.length()==0 return 0`, `if substr.length()>s.length() return -1`
  - loop: `loop(i <= s.length() - substr.length()) { if s.substring(i, i+substr.length()) == substr return i; i=i+1 }`
  - post: `return -1`
  - caps: `If,Loop,Return`

## Goal

- ✅ `./tools/smokes/v2/run.sh --profile quick` が `StringUtils.index_of_string/2` を突破し、次の FAIL へ進む

## Proposed Approach (P0)

方針: Pattern6（ScanWithInit）を “needle length 付き scan” に最小拡張する（構造で解決）

Pattern6 との差分だけを足す:
- loop cond: `i <= s.length() - substr.length()`（bound 付き）
- window: `substring(i, i + substr.length())`
- needle: `substr`（String）

JoinIR 側は P0 では “毎回 length() を呼んでよい”。まず correctness を固定:
- `needle_len = substr.length()`
- `bound = s.length() - needle_len`
- stop: `i > bound` で k_exit（not-found return）
- match: `s.substring(i, i + needle_len) == substr` で return i
- step: `i = i + 1`

## Tasks (Draft)

1) Fixture + integration smokes
2) Pattern6 extractor を拡張して `index_of_string/2` の loop 形を accept（Fail-Fast）
3) Pattern6 lowerer を拡張（dynamic window length / bound）して JoinIR を生成
4) `--verify` / integration / quick を回して SSOT 更新
