@seanyao/roll 3.609.2 → 3.610.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +44 -1
  2. package/README.md +5 -6
  3. package/dist/roll.mjs +16146 -16186
  4. package/package.json +3 -2
  5. package/skills/README.md +14 -1
  6. package/skills/docs/skill-authoring.md +66 -0
  7. package/skills/reports/skill-audit-summary.md +53 -0
  8. package/skills/roll-.changelog/SKILL.md +25 -443
  9. package/skills/roll-.changelog/references/full-contract.md +462 -0
  10. package/skills/roll-.clarify/SKILL.md +6 -4
  11. package/skills/roll-.dream/SKILL.md +26 -353
  12. package/skills/roll-.dream/references/full-contract.md +365 -0
  13. package/skills/roll-.echo/SKILL.md +6 -4
  14. package/skills/roll-.qa/SKILL.md +25 -236
  15. package/skills/roll-.qa/references/full-contract.md +256 -0
  16. package/skills/roll-.review/SKILL.md +6 -2
  17. package/skills/roll-brief/SKILL.md +6 -8
  18. package/skills/roll-build/SKILL.md +28 -864
  19. package/skills/roll-build/references/full-contract.md +883 -0
  20. package/skills/roll-debug/SKILL.md +26 -585
  21. package/skills/roll-debug/references/full-contract.md +607 -0
  22. package/skills/roll-design/SKILL.md +28 -903
  23. package/skills/roll-design/references/full-contract.md +923 -0
  24. package/skills/roll-doc/SKILL.md +25 -574
  25. package/skills/roll-doc/references/full-contract.md +594 -0
  26. package/skills/roll-doctor/SKILL.md +21 -2
  27. package/skills/roll-fix/SKILL.md +28 -621
  28. package/skills/roll-fix/references/full-contract.md +640 -0
  29. package/skills/roll-idea/SKILL.md +6 -2
  30. package/skills/roll-loop/SKILL.md +27 -543
  31. package/skills/roll-loop/references/full-contract.md +555 -0
  32. package/skills/roll-notes/SKILL.md +6 -2
  33. package/skills/roll-onboard/SKILL.md +6 -2
  34. package/skills/roll-peer/SKILL.md +27 -316
  35. package/skills/roll-peer/references/full-contract.md +329 -0
  36. package/skills/roll-propose/SKILL.md +6 -8
  37. package/skills/roll-review-pr/SKILL.md +6 -2
  38. package/skills/roll-sentinel/SKILL.md +26 -344
  39. package/skills/roll-sentinel/references/full-contract.md +363 -0
  40. package/skills/roll-spar/SKILL.md +27 -269
  41. package/skills/roll-spar/references/full-contract.md +288 -0
  42. package/skills/route-cases/skills.json +235 -0
  43. package/skills/scripts/audit-skills.mjs +272 -0
  44. package/skills/scripts/test-audit-skills.mjs +39 -0
  45. package/skills/tests/fixtures/skill-audit/block-skill/SKILL.md +12 -0
  46. package/skills/tests/fixtures/skill-audit/minimal-skill/SKILL.md +8 -0
  47. package/skills/tests/fixtures/skill-audit/quoted-skill/SKILL.md +10 -0
  48. package/skills/tests/fixtures/skill-audit/route-cases.json +21 -0
  49. package/skills/tests/fixtures/skill-audit/spoke-skill/SKILL.md +12 -0
  50. package/skills/tests/fixtures/skill-audit/spoke-skill/references/runbook.md +3 -0
  51. package/bin/roll +0 -15361
  52. package/lib/backfill-pi-usage.py +0 -243
  53. package/lib/changelog_audit.py +0 -149
  54. package/lib/changelog_generate.py +0 -470
  55. package/lib/consistency_check.py +0 -409
  56. package/lib/context_feed_budget.sh +0 -194
  57. package/lib/github_sync.py +0 -876
  58. package/lib/i18n/slides.sh +0 -3
  59. package/lib/i18n/slides_build.sh +0 -38
  60. package/lib/i18n/slides_delete.sh +0 -19
  61. package/lib/i18n/slides_list.sh +0 -14
  62. package/lib/i18n/slides_logs.sh +0 -12
  63. package/lib/i18n/slides_new.sh +0 -15
  64. package/lib/i18n/slides_preview.sh +0 -14
  65. package/lib/i18n/slides_templates.sh +0 -7
  66. package/lib/i18n.sh +0 -211
  67. package/lib/loop-exit-summary.py +0 -393
  68. package/lib/loop-fmt.py +0 -589
  69. package/lib/loop_pick_agent.py +0 -316
  70. package/lib/loop_result_eval.py +0 -469
  71. package/lib/loop_unstick.py +0 -180
  72. package/lib/model_prices.py +0 -194
  73. package/lib/prices_fetcher.py +0 -534
  74. package/lib/roll-backlog.py +0 -225
  75. package/lib/roll-brief.py +0 -286
  76. package/lib/roll-help.py +0 -158
  77. package/lib/roll-home.py +0 -556
  78. package/lib/roll-init.py +0 -156
  79. package/lib/roll-loop-status.py +0 -1691
  80. package/lib/roll-loop-story.py +0 -191
  81. package/lib/roll-peer.py +0 -252
  82. package/lib/roll-setup.py +0 -102
  83. package/lib/roll-status.py +0 -367
  84. package/lib/roll_git.py +0 -41
  85. package/lib/roll_render.py +0 -414
  86. package/lib/slides/components/README.md +0 -123
  87. package/lib/slides/components/cards-2.html +0 -9
  88. package/lib/slides/components/cards-3.html +0 -9
  89. package/lib/slides/components/cards-4.html +0 -9
  90. package/lib/slides/components/compare.html +0 -22
  91. package/lib/slides/components/highlight.html +0 -9
  92. package/lib/slides/components/pipeline.html +0 -12
  93. package/lib/slides/components/plain.html +0 -7
  94. package/lib/slides/components/quote.html +0 -4
  95. package/lib/slides/components/timeline.html +0 -9
  96. package/lib/slides/templates/introduction-v3.html +0 -571
  97. package/lib/slides/templates/pitch.html +0 -0
  98. package/lib/slides-render.py +0 -778
  99. package/lib/slides-validate.py +0 -357
  100. package/lib/test_quality_gate.py +0 -143
  101. package/skills/roll-deck/SKILL.md +0 -296
  102. /package/skills/roll-debug/{injectable-bb.js → assets/injectable-bb.js} +0 -0
  103. /package/skills/roll-design/{ENGINEERING_CHECKLIST.md → references/engineering-checklist.md} +0 -0
@@ -1,3 +0,0 @@
1
-
2
- _i18n_set en slides.unknown_subcommand "Unknown subcommand: %s"
3
- _i18n_set zh slides.unknown_subcommand "未知子命令:%s"
@@ -1,38 +0,0 @@
1
-
2
- _i18n_set en slides_build.unknown_option_1 "Unknown option: %s"
3
- _i18n_set zh slides_build.unknown_option_1 "未知选项: %s"
4
- _i18n_set en slides_build.unexpected_argument_1 "Unexpected argument: %s"
5
- _i18n_set zh slides_build.unexpected_argument_1 "多余参数: %s"
6
- _i18n_set en slides_build.slides_toolchain_missing_re_run_roll "Slides toolchain missing — re-run 'roll setup'"
7
- _i18n_set zh slides_build.slides_toolchain_missing_re_run_roll "渲染工具缺失,请运行 roll setup"
8
- _i18n_set en slides_build.deck_md " deck.md"
9
- _i18n_set zh slides_build.deck_md "校验失败,请先修复上方提示再重试。"
10
- _i18n_set en slides_build.template_not_found "Template not found: %s"
11
- _i18n_set zh slides_build.template_not_found "未找到模板:%s"
12
- _i18n_set en slides_build.render_failed_for "Render failed for %s"
13
- _i18n_set zh slides_build.render_failed_for "渲染失败:%s"
14
- _i18n_set en slides_build.rendered "Rendered → %s"
15
- _i18n_set zh slides_build.rendered "渲染完成 → %s"
16
-
17
- # US-DECK-012: build failure recovery paths
18
- _i18n_set en slides_build.validation_failed_for "Validation failed for %s"
19
- _i18n_set zh slides_build.validation_failed_for "校验失败:%s"
20
- _i18n_set en slides_build.hint_fix_and_rerun "Hint: fix the issues above in %s and re-run 'roll slides build %s'"
21
- _i18n_set zh slides_build.hint_fix_and_rerun "提示:请按上方提示修复 %s,然后重新运行 'roll slides build %s'"
22
- _i18n_set en slides_build.available_templates "Available templates:"
23
- _i18n_set zh slides_build.available_templates "可用模板:"
24
- _i18n_set en slides_build.templates_list_hint "Hint: 'roll slides templates' lists all installed templates"
25
- _i18n_set zh slides_build.templates_list_hint "提示:'roll slides templates' 列出所有已安装模板"
26
- _i18n_set en slides_build.renderer_crashed_for "Renderer crashed for %s"
27
- _i18n_set zh slides_build.renderer_crashed_for "渲染器崩溃:%s"
28
- _i18n_set en slides_build.see_full_error_logs "See full error: roll slides logs %s"
29
- _i18n_set zh slides_build.see_full_error_logs "查看完整错误:roll slides logs %s"
30
- _i18n_set en slides_build.last_5_lines_of_renderer_output "Last 5 lines of renderer output:"
31
- _i18n_set zh slides_build.last_5_lines_of_renderer_output "渲染器最后 5 行:"
32
-
33
- _i18n_set en slides_build.usage_roll_slides_build_slug_no "Usage: roll slides build <slug> [--no-open]"
34
- _i18n_set zh slides_build.usage_roll_slides_build_slug_no "用法: roll slides build <slug> [--no-open]"
35
- _i18n_set en slides_build.en_deck "[EN: 未找到 deck 文件:%s...]"
36
- _i18n_set zh slides_build.en_deck " 未找到 deck 文件:%s"
37
- _i18n_set en slides_build.en_roll_slides_new "[EN: 提示:先运行 'roll slides new \"<主题>\"' 生成新的幻灯片。...]"
38
- _i18n_set zh slides_build.en_roll_slides_new " 提示:先运行 'roll slides new \"<主题>\"' 生成新的幻灯片。"
@@ -1,19 +0,0 @@
1
-
2
- _i18n_set en slides_delete.unknown_option_1 "Unknown option: %s"
3
- _i18n_set zh slides_delete.unknown_option_1 "未知选项: %s"
4
- _i18n_set en slides_delete.unexpected_argument_1 "Unexpected argument: %s"
5
- _i18n_set zh slides_delete.unexpected_argument_1 "多余参数: %s"
6
- _i18n_set en slides_delete.deck_not_found "Deck not found: %s"
7
- _i18n_set zh slides_delete.deck_not_found "未找到幻灯片:%s"
8
- _i18n_set en slides_delete.non_interactive_terminal_must_use_force "Non-interactive terminal: must use --force to delete"
9
- _i18n_set zh slides_delete.non_interactive_terminal_must_use_force "非交互终端:需使用 --force 参数"
10
- _i18n_set en slides_delete.cancelled "Cancelled"
11
- _i18n_set zh slides_delete.cancelled "已取消"
12
- _i18n_set en slides_delete.deleted "Deleted %s"
13
- _i18n_set zh slides_delete.deleted "已删除 %s"
14
-
15
- _i18n_set en slides_delete.usage_roll_slides_delete_slug_force "Usage: roll slides delete <slug> [--force]"
16
- _i18n_set zh slides_delete.usage_roll_slides_delete_slug_force "用法: roll slides delete <slug> [--force]"
17
-
18
- _i18n_set en slides_delete.prompt 'Delete deck "%s"? (y/N)'
19
- _i18n_set zh slides_delete.prompt '删除幻灯片 "%s"?(y/N)'
@@ -1,14 +0,0 @@
1
-
2
- _i18n_set en slides_list.unknown_option_1 "Unknown option: %s"
3
- _i18n_set zh slides_list.unknown_option_1 "未知选项: %s"
4
- _i18n_set en slides_list.unexpected_argument_1 "Unexpected argument: %s"
5
- _i18n_set zh slides_list.unexpected_argument_1 "多余参数: %s"
6
- _i18n_set en slides_list.no_decks_found_under_roll_slides "No decks found under .roll/slides/"
7
- _i18n_set zh slides_list.no_decks_found_under_roll_slides "无幻灯片"
8
- _i18n_set en slides_list.no_decks_found_under_roll_slides_2 "No decks found under .roll/slides/"
9
- _i18n_set zh slides_list.no_decks_found_under_roll_slides_2 "无幻灯片"
10
-
11
- _i18n_set en slides_list.en_roll_slides_new "[EN: 提示:运行 'roll slides new \"<主题>\"' 创建第一个幻灯片。...]"
12
- _i18n_set zh slides_list.en_roll_slides_new " 提示:运行 'roll slides new \"<主题>\"' 创建第一个幻灯片。"
13
- _i18n_set en slides_list.en_roll_slides_new_2 "[EN: 提示:运行 'roll slides new \"<主题>\"' 创建第一个幻灯片。...]"
14
- _i18n_set zh slides_list.en_roll_slides_new_2 " 提示:运行 'roll slides new \"<主题>\"' 创建第一个幻灯片。"
@@ -1,12 +0,0 @@
1
-
2
- _i18n_set en slides_logs.unknown_option_1 "Unknown option: %s"
3
- _i18n_set zh slides_logs.unknown_option_1 "未知选项: %s"
4
- _i18n_set en slides_logs.unexpected_argument_1 "Unexpected argument: %s"
5
- _i18n_set zh slides_logs.unexpected_argument_1 "多余参数: %s"
6
- _i18n_set en slides_logs.deck_not_found "Deck not found: %s"
7
- _i18n_set zh slides_logs.deck_not_found "未找到幻灯片:%s"
8
- _i18n_set en slides_logs.no_failure_records_for "No failure records for %s"
9
- _i18n_set zh slides_logs.no_failure_records_for "该幻灯片没有失败记录"
10
-
11
- _i18n_set en slides_logs.usage_roll_slides_logs_slug "Usage: roll slides logs <slug>"
12
- _i18n_set zh slides_logs.usage_roll_slides_logs_slug "用法: roll slides logs <slug>"
@@ -1,15 +0,0 @@
1
-
2
- _i18n_set en slides_new.unknown_option_1 "Unknown option: %s"
3
- _i18n_set zh slides_new.unknown_option_1 "未知选项: %s"
4
- _i18n_set en slides_new.unexpected_argument_1 "Unexpected argument: %s"
5
- _i18n_set zh slides_new.unexpected_argument_1 "多余参数: %s"
6
-
7
- _i18n_set en slides_new.en_roll_slides_new_template "[EN: 用法:roll slides new \"<主题>\" [--template <模板名>] [...]"
8
- _i18n_set zh slides_new.en_roll_slides_new_template " 用法:roll slides new \"<主题>\" [--template <模板名>] [--quiet] [--no-build]"
9
- _i18n_set en slides_new.en_slug "[EN: 无法从主题派生 slug:%s...]"
10
- _i18n_set zh slides_new.en_slug " 无法从主题派生 slug:%s"
11
- _i18n_set en slides_new.en_roll_slides_build "[EN:下一步:roll slides build %s...]"
12
- _i18n_set zh slides_new.en_roll_slides_build "下一步:roll slides build %s"
13
-
14
- _i18n_set en slides_new.template_requires_value "--template requires a value"
15
- _i18n_set zh slides_new.template_requires_value "--template 需要一个值"
@@ -1,14 +0,0 @@
1
-
2
- _i18n_set en slides_preview.unknown_option_1 "Unknown option: %s"
3
- _i18n_set zh slides_preview.unknown_option_1 "未知选项: %s"
4
- _i18n_set en slides_preview.unexpected_argument_1 "Unexpected argument: %s"
5
- _i18n_set zh slides_preview.unexpected_argument_1 "多余参数: %s"
6
- _i18n_set en slides_preview.preview "Preview → %s"
7
- _i18n_set zh slides_preview.preview "打开预览 → %s"
8
-
9
- _i18n_set en slides_preview.usage_roll_slides_preview_slug_no "Usage: roll slides preview <slug> [--no-open]"
10
- _i18n_set zh slides_preview.usage_roll_slides_preview_slug_no "用法: roll slides preview <slug> [--no-open]"
11
- _i18n_set en slides_preview.en_html "[EN: 未找到已渲染的 HTML:%s...]"
12
- _i18n_set zh slides_preview.en_html " 未找到已渲染的 HTML:%s"
13
- _i18n_set en slides_preview.en_roll_slides_build "[EN: 提示:先运行 'roll slides build %s' 渲染幻灯片。...]"
14
- _i18n_set zh slides_preview.en_roll_slides_build " 提示:先运行 'roll slides build %s' 渲染幻灯片。"
@@ -1,7 +0,0 @@
1
-
2
- _i18n_set en slides_templates.unknown_option_1 "Unknown option: %s"
3
- _i18n_set zh slides_templates.unknown_option_1 "未知选项: %s"
4
- _i18n_set en slides_templates.unexpected_argument_1 "Unexpected argument: %s"
5
- _i18n_set zh slides_templates.unexpected_argument_1 "多余参数: %s"
6
- _i18n_set en slides_templates.no_templates_found "No templates found"
7
- _i18n_set zh slides_templates.no_templates_found "无可用模板"
package/lib/i18n.sh DELETED
@@ -1,211 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Roll i18n engine — US-I18N-001.
3
- #
4
- # Provides:
5
- # _i18n_resolve_lang — resolve user's language per precedence
6
- # msg <key> [args...] — look up message catalog with EN fallback
7
- # _i18n_set <lang> <key> <value> — fill the catalog (used by modules)
8
- #
9
- # Storage is bash 3.2-compatible (macOS default ships 3.2): each catalog entry
10
- # is held in a plain variable named `MSG_<LANG>_<key>`, looked up via indirect
11
- # reference. Bash 4 assoc arrays would be cleaner but break macOS default bash
12
- # (see AGENTS.md §4: single bash script, no runtime).
13
- #
14
- # Precedence: ROLL_LANG env > ~/.roll/config.yaml `lang` > LC_ALL > LANG
15
- # > (macOS) AppleLanguages > 'en'.
16
- # Decision: value starting with `zh` → "zh", everything else → "en".
17
-
18
- # Fill the catalog. Modules call this at source-time:
19
- # _i18n_set en hello "Hello, %s!"
20
- # _i18n_set zh hello "你好,%s!"
21
- # No subshell forks: lang uppercasing uses a case statement (bash 3.2-safe;
22
- # ${var^^} requires bash 4+), key sanitization uses inline param-expansion.
23
- _i18n_set() {
24
- local lang="$1" key="$2" val="$3"
25
- local upper safe varname
26
- case "$lang" in
27
- en|EN) upper=EN ;;
28
- zh|ZH) upper=ZH ;;
29
- *) upper="$(printf '%s' "$lang" | tr '[:lower:]' '[:upper:]')" ;;
30
- esac
31
- safe="${key//[^A-Za-z0-9_]/_}" # inline param-expansion — no subshell fork
32
- varname="MSG_${upper}_${safe}"
33
- printf -v "$varname" '%s' "$val"
34
- export "$varname"
35
- }
36
-
37
- # Decide "zh" or "en" from a free-form locale string.
38
- _i18n_classify() {
39
- case "${1:-}" in
40
- zh*) echo zh ;;
41
- *) echo en ;;
42
- esac
43
- }
44
-
45
- # Resolve the active language. Caches in ROLL_LANG_RESOLVED so later calls are
46
- # free.
47
- _i18n_resolve_lang() {
48
- if [[ -n "${ROLL_LANG_RESOLVED:-}" ]]; then
49
- echo "$ROLL_LANG_RESOLVED"
50
- return
51
- fi
52
-
53
- local lang=""
54
-
55
- if [[ -n "${ROLL_LANG:-}" ]]; then
56
- lang=$(_i18n_classify "$ROLL_LANG")
57
- fi
58
-
59
- if [[ -z "$lang" && -n "${ROLL_CONFIG:-}" && -f "${ROLL_CONFIG}" ]]; then
60
- local cfg
61
- cfg=$(grep -E '^lang:' "$ROLL_CONFIG" 2>/dev/null | head -1 \
62
- | sed 's/^lang:[[:space:]]*//' \
63
- | sed 's/[[:space:]]*#.*$//' \
64
- | sed 's/[[:space:]]*$//')
65
- [[ -n "$cfg" ]] && lang=$(_i18n_classify "$cfg")
66
- fi
67
-
68
- if [[ -z "$lang" && -n "${LC_ALL:-}" ]]; then
69
- lang=$(_i18n_classify "$LC_ALL")
70
- fi
71
-
72
- if [[ -z "$lang" && -n "${LANG:-}" ]]; then
73
- lang=$(_i18n_classify "$LANG")
74
- fi
75
-
76
- if [[ -z "$lang" ]] && command -v defaults >/dev/null 2>&1; then
77
- local apple
78
- apple=$(defaults read -g AppleLanguages 2>/dev/null | head -2 | tail -1 \
79
- | tr -d ' ",()' | head -1 || true)
80
- [[ -n "$apple" ]] && lang=$(_i18n_classify "$apple")
81
- fi
82
-
83
- [[ -z "$lang" ]] && lang="en"
84
-
85
- ROLL_LANG_RESOLVED="$lang"
86
- echo "$lang"
87
- }
88
-
89
- # Look up message catalog entry. Falls back to EN, then to the key itself so
90
- # missing translations stay visible without crashing the caller.
91
- msg() {
92
- local key="$1"; shift || true
93
- local lang safe
94
- # FIX: avoid subshell forks — check cached value first, inline key sanitize
95
- if [[ -n "${ROLL_LANG_RESOLVED:-}" ]]; then
96
- lang="$ROLL_LANG_RESOLVED"
97
- else
98
- lang=$(_i18n_resolve_lang)
99
- fi
100
- safe="${key//[^A-Za-z0-9_]/_}" # FIX: inline — no subshell fork
101
-
102
- local zh_var="MSG_ZH_${safe}"
103
- local en_var="MSG_EN_${safe}"
104
- local tmpl=""
105
-
106
- if [[ "$lang" == "zh" && -n "${!zh_var:-}" ]]; then
107
- tmpl="${!zh_var}"
108
- elif [[ -n "${!en_var:-}" ]]; then
109
- tmpl="${!en_var}"
110
- else
111
- tmpl="$key"
112
- fi
113
-
114
- # shellcheck disable=SC2059 — template comes from our own catalog
115
- printf "$tmpl" "$@"
116
- echo
117
- }
118
-
119
- # Look up message catalog entry with an explicit language, bypassing ROLL_LANG env.
120
- # Usage: msg_lang <lang> <key> [printf-args...]
121
- # Useful when the caller tracks language independently of the process env (e.g.
122
- # _loop_schedule_desc passes an explicit lang= parameter).
123
- msg_lang() {
124
- local lang="$1" key="$2"; shift 2 || true
125
- local upper safe varname tmpl
126
- case "$lang" in
127
- en|EN) upper=EN ;;
128
- zh|ZH) upper=ZH ;;
129
- *) upper="$(printf '%s' "$lang" | tr '[:lower:]' '[:upper:]')" ;;
130
- esac
131
- safe="${key//[^A-Za-z0-9_]/_}"
132
- varname="MSG_${upper}_${safe}"
133
- tmpl="${!varname:-}"
134
- if [[ -z "$tmpl" ]]; then
135
- varname="MSG_EN_${safe}"
136
- tmpl="${!varname:-$key}"
137
- fi
138
- # shellcheck disable=SC2059 — template comes from our own catalog
139
- printf "$tmpl" "$@"
140
- echo
141
- }
142
-
143
- # ── CJK display-width helpers (US-I18N-006) ──────────────────────────────
144
- # Compute visual display width of a string (CJK fullwidth = 2 cells,
145
- # ASCII = 1 cell). Delegates to python3 which is already a dependency.
146
- _strw() {
147
- python3 -c "
148
- import sys
149
- from unicodedata import east_asian_width
150
- s = sys.argv[1]
151
- w = sum(2 if east_asian_width(c) in ('F','W') else 1 for c in s)
152
- print(w)
153
- " "$1"
154
- }
155
-
156
- # Left-pad a string to a target visual display width (CJK-aware).
157
- _pad_l() {
158
- local s="$1" target="$2"
159
- local sw
160
- sw=$(_strw "$s")
161
- local pad=$((target - sw))
162
- if ((pad <= 0)); then
163
- printf '%s' "$s"
164
- else
165
- printf '%s%*s' "$s" "$pad" ''
166
- fi
167
- }
168
-
169
- # Right-pad a string to a target visual display width (CJK-aware).
170
- _pad_r() {
171
- local s="$1" target="$2"
172
- local sw
173
- sw=$(_strw "$s")
174
- local pad=$((target - sw))
175
- if ((pad <= 0)); then
176
- printf '%s' "$s"
177
- else
178
- printf '%*s%s' "$pad" '' "$s"
179
- fi
180
- }
181
-
182
- # ── Load per-command message catalogs (US-I18N-002) ──
183
- # Source all lib/i18n/*.sh files (skip self). Called once at bin/roll startup.
184
- _i18n_load_catalogs() {
185
- # FIX: guard — skip re-load when catalogs already loaded in this shell.
186
- # Each bats test sources bin/roll in its own subshell so the guard resets
187
- # automatically; but within a single shell (e.g. integration tests) this
188
- # prevents 2.6s of catalog I/O on every subsequent source.
189
- [[ -n "${_I18N_CATALOGS_LOADED:-}" ]] && return 0
190
- local i18n_dir
191
- i18n_dir="$(dirname "${BASH_SOURCE[0]:-$0}")/i18n"
192
- if [[ -d "$i18n_dir" ]]; then
193
- local f
194
- # Load per-command catalogs
195
- for f in "$i18n_dir"/*.sh; do
196
- [[ -f "$f" ]] || continue
197
- # shellcheck source=/dev/null
198
- source "$f"
199
- done
200
- # Load skill catalogs (US-I18N-003)
201
- if [[ -d "$i18n_dir/skills" ]]; then
202
- for f in "$i18n_dir/skills"/*.sh; do
203
- [[ -f "$f" ]] || continue
204
- # shellcheck source=/dev/null
205
- source "$f"
206
- done
207
- fi
208
- fi
209
- _I18N_CATALOGS_LOADED=1
210
- }
211
- _i18n_load_catalogs