// Stage-B compiler entry — ParserBox → FlowEntry emit-only
// Notes (Dev/Doc):
// - Scope: Hako だけで Program(JSON v0) を生成する自己ホスト入口。本文抽出→コメント除去→正規化→parse_program2 で一行JSONを出力。
// - Robustness: 本文抽出は "method main" なしでも `box Main { main(...) { ... } }` からフォールバック抽出する。
//               文字列を正しくスキップ（エスケープ対応）しつつ () と {} の対応を検出。// と /* */ のコメントは除去。
//               前後の空白/改行もトリムして JSON 揺れを低減する。
// - Known limits: VM の include は未対応。MirBuilder を VM 直で実行する経路は既定OFF（opt‑in検証のみ）。
// - Policy: Program(JSON) → MIR(JSON) は Rust CLI 変換（Gate‑C）を既定とし、Hako ビルダーは opt‑in で段階導入する。
// - Toggles around: HAKO_MIR_BUILDER_{INTERNAL,REGISTRY,DELEGATE}（MirBuilder側）。ここでは JSON 生成に専念する。
// - Recommended: Dev/CI は wrapper（tools/hakorune_emit_mir.sh）経由で Program→MIR を行い、失敗時は Gate‑C に自動委譲する。

// Note: sh_core must be in [modules] for parser dependencies to resolve
using hako.compiler.entry.bundle_resolver as BundleResolver
// ParserBox module: use `parser.parser_box` to avoid `box` keyword in path
using lang.compiler.parser.parser_box as ParserBox
using lang.compiler.entry.func_scanner as FuncScannerBox
using lang.compiler.entry.using_resolver as Stage1UsingResolverBox
using lang.compiler.builder.mod as CompilerBuilder

// Dev-only trace helper (Phase 25.1c)
// - Enabled when HAKO_STAGEB_TRACE=1
// - Keeps Stage-B behavior unchanged（ログのみ追加）
box StageBTraceBox {
  log(label) {
    local flag = env.get("HAKO_STAGEB_TRACE")
    if flag == null { return 0 }
    if ("" + flag) != "1" { return 0 }
    // label が null/Void でも落ちないように守る（dev専用）
    local msg = "[stageb/trace]"
    if label != null {
      msg = msg + " " + ("" + label)
    }
    print(msg)
    return 0
  }
}

// Phase 152-B: Main Detection Helper (箱化モジュール化)
// - Supports both legacy and modern entry point patterns
// - Clear modular separation for pattern detection
static box MainDetectionHelper {
  /// Find main() body from source code
  /// Supports both legacy and modern patterns
  findMainBody(src) {
    if src == null { return "" }

    // Pattern 1: Legacy - static method main (deprecated)
    local result = me.tryLegacyPattern(src)
    if result != "" { return result }

    // Pattern 2: Modern - static box Main { main() { } }
    result = me.tryModernPattern(src)
    if result != "" { return result }

    // Fallback: not found
    return ""
  }

  /// Try legacy pattern: "static method main" or "method main"
  tryLegacyPattern(src) {
    // First try "static method main"
    local p = me.findPattern(src, "static method main")
    if p >= 0 {
      return me.extractBodyFromPosition(src, p + 19) // Skip "static method main"
    }

    // Fallback to just "method main"
    p = me.findPattern(src, "method main")
    if p >= 0 {
      return me.extractBodyFromPosition(src, p + 11) // Skip "method main"
    }

    return ""
  }

  /// Try modern pattern: "static box Main { main(" or "box Main { main("
  tryModernPattern(src) {
    // Try "static box Main" first
    local p = me.findPattern(src, "static box Main")
    if p < 0 {
      // Fallback: "box Main"
      p = me.findPattern(src, "box Main")
    }

    if p >= 0 {
      // Find "main(" after Main
      local start = me.findPattern(src, "main(", p)
      if start >= 0 {
        return me.extractBodyFromPosition(src, start)
      }
    }

    return ""
  }

  /// Find pattern in source starting from offset
  findPattern(src, pat, offset) {
    if src == null { return -1 }
    local start = 0
    if offset != null { start = offset }

    local s = "" + src
    local n = s.length()
    local m = pat.length()
    local i = start
    loop(i + m <= n) {
      if s.substring(i, i + m) == pat { return i }
      i = i + 1
    }
    return -1
  }

  /// Extract method body: from position find matching braces
  /// Returns complete { ... } block content (inside braces)
  extractBodyFromPosition(src, pos) {
    // Find opening brace {
    local bracePos = me.findPattern(src, "{", pos)
    if bracePos < 0 { return "" }

    // Find matching closing brace }
    local depth = 0
    local i = bracePos
    local n = ("" + src).length()
    loop(i < n) {
      local ch = ("" + src).substring(i, i + 1)
      if ch == "{" {
        depth = depth + 1
      } else if ch == "}" {
        depth = depth - 1
        if depth == 0 {
          // Return body content (inside braces)
          return ("" + src).substring(bracePos + 1, i)
        }
      }
      i = i + 1
    }

    return ""
  }
}

// Phase 25.1c: CLI argument → source resolution
static box StageBArgsBox {
  resolve_src(args) {
    {
      local trace_flag = env.get("HAKO_STAGEB_TRACE")
      if trace_flag != null && ("" + trace_flag) == "1" {
        print("[stageb/direct] StageBArgsBox.resolve_src ENTERED")
      }
    }
    local tracer = new StageBTraceBox()
    tracer.log("StageBArgsBox.resolve_src:enter")
    // 1) Collect source from args or env
    local src = null
    local src_file = null
    if args != null {
      local i = 0
      local n = args.length()
      loop(i < n) {
        local t = "" + args.get(i)
        if t == "--source" && i + 1 < n { src = "" + args.get(i + 1)  break }
        if t == "--source-file" && i + 1 < n { src_file = "" + args.get(i + 1)  i = i + 1 }
        i = i + 1
      }
    }
    // Prefer explicit source string when provided
    // If --source-file is used, prefer env-provided content (wrapper supplies HAKO_SOURCE_FILE_CONTENT)
    if src == null && src_file != null {
      local inline = env.get("HAKO_SOURCE_FILE_CONTENT")
      if inline != null { src = "" + inline }
    }
    // Phase 73: Fallback to HAKO_SRC env variable (unified I/O path)
    // This eliminates the need for large argv strings (solves "Argument list too long")
    if src == null {
      src = env.get("HAKO_SRC")
    }
    if src == null { src = "return 0" }

    {
      // Trace final source length（dev専用）
      local l = 0
      if src != null { l = ("" + src).length() }
      local tracer = new StageBTraceBox()
      tracer.log("StageBArgsBox.resolve_src:return_len=" + ("" + l))
    }

    return src
  }
}

// Phase 25.1c: Body extraction + bundle + using + trim
static box StageBBodyExtractorBox {
  build_body_src(src, args) {
    {
      //入口トレース: 入力ソース長と引数有無
      local l = 0
      if src != null { l = ("" + src).length() }
      local tracer = new StageBTraceBox()
      tracer.log("StageBBodyExtractorBox.build_body_src:enter len=" + ("" + l))
    }
    // ============================================================================
    // Depth guard: prevent accidental recursion inside Stage‑B body extractor
    // ============================================================================
    {
      local depth = env.get("HAKO_STAGEB_BODY_DEPTH")
      if depth != null && ("" + depth) != "0" {
        print("[stageb/recursion] StageBBodyExtractorBox.build_body_src recursion detected")
        // Fail-fast but still return a sensible value: fall back to original src
        return src
      }
      env.set("HAKO_STAGEB_BODY_DEPTH", "1")
    }

    // ============================================================================
    // 4) Body extraction: extract main method body from box Main wrapper
    // ============================================================================
    // If wrapped in `box Main { method main() { ... } }` or `static box Main { method main() { ... } }`,
    // extract the method body text. Allow disabling via env HAKO_STAGEB_BODY_EXTRACT=0.

    local body_src = null
    {
      local do_extract = 1
      {
        local ex = env.get("HAKO_STAGEB_BODY_EXTRACT")
        if ex != null && ("" + ex) == "0" { do_extract = 0 }
      }

      if do_extract == 0 {
        body_src = src
      } else {
        // naive search for "method main" → '(' → ')' → '{' ... balanced '}'
        local s = src

        // naive substring search for "method main"; fallback to "main(" inside box Main
        local k0 = -1
        {
          local pat = "method main"
          local m = pat.length()
          local i = 0
          local n = s.length()
          loop(i + m <= n) {
            if s.substring(i, i + m) == pat { k0 = i  break }
            i = i + 1
          }
        }

        if k0 < 0 {
          // Fallback: find "box Main" (with or without leading 'static') then locate "main(" after it
          local kbox = -1
          {
            local pat = "box Main"
            local m = pat.length()
            local i = 0
            local n = s.length()
            loop(i + m <= n) {
              if s.substring(i, i + m) == pat { kbox = i  break }
              i = i + 1
            }
          }

          if kbox >= 0 {
            // search for "main(" starting at kbox
            local i = kbox
            local n = s.length()
            loop(i + 5 <= n) { // len("main(") = 5
              if s.substring(i, i + 5) == "main(" { k0 = i  break }
              i = i + 1
            }
          } else {
            // last resort: global search of "main(" (may overmatch but better than full-file body)
            local i = 0
            local n = s.length()
            loop(i + 5 <= n) {
              if s.substring(i, i + 5) == "main(" { k0 = i  break }
              i = i + 1
            }
          }
        }

        {
          local dbg = env.get("HAKO_STAGEB_DEBUG")
          if dbg != null && ("" + dbg) == "1" {
            print("[DEBUG] Source length: " + s.length())
            print("[DEBUG] k0 (main position) = " + k0)
          }
        }

        if k0 >= 0 {
          // find '(' after k0 (skip inside strings)
          local k1 = -1
          {
            local j = k0
            local n = s.length()
            local in_str = 0
            local esc = 0
            loop(j < n) {
              local ch = s.substring(j, j + 1)
              if in_str == 1 {
                if esc == 1 { esc = 0  j = j + 1  continue }
                if ch == "\\" { esc = 1  j = j + 1  continue }
                if ch == "\"" { in_str = 0  j = j + 1  continue }
                j = j + 1
                continue
              }
              if ch == "\"" { in_str = 1  j = j + 1  continue }
              if ch == "(" { k1 = j  break }
              j = j + 1
            }
          }

          {
            local dbg = env.get("HAKO_STAGEB_DEBUG")
            if dbg != null && ("" + dbg) == "1" {
              print("[DEBUG] k1 ('(' position) = " + k1)
            }
          }

          if k1 >= 0 {
            // find ')' after k1 (skip inside strings)
            local k2 = -1
            {
              local j = k1
              local n = s.length()
              local in_str = 0
              local esc = 0
              loop(j < n) {
                local ch = s.substring(j, j + 1)
                if in_str == 1 {
                  if esc == 1 { esc = 0  j = j + 1  continue }
                  if ch == "\\" { esc = 1  j = j + 1  continue }
                  if ch == "\"" { in_str = 0  j = j + 1  continue }
                  j = j + 1
                  continue
                }
                if ch == "\"" { in_str = 1  j = j + 1  continue }
                if ch == ")" { k2 = j  break }
                j = j + 1
              }
            }

            {
              local dbg = env.get("HAKO_STAGEB_DEBUG")
              if dbg != null && ("" + dbg) == "1" {
                print("[DEBUG] k2 (')' position) = " + k2)
              }
            }

            if k2 >= 0 {
              // Find opening '{' following ')' (skip inside strings)
              local k3 = -1
              {
                local j = k2
                local n = s.length()
                local in_str = 0
                local esc = 0
                loop(j < n) {
                  local ch = s.substring(j, j + 1)
                  if in_str == 1 {
                    if esc == 1 { esc = 0  j = j + 1  continue }
                    if ch == "\\" { esc = 1  j = j + 1  continue }
                    if ch == "\"" { in_str = 0  j = j + 1  continue }
                    j = j + 1
                    continue
                  }
                  if ch == "\"" { in_str = 1  j = j + 1  continue }
                  if ch == "{" { k3 = j  break }
                  j = j + 1
                }
              }

              {
                local dbg = env.get("HAKO_STAGEB_DEBUG")
                if dbg != null && ("" + dbg) == "1" {
                  print("[DEBUG] k3 ('{' position) = " + k3)
                }
              }

              if k3 >= 0 {
                // Phase 25.1c: Guaranteed trace before balanced scan
                {
                  local trc = env.get("HAKO_STAGEB_TRACE")
                  if trc != null && ("" + trc) == "1" {
                    print("[stageb/body] Before balanced scan: k3=" + ("" + k3) + " len=" + ("" + s.length()))
                  }
                }

                // Balanced scan for matching '}'
                local depth = 0
                local i = k3
                local n = s.length()
                local in_str = 0
                local esc = 0
                local start_pos = -1
                local end_pos = -1
                local iter_count = 0  // Phase 25.1c: iteration counter
                loop(i < n) {
                  // Phase 25.1c: EVERY iteration trace (to detect i stall)
                  iter_count = iter_count + 1
                  {
                    local trc = env.get("HAKO_STAGEB_TRACE")
                    if trc != null && ("" + trc) == "1" {
                      // Print EVERY iteration to catch i-stall immediately
                      print("[stageb/body/iter] #" + ("" + iter_count) + " i=" + ("" + i) + " depth=" + ("" + depth))
                    }
                  }
                  // Phase 25.1c: Trace before substring call
                  {
                    local trc = env.get("HAKO_STAGEB_TRACE")
                    if trc != null && ("" + trc) == "1" {
                      print("[stageb/body/substr-pre] #" + ("" + iter_count) + " calling s.substring(" + ("" + i) + ", " + ("" + (i + 1)) + ")")
                    }
                  }
                  local ch = s.substring(i, i + 1)
                  // Phase 25.1c: Trace after substring call
                  {
                    local trc = env.get("HAKO_STAGEB_TRACE")
                    if trc != null && ("" + trc) == "1" {
                      print("[stageb/body/substr-post] #" + ("" + iter_count) + " got ch")
                    }
                  }
                  // Phase 25.1c: Trace each branch
                  if in_str == 1 {
                    print("[stageb/body/branch] in_str=1")
                    if esc == 1 { esc = 0  i = i + 1  continue }
                    if ch == "\\" { esc = 1  i = i + 1  continue }
                    if ch == "\"" { in_str = 0  i = i + 1  continue }
                    i = i + 1
                    continue
                  }
                  if ch == "\"" {
                    print("[stageb/body/branch] quote")
                    in_str = 1  i = i + 1  continue
                  }
                  if ch == "{" {
                    print("[stageb/body/branch] open-brace depth=" + ("" + depth) + "->+1 i=" + ("" + i) + "->+1")
                    depth = depth + 1  i = i + 1  continue
                  }
                  if ch == "}" {
                    print("[stageb/body/branch] close-brace depth=" + ("" + depth) + "->-1 i=" + ("" + i) + "->+1")
                    depth = depth - 1
                    i = i + 1
                    if depth == 0 { break }
                    continue
                  }
                  print("[stageb/body/branch] other i=" + ("" + i) + "->+1")
                  i = i + 1
                }

                // Phase 25.1c: Guaranteed trace after balanced scan
                {
                  local trc = env.get("HAKO_STAGEB_TRACE")
                  if trc != null && ("" + trc) == "1" {
                    print("[stageb/body] After balanced scan: depth=" + ("" + depth) + " i=" + ("" + i) + " iters=" + ("" + iter_count))
                  }
                }

                {
                  local dbg = env.get("HAKO_STAGEB_DEBUG")
                  if dbg != null && ("" + dbg) == "1" {
                    print("[DEBUG] After balanced scan: depth=" + depth + ", i=" + i)
                  }
                }

                if depth == 0 {
                  // inside of '{'..'}'
                  start_pos = k3 + 1
                  end_pos = i - 1
                  {
                    local dbg = env.get("HAKO_STAGEB_DEBUG")
                    if dbg != null && ("" + dbg) == "1" {
                      print("[DEBUG] Will extract substring(" + start_pos + ", " + end_pos + ")")
                    }
                  }
                }

                // Extract outside the nested blocks to avoid potential scoping issues
                if start_pos >= 0 && end_pos >= start_pos {
                  body_src = s.substring(start_pos, end_pos)
                  {
                    local dbg = env.get("HAKO_STAGEB_DEBUG")
                    if dbg != null && ("" + dbg) == "1" {
                      if body_src == null {
                        print("[DEBUG] After extraction: body_src is NULL!")
                      } else {
                        print("[DEBUG] After extraction: body_src length = " + body_src.length())
                      }
                    }
                  }
                }

                {
                  local dbg = env.get("HAKO_STAGEB_DEBUG")
                  if dbg != null && ("" + dbg) == "1" {
                    if body_src == null { print("[DEBUG] body_src is NULL after line 254") } else { print("[DEBUG] body_src OK after line 254, len=" + body_src.length()) }
                  }
                }
              }

              {
                local dbg = env.get("HAKO_STAGEB_DEBUG")
                if dbg != null && ("" + dbg) == "1" {
                  if body_src == null { print("[DEBUG] body_src is NULL after line 256") } else { print("[DEBUG] body_src OK after line 256, len=" + body_src.length()) }
                }
              }
            }

            {
              local dbg = env.get("HAKO_STAGEB_DEBUG")
              if dbg != null && ("" + dbg) == "1" {
                if body_src == null { print("[DEBUG] body_src is NULL after line 268") } else { print("[DEBUG] body_src OK after line 268, len=" + body_src.length()) }
              }
            }
          }

          {
            local dbg = env.get("HAKO_STAGEB_DEBUG")
            if dbg != null && ("" + dbg) == "1" {
              if body_src == null { print("[DEBUG] body_src is NULL after line 269") } else { print("[DEBUG] body_src OK after line 269, len=" + body_src.length()) }
            }
          }
        }

        {
          local dbg = env.get("HAKO_STAGEB_DEBUG")
          if dbg != null && ("" + dbg) == "1" {
            if body_src == null { print("[DEBUG] body_src is NULL after line 270") } else { print("[DEBUG] body_src OK after line 270, len=" + body_src.length()) }
          }
        }
      }

      // Fallback: if extraction failed or produced empty, use full src
      {
        local dbg = env.get("HAKO_STAGEB_DEBUG")
        if dbg != null && ("" + dbg) == "1" {
          print("[DEBUG] Body extraction:")
          if body_src == null {
            print("[DEBUG]   body_src = null")
          } else {
            print("[DEBUG]   body_src length = " + ("" + body_src).length())
            print("[DEBUG]   body_src = [" + body_src + "]")
          }
        }
      }
      if body_src == null || ("" + body_src).length() == 0 { body_src = src }
    }


    // ============================================================================
    // 4.7) Comment removal: strip // and /* */ comments
    // ============================================================================

    {
      local s = body_src
      local out = ""
      local i = 0
      local n = s.length()
      local in_str = 0
      local esc = 0
      local in_line = 0
      local in_block = 0

      loop(i < n) {
        local ch = s.substring(i, i + 1)

        if in_line == 1 {
          if ch == "\n" { in_line = 0  out = out + ch }
          i = i + 1
          continue
        }

        if in_block == 1 {
          if ch == "*" && i + 1 < n && s.substring(i + 1, i + 2) == "/" { in_block = 0  i = i + 2  continue }
          i = i + 1
          continue
        }

        if in_str == 1 {
          if esc == 1 { out = out + ch  esc = 0  i = i + 1  continue }
          if ch == "\\" { out = out + ch  esc = 1  i = i + 1  continue }
          if ch == "\"" { out = out + ch  in_str = 0  i = i + 1  continue }
          out = out + ch
          i = i + 1
          continue
        }

        // Not in string/comment
        if ch == "\"" { out = out + ch  in_str = 1  i = i + 1  continue }
        if ch == "/" && i + 1 < n {
          local ch2 = s.substring(i + 1, i + 2)
          if ch2 == "/" { in_line = 1  i = i + 2  continue }
          if ch2 == "*" { in_block = 1  i = i + 2  continue }
        }

        out = out + ch
        i = i + 1
      }

      body_src = out
    }


    // ============================================================================
    // 4.5) Bundle resolution: --bundle-src, --bundle-mod, --require-mod
    // ============================================================================
    // Optional: bundle extra module sources provided via repeated --bundle-src args
    // This is a minimal concatenation bundler (no I/O, no resolver). It simply places
    // provided module snippets before the main body for Stage-B parser to accept.
    // Usage example:
    //   compiler_stageb.hako -- --bundle-src "static box Util { method nop(a){ return a } }" --source "static box Main { method main(args){ return 0 } }"
    // Policy:
    //   - --bundle-mod "Name:code" accepts multiple named bundles, but duplicate Name is Fail-Fast
    //     and emits a stable tag: `[bundle/duplicate] Name`.
    //   - --require-mod Name ensures the named module is present (via --bundle-mod), otherwise
    //     Fail-Fast with `[bundle/missing] Name`.

    local bundles = new ArrayBox()
    // Named bundles (name:src) and requirements
    local bundle_names = new ArrayBox()
    local bundle_srcs = new ArrayBox()
    local require_mods = new ArrayBox()

    if args != null {
      local i = 0
      local n = args.length()
      loop(i < n) {
        local t = "" + args.get(i)

        if t == "--bundle-src" && i + 1 < n {
          bundles.push("" + args.get(i + 1))
          i = i + 1
        }

        if t == "--bundle-mod" && i + 1 < n {
          // Parse "name:code" into (name, code)
          local pair = "" + args.get(i + 1)
          local m = pair.length()
          local pos = -1
          {
            local j = 0
            loop(j < m) { if pair.substring(j, j + 1) == ":" { pos = j  break } j = j + 1 }
          }

          if pos >= 0 {
            local name = pair.substring(0, pos)
            local code = pair.substring(pos + 1, m)
            bundle_names.push(name)
            bundle_srcs.push(code)
          }
          i = i + 1
        }

        if t == "--require-mod" && i + 1 < n {
          require_mods.push("" + args.get(i + 1))
          i = i + 1
        }

        i = i + 1
      }
    }
    // Required modules are validated in BundleResolver.resolve (includes env alias injection)


    // ============================================================================
    // 4.6) Fail-Fast on duplicate named bundles
    // ============================================================================
    // Policy: duplicate module names are not allowed. Emit a stable diagnostic tag and exit.

    if bundle_names.length() > 1 {
      local i = 0
      local n = bundle_names.length()
      loop(i < n) {
        local name_i = "" + bundle_names.get(i)
        local j = i + 1
        loop(j < n) {
          if ("" + bundle_names.get(j)) == name_i {
            print("[bundle/duplicate] " + name_i)
            return 1
          }
          j = j + 1
        }
        i = i + 1
      }
    }


    // ============================================================================
    // Bundle merge + line-map debug output
    // ============================================================================

    if bundles.length() > 0 || bundle_srcs.length() > 0 || require_mods.length() > 0 {
      local merged_prefix = BundleResolver.resolve(bundles, bundle_names, bundle_srcs, require_mods)
      if merged_prefix == null { return 1 }

      // Debug: emit line-map for merged bundles so parse error line can be mapped
      {
        local dbg = env.get("HAKO_STAGEB_DEBUG")
        if dbg != null && ("" + dbg) == "1" {
          // Count lines helper (inline)
          local total = 0
          {
            local s2 = merged_prefix
            if s2 == null { total = 0 } else {
              local i = 0; local n = ("" + s2).length(); local c = 1
              loop(i < n){ if ("" + s2).substring(i, i + 1) == "\n" { c = c + 1 } i = i + 1 }
              total = c
            }
          }
          print("[stageb/line-map] prefix total lines=" + total)

          // bundle-src (anonymous)
          if bundles != null && bundles.length() > 0 {
            local i = 0; local acc = 1
            loop(i < bundles.length()) {
              local seg = "" + bundles.get(i)
              local ln = 0
              {
                local s2 = seg
                if s2 == null { ln = 0 } else {
                  local ii = 0; local nn = ("" + s2).length(); local cc = 1
                  loop(ii < nn){ if ("" + s2).substring(ii, ii + 1) == "\n" { cc = cc + 1 } ii = ii + 1 }
                  ln = cc
                }
              }
              local start = acc
              local finish = acc + ln - 1
              print("[stageb/line-map] bundle-src[#" + i + "] " + start + ".." + finish)
              acc = finish + 1
              i = i + 1
            }
          }

          // bundle-mod (named)
          if bundle_names != null && bundle_srcs != null {
            local i2 = 0; local acc2 = 1
            if bundles != null {
              // count lines of joined bundle-src
              local joined = bundles.join("\n")
              if joined == null { acc2 = 1 } else {
                local ii = 0; local nn = ("" + joined).length(); local cc = 1
                loop(ii < nn){ if ("" + joined).substring(ii, ii + 1) == "\n" { cc = cc + 1 } ii = ii + 1 }
                acc2 = cc + 1
              }
            }
            loop(i2 < bundle_srcs.length()) {
              local name = "" + bundle_names.get(i2)
              local seg = "" + bundle_srcs.get(i2)
              local ln = 0
              {
                local s2 = seg
                if s2 == null { ln = 0 } else {
                  local ii = 0; local nn = ("" + s2).length(); local cc = 1
                  loop(ii < nn){ if ("" + s2).substring(ii, ii + 1) == "\n" { cc = cc + 1 } ii = ii + 1 }
                  ln = cc
                }
              }
              local start = acc2
              local finish = acc2 + ln - 1
              print("[stageb/line-map] bundle-mod[name=" + name + "] " + start + ".." + finish)
              acc2 = finish + 1
              i2 = i2 + 1
            }
          }
        }
      }

      body_src = merged_prefix + body_src
    }


    // ============================================================================
    // Using resolver: resolve using statements
    // ============================================================================

    {
      local apply_flag = env.get("HAKO_STAGEB_APPLY_USINGS")
      if apply_flag == null || ("" + apply_flag) != "0" {
        local resolved_prefix = Stage1UsingResolverBox.resolve_for_source(body_src)
        if resolved_prefix != null && resolved_prefix != "" {
          body_src = resolved_prefix + "\n" + body_src
        }
      }
    }


    // ============================================================================
    // 5) Trim: normalize leading/trailing whitespaces and newlines
    // ============================================================================

    {
      local s = body_src
      local n = s.length()
      local b = 0

      // left trim (space, tab, CR, LF)
      loop(b < n) {
        local ch = s.substring(b, b + 1)
        if ch == " " || ch == "\t" || ch == "\r" || ch == "\n" { b = b + 1 } else { break }
      }

      // right trim
      local e = n
      loop(e > b) {
        local ch = s.substring(e - 1, e)
        if ch == " " || ch == "\t" || ch == "\r" || ch == "\n" { e = e - 1 } else { break }
      }

      if e > b { body_src = s.substring(b, e) } else { body_src = "" }
    }

    {
      //出口トレース: 抽出後 body_src 長
      local l2 = 0
      if body_src != null { l2 = ("" + body_src).length() }
      local tracer2 = new StageBTraceBox()
      tracer2.log("StageBBodyExtractorBox.build_body_src:return_len=" + ("" + l2))
    }

    // Clear depth guard before returning
    env.set("HAKO_STAGEB_BODY_DEPTH", "0")
    return body_src
  }
}

// Phase 25.1c: Local function scanner for Stage‑B
// - Scope: static box <Name> { method <name>(params) { ... } }
// - Output: ArrayBox of MapBox:
//     { "name": <String>, "params": ArrayBox<String>, "body_json": <String>, "box": <String> }
static box StageBFuncScannerBox {
  scan_all_boxes(source) {
    if source == null { return new ArrayBox() }
    local defs = new ArrayBox()
    local s = "" + source
    local n = s.length()
    local i = 0
    local in_str = 0
    local esc = 0
    local in_line = 0
    local in_block = 0

    loop(i < n) {
      local ch = s.substring(i, i + 1)
      if in_line == 1 {
        if ch == "\n" { in_line = 0 }
        i = i + 1
        continue
      }
      if in_block == 1 {
        if ch == "*" && i + 1 < n && s.substring(i + 1, i + 2) == "/" {
          in_block = 0
          i = i + 2
          continue
        }
        i = i + 1
        continue
      }
      if in_str == 1 {
        if esc == 1 { esc = 0  i = i + 1  continue }
        if ch == "\\" { esc = 1  i = i + 1  continue }
        if ch == "\"" { in_str = 0  i = i + 1  continue }
        i = i + 1
        continue
      }
      if ch == "\"" { in_str = 1  i = i + 1  continue }
      if ch == "/" && i + 1 < n {
        local ch2 = s.substring(i + 1, i + 2)
        if ch2 == "/" { in_line = 1  i = i + 2  continue }
        if ch2 == "*" { in_block = 1  i = i + 2  continue }
      }

      // Debug: box keyword detection
      if i + 3 <= n && s.substring(i, i + 3) == "box" {
        local boundary_before = FuncScannerBox.kw_boundary_before(s, i)
        local boundary_after = FuncScannerBox.kw_boundary_after(s, i + 3)
      }

      if s.substring(i, i + 3) == "box" && FuncScannerBox.kw_boundary_before(s, i) == 1 && FuncScannerBox.kw_boundary_after(s, i + 3) == 1 {
        print("[scan_all_boxes] found box keyword at i=" + i)
        local cursor = i + 3
        cursor = FuncScannerBox.skip_whitespace(s, cursor)

        local name_start = cursor
        loop(cursor < n) {
          local ch_name = s.substring(cursor, cursor + 1)
          if ch_name == " " || ch_name == "\t" || ch_name == "\n" || ch_name == "\r" || ch_name == "{" { break }
          cursor = cursor + 1
        }
        local box_name = s.substring(name_start, cursor)
        print("[scan_all_boxes] box_name='" + box_name + "'")

        cursor = FuncScannerBox.skip_whitespace(s, cursor)
        if s.substring(cursor, cursor + 1) != "{" {
          i = i + 3
          continue
        }

        local open_idx = cursor
        local close_idx = FuncScannerBox.find_matching_brace(s, open_idx)

        if close_idx < 0 {
          i = i + 3
          continue
        }

        local body = s.substring(open_idx + 1, close_idx)
        // Include Main.main as well so defs covers entry + helpers
        local skip_main = 0
        local include_me = 0
        if box_name != "Main" { include_me = 1 }
        local defs_box = StageBFuncScannerBox._scan_methods(body, box_name, skip_main, include_me)
        StageBFuncScannerBox._append_defs(defs, defs_box)
        i = close_idx + 1
        continue
      }

      i = i + 1
    }
    return defs
  }

  // Dev-only: minimal brace / defs harness for fib-like source.
  // Enabled via StageBDriverBox.main when HAKO_STAGEB_FUNCSCAN_TEST=1.
  test_fib_scan() {
    // Same shape as stageb_fib_program_defs_canary_vm.sh の入力
    local src = "static box TestBox {\n  method fib(n) {\n    local i = 0\n    local a = 0\n    local b = 1\n    loop(i < n) {\n      local t = a + b\n      a = b\n      b = t\n      i = i + 1\n    }\n    return b\n  }\n}\n\nstatic box Main {\n  method main(args) {\n    local t = new TestBox()\n    return t.fib(6)\n  }\n}\n"
    local n = src.length()
    print("[funcscan/test] src_len=" + ("" + n))

    // Open brace positions matching "box TestBox {" と "box Main {"
    local open1 = 19
    local close1 = FuncScannerBox.find_matching_brace(src, open1)
    print("[funcscan/test] brace1 open=" + ("" + open1) + " close=" + ("" + close1))

    local open2 = 209
    local close2 = FuncScannerBox.find_matching_brace(src, open2)
    print("[funcscan/test] brace2 open=" + ("" + open2) + " close=" + ("" + close2))

    // Full scan_all_boxes on the same src to observe defs
    // Use FuncScannerBox directly to avoid delegation issues
    local defs = FuncScannerBox.scan_all_boxes(src)
    local cnt = defs.length()
    print("[funcscan/test] defs_len=" + ("" + cnt))
    local i = 0
    loop(i < cnt) {
      local d = defs.get(i)
      print("[funcscan/test] def[" + ("" + i) + "] box=" + ("" + d.get("box")) + " name=" + ("" + d.get("name")))
      i = i + 1
    }
    return 0
  }

  _scan_methods(source, box_name, skip_main, include_me) {
    local methods = new ArrayBox()
    local s = "" + source
    local n = s.length()
    local i = 0

    loop(i < n) {
      // Search for "method "
      local k = -1
      {
        local pat = "method "
        local m = pat.length()
        local j = i
        loop(j + m <= n) {
          if s.substring(j, j + m) == pat { k = j  break }
          j = j + 1
        }
      }
      if k < 0 { break }
      i = k + 7

      // Extract method name (alphanumeric until '(')
      local name_start = i
      local name_end = -1
      {
        local j = i
        loop(j < n) {
          local ch = s.substring(j, j + 1)
          if ch == "(" { name_end = j  break }
          j = j + 1
        }
      }
      if name_end < 0 { break }
      local method_name = s.substring(name_start, name_end)

      if skip_main == 1 && box_name == "Main" && method_name == "main" { i = name_end  continue }

      // Find '(' after name
      local lparen = name_end

      // Find matching ')' for params (skip strings)
      local rparen = -1
      {
        local j = lparen + 1
        local in_str = 0
        local esc = 0
        loop(j < n) {
          local ch = s.substring(j, j + 1)
          if in_str == 1 {
            if esc == 1 { esc = 0  j = j + 1  continue }
            if ch == "\\" { esc = 1  j = j + 1  continue }
            if ch == "\"" { in_str = 0  j = j + 1  continue }
            j = j + 1
            continue
          }
          if ch == "\"" { in_str = 1  j = j + 1  continue }
          if ch == ")" { rparen = j  break }
          j = j + 1
        }
      }
      if rparen < 0 { break }

      // Extract params (minimal: comma-separated names)
      local params_str = s.substring(lparen + 1, rparen)
      local params = FuncScannerBox._parse_params(params_str)
      if include_me == 1 {
        local first = ""
        if params.length() > 0 { first = "" + params.get(0) }
        if first != "me" {
          local merged = new ArrayBox()
          merged.push("me")
          local pi2 = 0
          local pn2 = params.length()
          loop(pi2 < pn2) {
            merged.push(params.get(pi2))
            pi2 = pi2 + 1
          }
          params = merged
        }
      }

      // Find opening '{' after ')'
      local lbrace = -1
      {
        local j = rparen + 1
        local in_str = 0
        local esc = 0
        loop(j < n) {
          local ch = s.substring(j, j + 1)
          if in_str == 1 {
            if esc == 1 { esc = 0  j = j + 1  continue }
            if ch == "\\" { esc = 1  j = j + 1  continue }
            if ch == "\"" { in_str = 0  j = j + 1  continue }
            j = j + 1
            continue
          }
          if ch == "\"" { in_str = 1  j = j + 1  continue }
          if ch == "{" { lbrace = j  break }
          j = j + 1
        }
      }
      if lbrace < 0 { break }

      // Find matching '}' (balanced)
      local rbrace = -1
      {
        local depth = 0
        local j = lbrace
        local in_str = 0
        local esc = 0
        loop(j < n) {
          local ch = s.substring(j, j + 1)
          if in_str == 1 {
            if esc == 1 { esc = 0  j = j + 1  continue }
            if ch == "\\" { esc = 1  j = j + 1  continue }
            if ch == "\"" { in_str = 0  j = j + 1  continue }
            j = j + 1
            continue
          }
          if ch == "\"" { in_str = 1  j = j + 1  continue }
          if ch == "{" { depth = depth + 1  j = j + 1  continue }
          if ch == "}" {
            depth = depth - 1
            j = j + 1
            if depth == 0 { rbrace = j - 1  break }
            continue
          }
          j = j + 1
        }
      }
      if rbrace < 0 { break }

      // Extract method body (inside braces)
      local method_body = s.substring(lbrace + 1, rbrace)

      method_body = FuncScannerBox._strip_comments(method_body)
      method_body = FuncScannerBox._trim(method_body)

      // Parse method body to JSON (statement list)
      // Policy: block parser first (braced) to avoid VM dispatch misrouting, then Program parser as backup.
      local body_json = null
      if method_body.length() > 0 {
        // 1st: block parser with explicit braces (LoopForm-friendly, avoids ctx misbinding)
        {
          local p = new ParserBox()
          p.stage3_enable(1)
          local block_src = "{" + method_body + "}"
          local block_res = p.parse_block2(block_src, 0)
          local at = block_res.lastIndexOf("@")
          if at >= 0 { body_json = block_res.substring(0, at) }
        }
        // 2nd: program parser trims body JSON if block path produced nothing (opt-in)
        if body_json == null || body_json == "" || body_json == "[]" {
          local allow_prog = env.get("HAKO_STAGEB_FUNC_SCAN_PROG_FALLBACK")
          if allow_prog != null && ("" + allow_prog) == "1" {
            local p2 = new ParserBox()
            p2.stage3_enable(1)
            local prog_json = p2.parse_program2(method_body)
            local extracted = me._extract_body_from_program(prog_json)
            if extracted.length() > 0 { body_json = extracted }
          }
        }
      }

      if body_json != null && body_json != "" {
        local def = new MapBox()
        def.set("name", method_name)
        def.set("params", params)
        def.set("body_json", body_json)
        def.set("box", box_name)
        methods.push(def)
      }
      i = rbrace
    }

    return methods
  }

  _append_defs(defs, new_defs) {
    if new_defs == null { return 0 }
    local n = new_defs.length()
    local i = 0
    loop(i < n) {
      defs.push(new_defs.get(i))
      i = i + 1
    }
    return 0
  }

  // Helper: extract Program.body array from Program(JSON) string
  _extract_body_from_program(prog_json) {
    if prog_json == null { return "" }
    local s = "" + prog_json
    local n = s.length()
    local head = "\"body\":"
    local hlen = head.length()
    local start = -1
    {
      local i = 0
      loop(i + hlen <= n) {
        if s.substring(i, i + hlen) == head { start = i + hlen  break }
        i = i + 1
      }
    }
    if start < 0 { return "" }

    // Find '[' after "body":
    local open = -1
    {
      local j = start
      loop(j < n) {
        local ch = s.substring(j, j + 1)
        if ch == "[" { open = j  break }
        j = j + 1
      }
    }
    if open < 0 { return "" }

    local depth = 0
    local k = open
    loop(k < n) {
      local ch2 = s.substring(k, k + 1)
      if ch2 == "[" { depth = depth + 1 }
      else if ch2 == "]" {
        depth = depth - 1
        if depth == 0 { return s.substring(open, k + 1) }
      }
      k = k + 1
    }
    return ""
  }

  // Helper: 空白文字スキップ（FuncScannerBox に委譲）
  _skip_whitespace(s, idx) {
    return FuncScannerBox.skip_whitespace(s, idx)
  }

  // Helper: キーワード前境界チェック（FuncScannerBox に委譲）
  _kw_boundary_before(s, idx) {
    return FuncScannerBox.kw_boundary_before(s, idx)
  }

  // Helper: キーワード後境界チェック（FuncScannerBox に委譲）
  _kw_boundary_after(s, idx) {
    return FuncScannerBox.kw_boundary_after(s, idx)
  }

  // Helper: 識別子文字判定（FuncScannerBox に委譲）
  _is_ident_char(ch) {
    return FuncScannerBox.is_ident_char(ch)
  }

  test_nested_loop_int(n) {
    local i = 0
    loop(i < 3) {
      i = i + 1
    }
  }

  test_nested_loop_str(s) {
    local i = 0
    loop(i < 3) {
      i = i + 1
    }
  }

  // Helper: 対応する閉じブレース検索（FuncScannerBox に委譲）
  find_matching_brace(source_str, open_idx) {
    return FuncScannerBox.find_matching_brace(source_str, open_idx)
  }

  // Helper: パラメータリストパース（FuncScannerBox に委譲）
  parse_params(params_str) {
    return FuncScannerBox._parse_params(params_str)
  }

  // Helper: コメント削除（FuncScannerBox に委譲）
  strip_comments(source) {
    return FuncScannerBox._strip_comments(source)
  }

  // Helper: 前後空白削除（FuncScannerBox に委譲）
  trim(s) {
    return FuncScannerBox._trim(s)
  }
}

static box StageBHelperBox {
  test_loop(n) {
    local i = 0
    loop(i < 3) {
      i = i + 1
    }
  }
}

// Phase 25.1c: Main driver logic
static box StageBDriverBox {
  // Phase 28.2 Quick Win 3: SSOT compile API for compiler.hako direct call
  // Returns Program(JSON v0) instead of printing it.
  compile(source, prefer_cfg, stage3, v1_compat) {
    // 1) Set env vars for internal functions
    if stage3 != null && stage3 != 0 {
      env.set("NYASH_PARSER_STAGE3", "1")
      env.set("HAKO_PARSER_STAGE3", "1")
    }

    // 2) Create parser
    local p = new ParserBox()
    if stage3 != null && stage3 != 0 {
      p.stage3_enable(1)
    }

    // 3) Extract body and parse
    local body_src = StageBBodyExtractorBox.build_body_src(source, null)

    // 4) Parse to JSON
    local ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":[]}"
    if body_src != null {
      local block_src = "{" + body_src + "}"
      local block_res = p.parse_block2(block_src, 0)
      local at = block_res.lastIndexOf("@")
      if at >= 0 {
        local body_json = block_res.substring(0, at)
        if body_json != null && body_json != "" {
          ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":" + body_json + "}"
        }
      }
    }

    // 5) Apply SSA transformations
    ast_json = CompilerBuilder.apply_all(ast_json)

    // 6) Scan for function definitions
    local defs_json = ""
    local methods = StageBFuncScannerBox.scan_all_boxes(source)
    if methods != null && methods.length() > 0 {
      defs_json = ",\"defs\":["
      local mi = 0
      local mn = methods.length()
      loop(mi < mn) {
        local def = methods.get(mi)

        // Defensive check: skip if def is null or StringBox
        if def == null {
          mi = mi + 1
          continue
        }
        // Check if def is a StringBox by converting to string and comparing
        local def_str = "" + def
        if def_str == def {
          mi = mi + 1
          continue
        }

        local mname = "" + def.get("name")
        local mparams = def.get("params")
        local mbody = "" + def.get("body_json")
        local mbox = "" + def.get("box")
        local wrapped_body = "{\"type\":\"Block\",\"body\":" + mbody + "}"
        local params_arr = "["
        local pi = 0
        local pn = mparams.length()
        loop(pi < pn) {
          if pi > 0 { params_arr = params_arr + "," }
          params_arr = params_arr + "\"" + ("" + mparams.get(pi)) + "\""
          pi = pi + 1
        }
        params_arr = params_arr + "]"
        if mi > 0 { defs_json = defs_json + "," }
        defs_json = defs_json + "{\"name\":\"" + mname + "\",\"params\":" + params_arr + ",\"body\":" + wrapped_body + ",\"box\":\"" + mbox + "\"}"
        mi = mi + 1
      }
      defs_json = defs_json + "]"
    }

    // 7) Inject defs into Program JSON
    if defs_json != "" && defs_json.length() > 0 {
      local ajson = "" + ast_json
      local close_pos = -1
      local j = ajson.length() - 1
      loop(j >= 0) {
        if ajson.substring(j, j + 1) == "}" { close_pos = j  break }
        j = j - 1
      }
      if close_pos >= 0 {
        ast_json = ajson.substring(0, close_pos) + defs_json + ajson.substring(close_pos, ajson.length())
      }
    }

    // 8) Return JSON (SSOT - single return point)
    return ast_json
  }

  main(args) {
    // Dev trace to confirm entry dispatch
    print("[stageb/main] enter")

    // ============================================================================
    // Phase 25.1c: Guaranteed marker for entry point confirmation (dev-only)
    // ============================================================================
    {
      local marker_flag = env.get("HAKO_STAGEB_TRACE")
      if marker_flag != null && ("" + marker_flag) == "1" {
        print("[stageb/marker] StageBDriverBox.main:ENTRY_CONFIRMED")
      }
    }

    // ============================================================================
    // Depth guard: prevent accidental recursion in Stage‑B driver entry
    // ============================================================================
    {
      local depth = env.get("HAKO_STAGEB_DRIVER_DEPTH")
      if depth != null && ("" + depth) != "0" {
        print("[stageb/error] depth_guard tripped: HAKO_STAGEB_DRIVER_DEPTH!=0")
        return -1
      }
      env.set("HAKO_STAGEB_DRIVER_DEPTH", "1")
    }
    // Depth guard cleared → main 続行できることを明示
    print("[stageb/main] depth ok")

    // Dev-only: direct FuncScanner harness
    {
      local test_flag = env.get("HAKO_STAGEB_FUNCSCAN_TEST")
      if test_flag != null && ("" + test_flag) == "1" {
        print("[stageb/info] FUNC_SCAN_TEST=1, skipping emit")
        StageBFuncScannerBox.test_fib_scan()
        // Clear depth guard before returning
        env.set("HAKO_STAGEB_DRIVER_DEPTH", "0")
        return 0
      }
    }

    {
      local tracer = new StageBTraceBox()
      tracer.log("StageBDriverBox.main:enter")
    }

    local src = StageBArgsBox.resolve_src(args)
    {
      local l = 0
      if src != null { l = ("" + src).length() }
      local tracer = new StageBTraceBox()
      tracer.log("StageBDriverBox.main:after_resolve_src len=" + ("" + l))
    }

    // 2) Stage‑3 acceptance default ON for selfhost (env may turn off; keep tolerant here)
    local p = new ParserBox()
    p.stage3_enable(1)

    // 3) (Temporarily disabled) Extract using/externs — keep minimal path for emit stability
    // p.extract_usings(src)
    // local usings_json = p.get_usings_json()
    // p.extract_externs(src)
    // local externs_json = p.get_externs_json()

    local body_src = StageBBodyExtractorBox.build_body_src(src, args)
    if body_src == null {
      print("[stageb/error] no body_src (StageBBodyExtractorBox returned null)")
    }
    {
      local l2 = 0
      if body_src != null { l2 = ("" + body_src).length() }
      local tracer = new StageBTraceBox()
      tracer.log("StageBDriverBox.main:after_build_body_src len=" + ("" + l2))
    }

    // 6) Parse and emit Stage‑1 JSON v0 (Program)
    // Bridge(JSON v0) が Program v0 を受け取り MIR に lowering するため、ここでは AST(JSON v0) を出力する。
    // 既定で MIR 直出力は行わない（重い経路を避け、一行出力を保証）。
    // Stage‑B/selfhost: body_src は Main.main 内のブロック本文（外側の {} は含まない）。
    // 安定性向上のため Program パーサを優先し、空の場合のみ block パーサで再度包んで解釈する。
    local ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":[]}"
    {
      local body_json = null

      // Prefer block parser with explicit braces to avoid VM dispatch drift seen on program parser.
      if body_src != null {
        local block_src = "{" + body_src + "}"
        local block_res = p.parse_block2(block_src, 0)
        local at = block_res.lastIndexOf("@")
        if at >= 0 { body_json = block_res.substring(0, at) }
      }

      // Optional: enable program parser fallback only when explicitly requested
      if (body_json == null || body_json == "" || body_json == "[]") {
        local allow_prog = env.get("HAKO_STAGEB_PROGRAM_PARSE_FALLBACK")
        if allow_prog != null && ("" + allow_prog) == "1" {
          local prog_json = p.parse_program2(body_src)
          body_json = StageBFuncScannerBox._extract_body_from_program(prog_json)
        }
      }

      if body_json != null && body_json != "" {
        ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":" + body_json + "}"
      }
    }
    {
      // AST(JSON v0) の長さを軽く観測
      local la = 0
      if ast_json != null { la = ("" + ast_json).length() }
      local tracer = new StageBTraceBox()
      tracer.log("StageBDriverBox.main:after_parse_program2 len=" + ("" + la))
    }

    // 6.3) Apply SSA transformations (CompilerBuilder pipeline)
    {
      local trace = env.get("HAKO_COMPILER_BUILDER_TRACE")
      if trace != null && ("" + trace) == "1" {
        local preview_len = 200
        local ajson_len = ("" + ast_json).length()
        if ajson_len < preview_len { preview_len = ajson_len }
        print("[compiler-builder] before: " + ("" + ast_json).substring(0, preview_len) + "...")
      }

      ast_json = CompilerBuilder.apply_all(ast_json)

      if trace != null && ("" + trace) == "1" {
        local preview_len = 200
        local ajson_len = ("" + ast_json).length()
        if ajson_len < preview_len { preview_len = ajson_len }
        print("[compiler-builder] after: " + ("" + ast_json).substring(0, preview_len) + "...")
      }
    }

    // 6.5) Dev-toggle: scan for function definitions (static box { method <name>(...) {...} })
    // Toggle: HAKO_STAGEB_FUNC_SCAN
    // Policy:
    //   - 未設定(null) のときは既定で ON（defs を常に埋める）。
    //   - 明示的に "0" が入っているときだけ OFF。
    // Scope: method <name>(params) { ... } outside of main (same box Main)
    // Output: inject "defs":[{"name":"<name>","params":[...],"body":[...], "box":"Main"}] to Program JSON
    local defs_json = ""
    {
      local func_scan_env = env.get("HAKO_STAGEB_FUNC_SCAN")
      local func_scan_on = 1
      // 明示的に "0" のときだけ OFF 扱い。それ以外（null/1/true/on）は ON。
      if func_scan_env != null && ("" + func_scan_env) == "0" { func_scan_on = 0 }
      if func_scan_on == 1 {
        // Use StageBFuncScannerBox (Stage‑B local implementation) to extract method definitions from all boxes
        // Dev trace: observe source head used for FuncScanner (Stage‑B fib など)
        {
          local tracer = new StageBTraceBox()
          local head = "" + src
          local max_len = 80
          if head.length() > max_len { head = head.substring(0, max_len) }
          tracer.log("StageBDriverBox.main:func_scan src_head=" + head)
        }

        local methods = StageBFuncScannerBox.scan_all_boxes(src)

        {
          local cnt = 0
          if methods != null { cnt = methods.length() }
          local tracer = new StageBTraceBox()
          tracer.log("StageBDriverBox.main:func_scan methods=" + ("" + cnt))
          // Optional detailed trace when HAKO_STAGEB_TRACE=1:
          // dump each def entry (box/name) to確認 how FuncScannerBox saw the source.
          if cnt > 0 {
            local mi = 0
            loop(mi < cnt) {
              local def = methods.get(mi)
              local mbox = "" + def.get("box")
              local mname = "" + def.get("name")
              tracer.log("StageBDriverBox.main:func_scan def box=" + mbox + " name=" + mname)
              mi = mi + 1
            }
          }
        }

        // Build defs JSON array
        if methods.length() > 0 {
          defs_json = ",\"defs\":["
          local mi = 0
          local mn = methods.length()
          loop(mi < mn) {
            local def = methods.get(mi)

            // Defensive check: skip if def is null or StringBox
            if def == null {
              mi = mi + 1
              continue
            }
            // Check if def is a StringBox by converting to string and comparing
            local def_str = "" + def
            if def_str == def {
              mi = mi + 1
              continue
            }

            local mname = "" + def.get("name")
            local mparams = def.get("params")
            local mbody = "" + def.get("body_json")
            local mbox = "" + def.get("box")
            // Wrap body in Block node to align with Program.body shape expectations
            local wrapped_body = "{\"type\":\"Block\",\"body\":" + mbody + "}"
            // Build params array JSON
            local params_arr = "["
            local pi = 0
            local pn = mparams.length()
            loop(pi < pn) {
              if pi > 0 { params_arr = params_arr + "," }
              params_arr = params_arr + "\"" + ("" + mparams.get(pi)) + "\""
              pi = pi + 1
            }
            params_arr = params_arr + "]"
            if mi > 0 { defs_json = defs_json + "," }
            defs_json = defs_json + "{\"name\":\"" + mname + "\",\"params\":" + params_arr + ",\"body\":" + wrapped_body + ",\"box\":\"" + mbox + "\"}"
            mi = mi + 1
          }
          defs_json = defs_json + "]"
        }
      }
    }

    // 7) Inject defs into Program JSON if available
    if defs_json != "" && defs_json.length() > 0 {
      // Insert defs before closing } of Program JSON
      local ajson = "" + ast_json
      local close_pos = -1
      {
        local j = ajson.length() - 1
        loop(j >= 0) {
          if ajson.substring(j, j + 1) == "}" { close_pos = j  break }
          j = j - 1
        }
      }
      if close_pos >= 0 {
        ast_json = ajson.substring(0, close_pos) + defs_json + ajson.substring(close_pos, ajson.length())
      }
    }

    // Phase 71-SSA 71-11.1: Guaranteed trace before Program JSON emit
    {
      local marker_enabled = env.get("HAKO_STAGEB_TRACE")
      if marker_enabled != null && ("" + marker_enabled) == "1" {
        local ajson_len = 0
        if ast_json != null { ajson_len = ("" + ast_json).length() }
        print("[stageb/main] BEFORE_EMIT ast_json_len=" + ("" + ajson_len))
      }
    }
    print("[stageb/main] before ast_json")
    print(ast_json)
    print("[stageb/main] after ast_json")
    // Phase 71-SSA 71-11.1: Guaranteed trace after Program JSON emit
    {
      local marker_enabled = env.get("HAKO_STAGEB_TRACE")
      if marker_enabled != null && ("" + marker_enabled) == "1" {
        print("[stageb/main] AFTER_EMIT success")
      }
    }
    {
      local tracer = new StageBTraceBox()
      tracer.log("StageBDriverBox.main:exit rc=0")
    }
    // Clear depth guard before returning
    env.set("HAKO_STAGEB_DRIVER_DEPTH", "0")
    return 0
  }
}

// Note: Runner resolves entry as Main.main by default.
// Provide static box Main with method main(args) as the entry point.
// Phase 25.1c: Entry point (thin wrapper)
static box Main {
  main(args) {
    return StageBDriverBox.main(args)
  }
}
