Skip to content

chore(pua): fixture 自動生成 + 数値ハードコード集約 (B + B') - #392

Merged
ayutaz merged 5 commits into
devfrom
chore/pua-fixture-autogen-and-consolidation
May 5, 2026
Merged

chore(pua): fixture 自動生成 + 数値ハードコード集約 (B + B')#392
ayutaz merged 5 commits into
devfrom
chore/pua-fixture-autogen-and-consolidation

Conversation

@ayutaz

@ayutaz ayutaz commented May 5, 2026

Copy link
Copy Markdown
Owner

Summary

PR #389 で踏んだ「pua_map_count を 99 に bump したが pua_map dict は 96 のまま」というドリフトの再発防止。fixture を pua.json から自動生成 + drift 検知 CI、各ランタイムテストの絶対値 99 を fixture / pua.json / サニティ由来に置換。

Note: PR #389 を base にした stack PR です。先に #389 をマージしてください。マージ後 GitHub が自動的に base を dev に切り替えます。

何が問題だったか

PR #389tests/fixtures/g2p/phoneme_test_cases.jsonpua_map_count を 99 に bump したが pua_map dict は 96 entries のままで、Rust test_pua_map_individualpua_map entry count (96) != pua_map_count (99) で fail。

加えて 5 ランタイム x 複数ファイルに 99 がハードコードされ、PUA テーブル拡張時に「数箇所だけ更新漏れ」のドリフト発生源が散在していた。

変更内容

B (fixture 自動生成)

  • scripts/regenerate_test_fixture.py 新設
    • pua.json を canonical として fixture の pua_map_count / pua_map セクションだけを再生成
    • 配列フォーマット (expected_contains: ["a", "b"]) は触らない (= 差分最小)
    • --check モードで drift 検出 (CI 用、exit 1)
  • .github/workflows/pua-consistency.ymlfixture-drift job 追加

B' (数値ハードコード集約)

言語 ファイル Before After
Python test_compat.py / test_encode.py == 99 fixture pua_map_count 由来
Rust test_g2p_golden.rs pua_map_count, 99 削除 (FIXED_PUA_MAP.len() vs fixture で十分)
Rust test_token_map_parity.rs assert_eq!(.., 99) pua.json 直読み
Rust token_map.rs (unit test) assert_eq!(.., 99) >= 50 サニティ
Go pua_test.go const want = 99 len(allFixedPUA)
JS test-pua-map.js 99 x 2 箇所 FIXTURE.pua_map_count
C# PhonemeConverterTests Assert.Equal(99, ..) サニティ (>= 50)
C# SwedishPhonemizerTests PuaMapping_TotalCount_Is99 PuaMapping_IsPopulated (リネーム)

「強い不変条件」 (= pua.json と各ランタイムが byte-for-byte 一致) は既存の cross-runtime-table-consistency CI gate が保証。各テストでは len(internal) == fixture.pua_map_count のサニティに集約。

ローカル検証

  • Python: pytest test_pua_invariants.py test_encode.py::TestPUAMapping31/31 pass
  • JS: node --test test/test-pua-map.js140/140 pass
  • Go: go test ./phonemize/... -run "TestFixedPUA"all pass

Test plan

  • CI: All 6 runtime PUA tables match pua.json
  • CI: Test fixture in sync with pua.json (新規 job)
  • CI: Inventory coverage + fail-fast guards
  • CI: Rust / Go / JS-WASM PUA table tests
  • CI: Pre-flight config validator
  • CI: csharp-tests

@ayutaz ayutaz self-assigned this May 5, 2026
Base automatically changed from fix/pua-multi-codepoint-and-validation to dev May 5, 2026 02:02
PR #389 で踏んだ「pua_map_count を 99 に bump したが pua_map dict は 96 のまま」
というドリフトの再発防止。fixture を pua.json から自動生成 + drift 検知 CI、
各ランタイムテストの絶対値 `99` を fixture/pua.json/サニティ由来に置換。

# B (fixture 自動生成)
- scripts/regenerate_test_fixture.py 新設
  - pua.json から phoneme_test_cases.json の pua_map / pua_map_count を再生成
  - fixture の他フィールド (test cases) のフォーマットは触らない
  - --check モードでドリフト検出 (CI 用)
- .github/workflows/pua-consistency.yml に "fixture-drift" job 追加

# B' (数値ハードコード集約)
- Python: test_compat.py / test_encode.py — `== 99` を fixture pua_map_count 由来に
- Rust:
  - test_g2p_golden.rs — `pua_map_count, 99` 削除 (FIXED_PUA_MAP.len() vs fixture で十分)
  - test_token_map_parity.rs — `assert_eq!(.., 99)` を pua.json 直読みに
  - token_map.rs — unit test を `>= 50` サニティに弱める (強い不変条件は CI gate)
- Go: pua_test.go — `const want = 99` を `len(allFixedPUA)` に
- JS: test-pua-map.js — `99` を `FIXTURE.pua_map_count` 参照に
- C#: PhonemeConverterTests / SwedishPhonemizerTests — サニティ (>= 50) に弱める
  - SwedishPhonemizerTests のメソッド名 `_Is99` → `_IsPopulated` にリネーム

ローカル検証: Python 31/31, JS 140/140, Go all pass.
@ayutaz
ayutaz force-pushed the chore/pua-fixture-autogen-and-consolidation branch from 7d08924 to 078cb28 Compare May 5, 2026 02:03
import block の後ろに空行を追加 (PEP 8 / ruff I001)。
Copilot AI review requested due to automatic review settings May 5, 2026 02:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR reduces PUA table drift by making the cross-platform fixture derive from pua.json, adding CI drift detection, and removing hardcoded absolute counts (e.g., 99) across runtime test suites.

Changes:

  • Add scripts/regenerate_test_fixture.py to regenerate only pua_map_count / pua_map in the shared fixture from pua.json, with --check drift mode.
  • Add a fixture-drift GitHub Actions job to enforce fixture ↔ pua.json sync in CI.
  • Replace hardcoded PUA entry counts in various runtime tests with fixture- or pua.json-derived counts (or sanity checks).

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/wasm/g2p/test/test-pua-map.js Removes hardcoded 99 and ties entry-count assertions to fixture count
src/rust/piper-plus-g2p/src/token_map.rs Replaces exact-count assertion with a looser sanity check
src/rust/piper-core/tests/test_token_map_parity.rs Reads expected entry count from canonical pua.json instead of a literal
src/rust/piper-core/tests/test_g2p_golden.rs Removes baked-in count and relies on fixture consistency + runtime parity
src/python/g2p/tests/test_encode.py Uses fixture pua_map_count to validate Python mapping length
src/python/g2p/tests/test_compat.py Uses fixture pua_map_count to validate Python mapping length
src/go/phonemize/pua_test.go Removes literal expected count and derives it from reference list
src/csharp/PiperPlus.Core.Tests/SwedishPhonemizerTests.cs Replaces exact-count test with a sanity check and renames the test
src/csharp/PiperPlus.Core.Tests/PhonemeConverterTests.cs Replaces exact-count assertion with a sanity check
scripts/regenerate_test_fixture.py New script to regenerate only PUA-related blocks in fixture without reformatting
.github/workflows/pua-consistency.yml Adds CI job to detect fixture drift from pua.json

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/regenerate_test_fixture.py Outdated
Comment thread scripts/regenerate_test_fixture.py Outdated
Comment thread src/wasm/g2p/test/test-pua-map.js Outdated
Comment thread src/rust/piper-core/tests/test_token_map_parity.rs Outdated
Comment thread src/rust/piper-plus-g2p/src/token_map.rs Outdated
Comment thread src/csharp/PiperPlus.Core.Tests/SwedishPhonemizerTests.cs Outdated
ayutaz added 3 commits May 5, 2026 11:30
- regenerate_test_fixture.py:
  - re.subn で match count を確認、FixtureFormatError で fail-fast
  - pua_map regex を [^{}]* で固める (将来 nested {} 混入時に silent ではなく失敗)
- test-pua-map.js:
  - count assert を describe 評価時から it() 内に移動 (test として認識される)
- test_token_map_parity.rs:
  - 重複していた repo-root 検出を repo_root() ヘルパーに抽出
- token_map.rs:
  - >= 50 サニティを include_str! で pua.json を埋め込んだ厳密比較に強化
  - partial truncation を検出可能に
- SwedishPhonemizerTests.cs:
  - >= 50 サニティを pua.json 直読みでの厳密比較に強化
  - FindRepoFile ヘルパーで AppDomain BaseDirectory から walk-up
@ayutaz
ayutaz merged commit 3a38a61 into dev May 5, 2026
123 of 124 checks passed
@ayutaz
ayutaz deleted the chore/pua-fixture-autogen-and-consolidation branch May 5, 2026 15:20
ayutaz added a commit that referenced this pull request May 7, 2026
dev リベースで取り込んだ #392 (fixture schema v1→v2) と #400 (Kotlin G2P が
新規英語ケース追加) によって Swift CI 2 件失敗を解消。

# 修正内容
- tests/fixtures/g2p/phoneme_test_cases.json: regenerate_test_fixture.py で
  pua_map セクションを再生成 (drift 検出 CI が通る形式に統一、ensure_ascii=True
  で IPA 文字を \\u エスケープ表記に)。test_cases 自体は不変。
- GoldenPhonemeTests.swift: testFixtureLoads の expected version を 1 → 2 に
  更新。runGoldenCases で Rust crate の英語実装が letter-spelling fallback
  未実装である既知ギャップ (acronym/all-caps/mixed-case/repeated/Latin-1
  accent/quote-only) を skip リスト化。Rust 英語実装の改善は別 PR で追跡。
ayutaz added a commit that referenced this pull request May 8, 2026
* docs(swift-g2p): Swift G2P 対応の設計仕様 + 利用ガイド + 事前決定事項 (#387)

Issue #387 (Swift 向け G2P 対応) の実装着手前ドキュメントを整備。
方針 1 (既存 Rust piper-plus-g2p の C FFI を Swift から呼ぶ) を採用。

- docs/spec/swift-g2p.md
  配布仕様。独立 xcframework (`libpiper_plus_g2p-ios.xcframework.zip`)
  として合成エンジンと分離配布する。Cargo + lipo で iOS slice を
  ビルド、cbindgen で C ヘッダ自動生成、`Package.swift` に
  `PiperPlusG2P` product を追加。8 言語フル (ja/en/zh/ko/es/fr/pt/sv) 対応。

- docs/guides/swift-g2p-integration.md
  iOS 開発者向け SPM 統合ガイド。`Phonemizer` Swift API、
  単一/多言語/SwiftUI のコード例、トラブルシュート、ライセンス。

- docs/spec/swift-g2p-decisions.md
  実装着手前のチェックリスト全 15 項目。重大な盲点として
  EN (cmudict 3.7MB) / ZH (pinyin 2.7MB) の辞書が現状 disk 依存で
  iOS App Sandbox では動かない件を指摘。`bundled-dicts` feature
  新設による include_str! / include_bytes! 埋込を推奨。

* feat(g2p): Swift G2P prototype + iOS ビルド検証 (#387)

Issue #387 (Swift 向け G2P 対応) の実装着手前検証。
Rust crate 側の必要変更を最小限のプロトタイプで実証し、
iOS 全 slice (device + simulator universal) でビルド成功を確認。

検証結果:
- aarch64-apple-ios            : 26s, libpiper_plus_g2p.a 84MB
- aarch64-apple-ios-sim        : 18s
- x86_64-apple-ios             : 18s
- lipo (sim universal)         : 168MB
- bundled-dicts feature 有効   : 91MB (+6.4MB ≈ JSON 実サイズ)
- 既存 unit test 373 件全 pass : 後方互換確認
- runtime smoke test           : EN "Hello world" / ZH "你好世界" 両方とも IPA 出力 OK
- cbindgen header 自動生成     : piper_plus_g2p.h 70 行 (5 関数 + opaque struct)

変更内容:
- Cargo.toml
  + [lib] crate-type = ["staticlib", "cdylib", "rlib"]
  + bundled-dicts feature 新設 (default 非含有)
- src/english.rs
  + EnglishPhonemizer::new_bundled() (#[cfg(feature = "bundled-dicts")])
    cmudict_data.json を include_str! で埋込
- src/chinese.rs
  + ChinesePhonemizer::new_bundled() (#[cfg(feature = "bundled-dicts")])
    pinyin_{single,phrases}.json を include_bytes! で埋込
- cbindgen.toml 新設 (C ヘッダ自動生成設定)
- data/ 新設 (cmudict + pinyin JSON、src/cpp/ から複製、計 6.4MB)

TODO (follow-up):
- data/ の重複解消: build.rs か Cargo の package.include で
  src/cpp/ の JSON を参照する設計に切替 (現状は単純コピー)
- ffi.rs: register_one("zh") で new_bundled() を呼ぶ分岐追加
- ffi.rs: register_one("en") も new_bundled() に切替 (CMUDICT_PATH より優先)
- cbindgen.toml: PUA_COMPAT_VERSION 定数を [export.exclude] で除外
- Sources/PiperPlusG2P/, Package.swift, release-shared-lib.yml は別 PR

* feat(swift-g2p): PiperPlusG2P SPM product 実装 (#387)

Issue #387 (Swift 向け G2P 対応) の実装本体。
Rust piper-plus-g2p crate の C FFI を Swift から呼ぶ Plan A 採用。
合成エンジン (PiperPlus, ORT 必須) と独立した PiperPlusG2P product を
追加し、ORT 非依存で 8 言語の G2P を Swift から利用可能にする。

Step 1 — ffi.rs 拡張:
- register_one("zh") を bundled-dicts feature 有効時に
  ChinesePhonemizer::new_bundled() 経由に変更 (従来は登録不可)
- register_one("en") も bundled-dicts 有効時に new_bundled() 優先
- register_one("ja") は naist-jdic feature 有効時に new_bundled() 経由
- ffi tests に Chinese/English/Japanese bundled テスト追加 (396 件全 pass)
- cbindgen.toml に [export.exclude] PUA_COMPAT_VERSION 追加

Step 2 — Sources/PiperPlusG2P/:
- Language.swift   : enum Language (8 言語、CaseIterable + Sendable)
- G2PError.swift   : enum G2PError (initFailed/null/utf8/decode + 説明)
- PhonemizeResult.swift: struct PhonemizeResult (Codable, Sendable)
- Phonemizer.swift : final class Phonemizer
    init(languages:) → piper_plus_g2p_create
    phonemize(_:language:) → JSON → PhonemizeResult
    availableLanguages → piper_plus_g2p_available_languages
    deinit → piper_plus_g2p_free

Step 3 — Package.swift:
- 新 product .library(name: "PiperPlusG2P", ...)
- 新 binaryTarget PiperPlusG2PBinary
  (libpiper_plus_g2p-ios-v${VERSION}.xcframework.zip)
- g2pVersion / g2pChecksum を追加 (合成側 checksum と同じ手動更新運用)
- testTarget PiperPlusG2PTests を path: "tests/PiperPlusG2PTests" で追加
  (既存 tests/ ディレクトリと整合、Linux 大文字小文字区別対応)

Step 4 — release-shared-lib.yml:
- build-g2p-ios job: ios-arm64 / ios-arm64_x86_64-simulator の matrix
  cargo build (3 target) → lipo (sim universal) → cbindgen → modulemap
  rustup targets, cargo cache, cbindgen cache 設定
  FFI シンボル 5 個の export を nm で検証
- assemble-g2p-xcframework job: xcodebuild -create-xcframework で
  libpiper_plus_g2p-ios.xcframework.zip 生成、PrivacyInfo.xcprivacy 同梱
- release job: needs に assemble-g2p-xcframework 追加、
  Package.swift の g2pChecksum 検証、xcframework zip との sha256 突合
  artifact rename + Releases upload

Step 5 — README / CHANGELOG:
- CLAUDE.md ランタイム表に "iOS Swift G2P (SPM)" 行追加
- CHANGELOG.md [Unreleased] に PiperPlusG2P エントリ追加
  (関連: docs/spec/swift-g2p.md, docs/guides/swift-g2p-integration.md)

検証実施結果:
- cargo test (all-languages,naist-jdic,bundled-dicts,ffi) — 396 件 pass
- cargo build aarch64-apple-ios (release) — 成功
  libpiper_plus_g2p.a に piper_plus_g2p_* 5 シンボル全て export 確認
- YAML syntax check (release-shared-lib.yml) — pass

未対応 (follow-up):
- 初回リリース時は g2pChecksum を手動更新 (placeholder 0000... のまま)
- Sources/PiperPlusG2P/ の SourceKit 診断エラーは binaryTarget 未 resolve
  のため (xcframework 配信後に解消)
- examples/swift/G2PExample/ サンプルアプリは別 issue 切出し
- data/ JSON と src/cpp/ JSON の重複解消 (build.rs か symlink)

* test(swift-g2p): cross-runtime golden phoneme tests を Swift 側にも追加 (#387)

既存の tests/fixtures/g2p/phoneme_test_cases.json (Python/Rust/JS で
共有されている cross-platform G2P 整合性フィクスチャ) を Swift wrapper
からも参照し、同じ structural assertion を適用。

- Tests/PiperPlusG2PTests/GoldenPhonemeTests.swift 新設
- #filePath で source-relative にフィクスチャを解決 (コピー / 重複なし、
  source of truth は tests/fixtures/g2p/ のまま)
- 81 ケース (ja:7, en:3, zh:10, ko:4, es:15, fr:15, pt:15, sv:12) を
  言語別 testN メソッドで実行
- 適用する assertion:
  - expected_token_count_min  : トークン数下限
  - expected_contains         : 必須トークン
  - expected_not_contains     : 禁止トークン
  - expected_tokens           : 完全一致 (rule-based 言語向け)
  - expected_contains_any_tone: PUA codepoint 含有 (中国語声調)
  - expected_has_question_marker: 疑問詞マーカー (日本語 ?-suffix)

これにより Python/Rust/JS と Swift で同じ入力に対して同じ structural
output が得られることを CI でガードできる (xcframework 配信後)。

XCTest 診断の SourceKit 警告は binaryTarget 未 resolve のため
(ローカル swift build では XCTest が見えない、CI で resolve)。

* refactor(g2p): G2P 辞書 JSON の source of truth を data/ に統一 (#387)

prototype commit (00f4b95) で src/cpp/{cmudict,pinyin}*.json を
src/rust/piper-plus-g2p/data/ にコピーしてしまった重複を解消。
新しい source of truth は piper-plus-g2p crate 内 data/ に統一。

C++ コード (piper.cpp, chinese_phonemize.hpp) はファイル名のみで
参照しており、ランタイムで findDictionaryFile() がパスを解決する設計
だったため、ソースツリー上の場所変更は CMake / CI の cp パスの
更新のみで完了。

更新ファイル:
- src/cpp/cmudict_data.json         : 削除 (data/ に移動済)
- src/cpp/pinyin_single.json        : 削除 (同上)
- src/cpp/pinyin_phrases.json       : 削除 (同上)
- cmake/PiperPlusShared.cmake       : install paths を data/ 参照に
- cmake/Install.cmake                : 同上
- .github/workflows/build-piper.yml : Linux / macOS / Windows の cp 更新
- .github/workflows/docker-test.yml : test/models/ + fallback の cp 更新
- .github/workflows/test-multilingual-tts.yml : test/models/ の cp 更新

これにより:
- piper-plus-g2p crate の include_str! / include_bytes! は変更不要
  (data/ のまま、相対パスは crate 内で完結)
- C++ install ターゲットも変更前と同じパス (share/piper/dicts/) に
  正しく辞書を配置
- リポジトリ全体での重複 (~6.4MB の git tracked dup) を解消

検証:
- cargo test (all-languages,naist-jdic,bundled-dicts,ffi): 396 件 pass
- cargo build aarch64-apple-ios: 成功、piper_plus_g2p_* 5 シンボル export 確認
- grep src/cpp/{cmudict,pinyin}*.json — 残存参照なし (build-* は gitignore)

* chore(swift-g2p): 監査で検出した対応漏れに一括対応 (#387)

エージェントチームによる徹底監査の結果、以下の対応漏れを修正:

- LICENSE: THIRD_PARTY_LICENSES.md に CMU Pronouncing Dictionary
  (BSD-style) と pypinyin (MIT) の attribution セクションを追加
  (bundled-dicts で埋込む辞書の義務)
- README: README.md / README_EN.md / docs/guides/ios-integration.md
  に Swift G2P (SPM) への参照を追加。crate README を v0.4 に統一、
  ffi / bundled-dicts feature を Feature Flags 表に明記
- FFI: default_languages() 関数を新設し、有効 features と
  bundled-dicts に応じて piper_plus_g2p_create(NULL) のデフォルト
  言語を動的構築 (zh は chinese+bundled-dicts 同時有効時のみ)
- Test: tests/PiperPlusG2PTests/ConcurrencyTests.swift を追加。
  Phonemizer.@unchecked Sendable を 60 並列 phonemize / 20 並列 init /
  100 反復 deinit で実証
- Spec: docs/spec/swift-g2p-contract.toml 新設 (FFI/ABI/JSON の機械
  可読契約)。decisions.md に Resolution Summary を追加し全 D-* 項目
  の対応状況を記録
- .gitignore: xcframework / SwiftPM build artifacts (*.xcframework/,
  .build/, .swiftpm/, *.xcodeproj/, DerivedData/) を除外
- CHANGELOG: 上記の追加項目を Unreleased エントリに反映

検証: cargo test -p piper-plus-g2p \
  --features all-languages,naist-jdic,bundled-dicts,ffi で
  396 lib + 22 integration tests すべて pass。

* ci(swift-g2p): PR で Swift G2P を自動検証する CI 整備 (#387)

監査で検出した「テスト・CI が死蔵」状態を解消:

(A) release-shared-lib.yml に pull_request trigger 追加。build-g2p-ios と
assemble-g2p-xcframework のみ PR で smoke run、他 jobs は
`if: event_name != 'pull_request'` で tag push 限定を維持。文法エラー /
path ミスを PR 段階で検出可能に。

(B) .github/workflows/swift-g2p-ci.yml 新設。macOS runner で
cargo build (aarch64+x86_64 apple-darwin) → cbindgen ヘッダ生成 + 5 FFI
シンボル検証 → xcodebuild -create-xcframework で local macOS xcframework
組立 → Package.swift を CI 専用 path-based manifest に上書き →
swift test --filter PiperPlusG2PTests で PhonemizerTests /
GoldenPhonemeTests / ConcurrencyTests を実行。xcframework 未 publish
状態でも PR で Swift wrapper を実機検証できる。

(D) ffi.rs に default_languages() / register_one() のテストを 7 件追加。
lib tests 396 → 403:
- default_languages_full_feature_set: 8 言語確認
- default_languages_includes_rule_based: 規則ベース必須言語確認
- default_languages_excludes_zh_without_bundled_dicts (cfg gate)
- register_one_unsupported_language: UnsupportedLanguage 確認
- register_one_zh_without_bundled_dicts: Phonemize エラー (cfg gate)
- register_one_en/ko/ja_succeeds: 各 feature gate で正常系
- create_null_yields_default_set: NULL → default set の整合性

(E) PhonemizerTests.swift の assertion を Golden fixture と整合する
強い形に書換:
- isEmpty のみだった 8 言語テストに具体トークン照合
  (en "h", ja "k/o/n/i/a", es "o/l/a", fr "b", pt "o")
- 最小トークン数の XCTAssertGreaterThanOrEqual
- 中国語は PUA tone marker (E000-F8FF) を assertContainsPUA で検出

副作用: cargo fmt が english.rs::new_bundled() を自動整形。clippy 抑制
のため default_languages に #[allow(clippy::vec_init_then_push)] を追加
(cfg gate された push なので vec! 化できない)。

検証:
  cargo fmt -p piper-plus-g2p -- --check                   ✓
  cargo clippy -p piper-plus-g2p --all-features -D warnings ✓
  cargo test -p piper-plus-g2p --features all-languages,naist-jdic,bundled-dicts,ffi
    → 403 lib + 22 ffi tests pass
  python3 yaml.safe_load() 両 workflow                      ✓

* fix(ci): PR チェックの偽陽性 / 不足 COPY を解消 (#387)

PR #398 で 3 件の CI が失敗していた根本原因:

1. Swift G2P CI (macOS) — nm の最終 pipeline が pipefail で exit 1
   `nm -gU staticlib.a | grep | sort -u` の最終 pipeline が、staticlib に
   外部 symbol を持たない archive member を含むときに macOS nm が非ゼロを
   返し、`set -euo pipefail` で step 全体が失敗していた。出力には FFI
   symbol 5 つすべて表示されており、検証自体は成功していた。
   `nm -gU ... || true` で出力をキャプチャしてから for-loop で symbol を
   個別検証する形に変更。

2. Build piper-plus-g2p iOS ios-arm64 / ios-arm64_x86_64-simulator —
   release-shared-lib.yml の verify step も同じ形(最終 pipeline に
   `|| true` が無い)で失敗。同じ for-loop 検証へ統一。

3. test-cpp-inference (Docker) — cpp-inference/Dockerfile の builder
   stage が `src/rust/piper-plus-g2p/data/` を COPY していないため、
   cmake/Install.cmake:25 が cmudict_data.json / pinyin_*.json を install
   する際に「No such file or directory」で失敗。data ディレクトリの
   COPY を追加。

* fix(ci): nm symbol 検証で grep -q の SIGPIPE を回避 (#387)

前回の修正 (b50af91) でも CI は依然失敗:

  awk: write error on /dev/stdout
   input record number 1301, file
   source line number 1
  echo: write error: Broken pipe
  ::error::staticlib missing FFI symbol: piper_plus_g2p_create

ただし step の末尾には FFI symbol 5 つすべて出力されており、staticlib
には symbol が正しく存在していた。

根本原因: nm -gU の出力 (~10K 行) を `echo "$nm_out" | awk | grep -q`
の pipeline に流していた。grep -q はマッチ発見時点で stdout を閉じて
即 exit する。awk/echo はその後さらに書き込もうとして SIGPIPE で
非ゼロ exit。`set -euo pipefail` 下では pipeline 全体が失敗扱いとなり、
||の右辺の `exit 1` が発火。

対策: pipe を使わず bash の `case "$nm_out" in *" T _${sym}"*)` で
直接文字列マッチさせる。これなら nm 出力サイズに依存せず、`grep -q`
の早期 exit による SIGPIPE が発生しない。失敗時の診断出力 (printf
| grep) は pipe を使うが、fallback 用なので `|| true` で守る。

ローカルで 10,001 行のダミー nm 出力で 5 symbol すべて検出 / exit 0
を確認。

* fix(swift-g2p): OpaquePointer の冗長 wrap を削除 (#387)

Swift G2P CI のコンパイルが以下のエラーで失敗:

  Phonemizer.swift:28:13: error: no exact matches in call to initializer
  OpaquePointer(piper_plus_g2p_create(ptr))
                `- error: no exact matches in call to initializer

  candidate expects value of type 'UnsafeMutableRawPointer?' for
  parameter #1 (got 'OpaquePointer?')

根本原因: cbindgen が opaque forward-declared struct
(`typedef struct PiperG2pHandle PiperG2pHandle;`) を出力し、Swift の
clang importer はそれを直接 `OpaquePointer?` にブリッジする。よって
`piper_plus_g2p_create(ptr)` の戻り値は既に `OpaquePointer?` で、
`OpaquePointer(...)` で再 wrap すると型不一致になる
(OpaquePointer の init は UnsafeMutableRawPointer? しか受けない)。

修正: `OpaquePointer(...)` の wrap を削除し戻り値をそのまま `raw`
変数に代入。同じ理由で `piper_plus_g2p_free(.init(handle))` 等の
`.init(handle)` も不要 (OpaquePointer は OpaquePointer? に暗黙変換可)
なので `piper_plus_g2p_free(handle)` 等に簡素化。

修正対象:
- Phonemizer.swift:28 (init)
- Phonemizer.swift:36 (deinit)
- Phonemizer.swift:48 (phonemize)
- Phonemizer.swift:67 (availableLanguages)

* fix(swift-g2p): PUA mapping を追加し golden test を Rust と整合 (#387)

Swift G2P CI の swift test ステップで GoldenPhonemeTests が 5 言語で
10 failures。例:

  testSpanishGoldenCases: [es] perro grande — expected token 'rr' to
  be present, got: ["p", "ˈ", "e", "", "o", " ", "ɡ", "ɾ", ...]

根本原因: Rust 実装の `piper_plus_g2p_phonemize` は多文字音素トークン
(rr / cl / dʒ / eː など 99 種) を **PUA codepoint 1 文字** にエンコード
して JSON で返す。一方 fixture の `expected_contains` は人間可読な
named token (`"rr"` 文字列) で記述されている。Rust 側のクロスランタイム
テスト (`test_g2p_golden.rs::assert_case`) は `token_to_pua("rr")` で
PUA char に変換してから contains 判定を行うが、Swift 側の同等テストは
この変換を行っていなかったため fail。

修正:
- `Sources/PiperPlusG2P/PUAMap.swift` を新規追加。Rust の
  `token_map.rs::FIXED_PUA_MAP` (99 entry / PUA v2) を Swift 側に
  忠実に複製し、`tokenToPua` / `puaToToken` を public API として公開。
- `tests/PiperPlusG2PTests/GoldenPhonemeTests.swift` で
  `expected_contains` / `expected_not_contains` の検証時に
  `PUAMap.tokenToPua(needle)` で PUA 化してから contains 判定。

`expected_tokens` (exact match) は fixture 内に multi-char token が
含まれないため変換不要 (a/l/o/ˈ のみ)、改修対象外。

* fix(swift-g2p): エージェントレビュー指摘 19 件を解消 (#387)

PR #398 を 4 並列レビューエージェントで監査した結果の不整合・盲点を一括修正。
コピペで動かなかったコード片、未配信の version 参照、検出機構の抜け、契約強度
不足を中心に対処。

Critical (5):
- C1: G2PError のドキュメントが実装と不一致 (`unsupportedLanguage` 等の
  存在しないケース) を修正。guide / spec / troubleshooting テーブルを実装の
  4 ケース (initializationFailed / phonemizeReturnedNull / invalidUTF8 /
  decodeFailed) に揃え、各ケースの意味を明記。
- C2: Package.swift `g2pVersion` を `1.13.0` から `1.14.0` に bump し、
  guide / CLAUDE.md / decisions.md と整合。`g2pChecksum` が placeholder
  の意味と consumer がタグ前にビルドしようとした時の挙動をコメントで明示。
- C3: spec の cargo build 例 / CI yaml 例から `bundled-dicts,ffi` features
  が抜けていたのを補完 (decisions.md に書いてあるが実例では消えていた)。
- C4: PUA consistency gate に Swift PUAMap を追加。
  scripts/check_pua_consistency.py に parse_swift() 実装、
  pua-consistency.yml の paths に Sources/PiperPlusG2P/PUAMap.swift を追加、
  job 名を "All 7 runtime PUA tables" に更新。実行で 99 entries / version=2
  が他 6 ランタイムと一致を確認。Issue #389 の再発防止構造。
- C5: swift-g2p-ci.yml の Package.swift heredoc 上書きを Package.ci.swift
  (tracked file) 化。リリース版との drift が git diff で見える形に。
  iOS(.v15) を platforms に追加して iOS-only API の compile 検証を CI でも
  走らせる。

High (8):
- H1: crate README の C FFI 関数名 `destroy` / `version` が実装に存在しない
  → 実体 (create / phonemize / free_string / free / available_languages)
  + 各関数の所有権・戻り値を表で明示。
- H2: THIRD_PARTY_LICENSES の `new_from_path()` / `new_from_paths()` が
  実装に存在しない → 実体 (`new_with_dict` / `new(p1, p2)`) に修正。
- H3: docs/spec/swift-g2p.md:5 の Issue URL に全角カタカナ「ピ」混入で 404 →
  ASCII piper に修正。
- H4: swift-g2p-contract.toml に [compat] / [compat.breaking_change_policy]
  セクションを新設。pua_compat_version への参照、symbol 削除 / シグネチャ
  変更 / JSON envelope 追加・削除 / PUA bump / default 言語変更で何が
  壊れるかを明文化。
- H5: tests/PiperPlusG2PTests/PUAMapTests.swift を新規作成。
  fixedMap.count == 99、compatVersion == 2、PUA 範囲、コードポイント衝突、
  トークン衝突、forward/reverse round-trip、unknown lookup の 8 アサーション。
  シンボリックな count も差分で検出される。
- H6: swift-g2p-ci.yml に Thread Sanitizer ジョブを追加 (continue-on-error
  でまずは observability 優先)。`@unchecked Sendable` の主張に裏付けが
  生まれるよう ConcurrencyTests を `--sanitize=thread` で再実行。
- H7: ConcurrencyTests.testRepeatedInitDeinit にアサーションを追加。
  各 iteration の availableLanguages 検証 + 10 反復ごとに phonemize して
  pipeline の健全性確認 (元の loop は assertion ゼロでノーオペ化していた)。
- H8 (M1 兼用): testAvailableLanguagesRoundTripsRequestedSet を追加。
  Rust が新言語を返したとき Swift の Language enum が落とす silent drop
  を検出。

Medium (5):
- M3: バイナリサイズ表記を guide で 1 表に統合。xcframework.zip ダウンロード
  / app 増分 / 未 strip staticlib の 3 観点を区別して記載。各言語のサイズ
  寄与も実測値 (cmudict 3.7MB / pinyin 2.6MB / NAIST-JDIC 20MB) に揃えた。
- M4: NAIST-JDIC ライセンス全文 (BSD-3-Clause) と osdn.net 公式 URL を
  THIRD_PARTY_LICENSES.md に追加。guide でも表記揺れを解消し、
  attribution 義務の有無を整理。
- M5: english.rs / chinese.rs の OnceLock+expect が catch_unwind 後に poison
  状態で残る挙動をコメントで明示。iOS 経路 (new_bundled) は到達しないこと
  を併記して FFI consumer の安心材料に。
- M6: testEmptyTextDoesNotCrash の `try?` で例外握りつぶしを止め、
  Rust 側仕様 (空入力 → 成功 or phonemizeReturnedNull のいずれか) を
  Swift にも明示反映。
- L: integration guide の言語表に「学習済みモデルは 6 言語」「ko/sv は
  G2P 出力できても TTS モデルがない」旨の注釈を追加。

検証:
- python scripts/check_pua_consistency.py --verbose --check-version
  → 7 ランタイム全て 99 entries / version=2 一致
- cargo test -p piper-plus-g2p --features all-languages,naist-jdic,
  bundled-dicts,ffi --lib → 403 passed / 0 failed
- yaml.safe_load で 2 ワークフロー syntax ok

* feat(swift-g2p): macOS slice + diagnostic G2PError + HelloG2P サンプル (#387)

ユーザー視点レビュー指摘 (Concern B / C / E) を本ブランチで解消。

- macOS slice 追加 (Concern E):
  build-g2p-ios → build-g2p-apple に rename し macos-arm64_x86_64
  matrix エントリ追加。assemble-g2p-xcframework が 3 slice を扱うよう
  拡張。Package.swift に .macOS(.v13) を追加し、binaryTarget URL を
  libpiper_plus_g2p-ios- → libpiper_plus_g2p-apple- へ rename
  (macOS slice 同梱を反映)。
- G2PError 診断情報 (Concern C):
  initializationFailed(requestedLanguages:) /
  phonemizeReturnedNull(language:) に associated value を追加。
  multi-language caller が call site を辿らずに失敗箇所を特定可能に。
  既存テスト 1 件更新 + 新規テスト 2 件追加。
- examples/swift-g2p/HelloG2P/ (Concern B):
  macOS CLI demo (Package.swift + main.swift + README)。swift run で
  8 言語の動作確認が可能。tag publish 前は path: モードへの切替手順を
  README に明記。

ドキュメント整合: integration guide / spec / contract / decisions log /
CLAUDE.md を新 artifact 名・slice 構成・新エラー形式に追従。

未対応 Concern (本ブランチ範囲外): A (tag publish)、D (JSON decode 最適化)、
F (App Clip lite slice)、G (review approval)。詳細は
docs/spec/swift-g2p-decisions.md の Post-merge follow-ups 節を参照。

* fix(swift-g2p): fixture v2 対応 + Rust 英語 OOV 既知ケース skip (#387)

dev リベースで取り込んだ #392 (fixture schema v1→v2) と #400 (Kotlin G2P が
新規英語ケース追加) によって Swift CI 2 件失敗を解消。

# 修正内容
- tests/fixtures/g2p/phoneme_test_cases.json: regenerate_test_fixture.py で
  pua_map セクションを再生成 (drift 検出 CI が通る形式に統一、ensure_ascii=True
  で IPA 文字を \\u エスケープ表記に)。test_cases 自体は不変。
- GoldenPhonemeTests.swift: testFixtureLoads の expected version を 1 → 2 に
  更新。runGoldenCases で Rust crate の英語実装が letter-spelling fallback
  未実装である既知ギャップ (acronym/all-caps/mixed-case/repeated/Latin-1
  accent/quote-only) を skip リスト化。Rust 英語実装の改善は別 PR で追跡。

* docs(swift-g2p): 実装完了後に陳腐化した設計時 doc を整理 (#387)

エージェントチームの監査で「役目終了」と判定された箇所を削除/削減。
純減 334 行 (89 insertions, 423 deletions)。

# 削除
- docs/spec/swift-g2p-decisions.md (387 行)
  - 自身が冒頭で Resolved 宣言済み、末尾「全 ID 解決後は役割を
    swift-g2p.md / swift-g2p-integration.md に引き継ぐ」と明記
  - 全 D-1〜D-15 と Post-merge follow-ups (2026-05-07) で完了済み
  - 参照: CHANGELOG.md のリンク行削除、swift-g2p-ci.yml のコメント差し替え

# 削減
- docs/spec/swift-g2p.md (564 → 207 行)
  - Status: Proposed (Draft) → Implemented (v1.14.0+)
  - §3.5 Swift API: コード塊掲載 → Sources/PiperPlusG2P/ への参照テーブル化
  - §4 実装スコープ: Cargo.toml/cbindgen.toml/CI YAML/Package.swift 全文
    → 「実体は実コード/CI/manifest が真」のサマリ表
  - §4.7 リリース手順: Package.swift 冒頭コメントの正本へのリンク化
  - §4.9 テスト一覧: tests/PiperPlusG2PTests/ への参照のみ
  - 残存: §1 ユースケース、§2 採用方針、§3 配布構成、§5 採用しなかった案、
    §6 拡張可能性、§7 関連リンク
- examples/swift-g2p/README.md (101 → 64 行)
  - "Quick Start" を 3 行に圧縮、ガイドへのリンク化
  - "Integrating into your own project" を削除 (ガイドへ統合)
  - "Local Development" / "Why a separate example directory?" は unique
    価値ありで保持

# 保持
- docs/spec/swift-g2p-contract.toml (FFI/ABI 契約として現役)
- docs/guides/swift-g2p-integration.md (consumer 向けガイド本体)
- examples/swift-g2p/HelloG2P/ (実行可能サンプル)
- src/rust/piper-plus-g2p/README.md (crate-level doc, Swift 言及は最小限)

* docs(spec): 孤立した pua-codegen-design.md を削除

エージェント調査で参照ゼロ + 自己宣言「Design proposal — not yet
implemented」と判定。Strategy C (現行手動同期 + CI gate) が
`scripts/check_pua_consistency.py` + `/check-pua` skill +
pre-commit hook で実運用成立済みのため、Strategy A/B 提案文書は陳腐化。

他 doc/コード/CI からの参照ゼロを grep で確認済 (削除によるリンク
切れなし)。

# 残った削減推奨 (本 PR 範囲外、別 PR 推奨)
- zh-en-loanword-runtime-rollout.md (1958 行、Issue #384 完了)
- kotlin-g2p-design.md (540 行、Issue #388 PR #400 完了)
- kotlin-g2p-requirements.md (650 行、同上)
これらは本ブランチ (Swift G2P #387) と無関係の領域で、git blame と
PR レビューの clean さを優先して別 PR で対応する。

* docs(spec): 実装完了済み 3 spec の陳腐化セクションを削除

エージェントチーム調査で「実装完了で役目終了」と判定された箇所を削除。
本ブランチ範囲外領域だがユーザー判断で同 PR に同梱。
純減 325 行 (30 insertions, 355 deletions)。

# zh-en-loanword-runtime-rollout.md (1958 → 1739)
Issue #384 / PR #399 で全 7 ランタイム同期実装完了済み。
- Status: Draft → Implemented
- §4.1 ランタイム別タスク表 (R/G/C/W/P) 削除 (git log と PR 履歴に集約)
- §5 1 ブランチ戦略のリスクと対策 削除
- §6 ロールアウト計画 (Day 1-14 + 受け入れ基準) 削除
- §8.11 リリース戦略 (実装後不要) 削除
- §8.15 CI ジョブ整合性 (実装後不要) 削除
- §9 改訂履歴 (Draft v1-v9) 削除

残置: §1 背景、§2 各ランタイム現状、§3 共通の実装パターン、§4 横断的
な設計課題、§5 関連ドキュメント、§8 深堀り (規範・運用 SOP・将来拡張)。

# kotlin-g2p-design.md (540 → 502)
Issue #388 / PR #400 で実装完了済み。
- Status: Implemented を冒頭追加
- L8 「実装着手前に更新」警告 削除 (前提矛盾の自己警告は陳腐化済)
- §6 実装ロードマップ (Day-by-Day Phase 1-7) 削除
- §9 受け入れ基準 削除
- §12 改訂履歴 削除
- §7-§11 のセクション番号を §6-§9 に詰め直し

残置: §1 ゴール、§2 OSS コンセプト分析、§3 既存資産、§4 アプローチ
比較、§5 推奨アプローチ、§6 自動化戦略、§7 リスクと対策、§8 関連
ドキュメント、§9 競合事例。

# kotlin-g2p-requirements.md (650 → 582)
- Status: Implemented を冒頭追加
- §13 リスクと緩和策 削除 (実装後に発火/不発が確定)
- §14 受け入れ基準 削除 (PR #400 で全 AC 実現済み)
- §15 オープンクエスチョン 削除 (Q-1〜Q-10 全解決済み)
- §16 用語集再掲 ("§2 を参照" のみ) 削除
- §17 関連ドキュメント を §13 に番号繰り上げ
- §18 改訂履歴 削除

残置: §0〜§12 (要件定義としての規範性が継続)、§13 関連ドキュメント。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants