@ccoalm/ccl-skills 0.18.6 → 0.18.8

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 (48) hide show
  1. package/README.md +69 -2
  2. package/dist/assets/marketplace/plugins/ccl-skills/agent-context/session-policy.md +2 -2
  3. package/dist/assets/marketplace/plugins/ccl-skills/agent-context/session-start.md +6 -6
  4. package/dist/assets/marketplace/plugins/ccl-skills/hooks/host-input.py +39 -4
  5. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_proposed_next.py +66 -0
  6. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/development-completion.md +3 -2
  7. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +30 -2
  8. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/SKILL.md +5 -5
  9. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/design-review-gate-mechanics.md +1 -1
  10. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/pre-final-continuation-gate.md +2 -2
  11. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/review-reception.md +1 -1
  12. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/SKILL.md +1 -1
  13. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/eval-routing.md +4 -0
  14. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +28 -0
  15. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-ccl-skills.sh +2 -0
  16. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-size-budget.sh +2 -0
  17. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-sync-pointers.sh +2 -0
  18. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-routing-bank.rb +11 -2
  19. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_body_compliance_grading.sh +79 -0
  20. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +4 -2
  21. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_register_pending_exclusion.sh +2 -0
  22. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +4 -0
  23. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_route_drift.sh +2 -0
  24. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_size_budget.sh +4 -2
  25. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_source_register_lifecycle.sh +9 -1
  26. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_sync_pointers.sh +2 -0
  27. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_grader_diagnostics.sh +2 -0
  28. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_resolution.sh +32 -0
  29. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_surface_binding.sh +2 -0
  30. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_prose_target.sh +2 -0
  31. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_dateless_host.sh +3 -1
  32. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_verdict_differential.sh +2 -0
  33. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_round_attribution.sh +2 -0
  34. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_self_adjudication.sh +2 -0
  35. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_source_refuted.sh +2 -0
  36. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_locale_independent_gates.sh +264 -0
  37. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_resolution.sh +2 -0
  38. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_wiring.sh +2 -0
  39. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_uiux_delivery_contract.sh +2 -0
  40. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_uiux_loading_budget.sh +2 -0
  41. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate-skill.sh +2 -0
  42. package/dist/assets/release.json +60 -45
  43. package/dist/auto-update.d.ts +26 -0
  44. package/dist/auto-update.js +551 -0
  45. package/dist/cli.d.ts +2 -0
  46. package/dist/cli.js +29 -3
  47. package/dist/opencode-adapter.js +1 -1
  48. package/package.json +1 -1
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env bash
2
+ # Regression: the repository gates must not depend on the caller's locale.
3
+ # Under a POSIX/unset locale Ruby defaults to US-ASCII, so a gate that reads the
4
+ # UTF-8 skill text (or runs a `ruby -e` program with UTF-8 literals) crashed with
5
+ # "invalid byte sequence in US-ASCII" before checking anything, while CI (which
6
+ # runs C.UTF-8) stayed green. Each ruby-invoking script pins `RUBYOPT=-Ku`.
7
+ # (1) every tracked live shell script (skills/, hooks/, scripts/) and every
8
+ # Makefile target that invokes ruby carries the pin, and no other literal RUBYOPT mention drops it; the
9
+ # classifier is held by pinned bypass and near-miss rows; frozen evidence
10
+ # under specs/ and eval/ is exempt;
11
+ # (2) validate-skill.sh passes on a UTF-8 fixture under LC_ALL=C;
12
+ # (3) the same run with the pin deleted goes red for the encoding reason, which
13
+ # shows the pin is what makes leg (2) pass; on a host whose C locale already
14
+ # reads UTF-8 the pin is not load-bearing and leg (3) reports unevaluated.
15
+ set -euo pipefail
16
+ # Ruby takes its encoding from the locale; under a POSIX/unset locale it reads the UTF-8 skill text as US-ASCII and crashes. Pin UTF-8, as CI runs.
17
+ case " ${RUBYOPT:-} " in *" -Ku "*) ;; *) export RUBYOPT="-Ku${RUBYOPT:+ $RUBYOPT}" ;; esac
18
+
19
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
20
+ ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd -P)"
21
+ PIN='case " ${RUBYOPT:-} " in *" -Ku "*) ;; *) export RUBYOPT="-Ku${RUBYOPT:+ $RUBYOPT}" ;; esac'
22
+ fail() { printf 'FAIL: %b\n' "$*" >&2; exit 1; }
23
+
24
+ # Closed contract over the idiom this repo owns: a RUBYOPT mention keeps the pin
25
+ # only as the pin line itself, the keep idiom RUBYOPT="${RUBYOPT:+$RUBYOPT }..."
26
+ # whose suffix holds only `-r<lib>` requires or variables, or an empty RUBYOPT=
27
+ # before `bash "$script"` (that script pins itself). Any other literal mention -
28
+ # replacing, clearing, unset, env -u, any other Ruby option - drops the pin.
29
+ # Ruby's option names are not this repo's to enumerate, so the suffix is
30
+ # allowlisted, never denylisted. Returns 0 when allowed.
31
+ KEEP_TOKEN='(-r[^[:space:]"\\]+|[$][{]?[A-Za-z_0-9]+[}]?)'
32
+ pin_kept() {
33
+ local line="$1" rest
34
+ [ "$line" = "$PIN" ] && return 0
35
+ # An attribute builtin on the variable can unexport or rescope it even when
36
+ # the value is the keep idiom.
37
+ if printf '%s\n' "$line" | grep -qE '(^|[[:space:];&|({])(declare|typeset|local|readonly|export[[:space:]]+-n)([[:space:]]|$)[^;|&]*RUBYOPT'; then
38
+ return 1
39
+ fi
40
+ rest="$(printf '%s\n' "$line" | sed -E \
41
+ -e "s/^([^\"'\\\\]*)[[:space:]]#.*\$/\\1/" \
42
+ -e "s/RUBYOPT=\"[\$][{]RUBYOPT:[+][\$]RUBYOPT [}](${KEEP_TOKEN}([[:space:]]+${KEEP_TOKEN})*)?\"//g" \
43
+ -e 's/(^|[[:space:]])RUBYOPT=[[:space:]]+bash[[:space:]]+"[$][A-Za-z_{][^"]*"/\1/g')"
44
+ case "$rest" in *RUBYOPT*) return 1 ;; esac
45
+ return 0
46
+ }
47
+
48
+ # Every RUBYOPT line of one file that drops the pin, as "N:line". Whole-line
49
+ # comments are skipped; nothing else is pre-filtered, so a line that merely
50
+ # contains the pin text is still classified.
51
+ pin_drops() {
52
+ local hit
53
+ while IFS= read -r hit; do
54
+ pin_kept "${hit#*:}" || printf '%s\n' "$hit"
55
+ done < <(grep -nE 'RUBYOPT' "$1" | grep -vE '^[0-9]+:[[:space:]]*#' || true)
56
+ }
57
+
58
+ # (1a) The classifier itself: every bypass row must be flagged and every
59
+ # near-miss row must stay allowed, so a broken classifier cannot pass on a
60
+ # corpus that happens to hold only allowed shapes.
61
+ while IFS= read -r row; do
62
+ [ -n "$row" ] || continue
63
+ if pin_kept "$row"; then fail "classifier allowed a pin-dropping line: $row"; fi
64
+ done <<'ROWS'
65
+ RUBYOPT="-r$SHIM" ruby x.rb
66
+ RUBYOPT= ruby -e "p 1"
67
+ unset RUBYOPT
68
+ env -u RUBYOPT ruby -e 1
69
+ RUBYOPT="$RUBYOPT_EXTRA -rfoo" ruby -e 1
70
+ export RUBYOPT=
71
+ RUBYOPT=
72
+ RUBYOPT= LC_ALL=C ruby -e 1
73
+ LC_ALL=C RUBYOPT= LANG=C ruby -e 1
74
+ RUBYOPT= exec ruby -e 1
75
+ RUBYOPT= "${RUBY:-ruby}" -e 1
76
+ RUBYOPT= bash -c "ruby -e 1"
77
+ unset -v RUBYOPT
78
+ unset LANG RUBYOPT
79
+ env --unset=RUBYOPT ruby -e 1
80
+ env -uRUBYOPT ruby -e 1
81
+ RUBYOPT=-W0 ruby -e 1 "$RUBYOPT"
82
+ RUBYOPT="${RUBYOPT#-Ku}" ruby -e 1
83
+ RUBYOPT="" bash x.sh
84
+ export RUBYOPT=-W0
85
+ echo "a #b"; RUBYOPT=-Kn ruby -e 1
86
+ printf ' #' ; unset RUBYOPT
87
+ case " ${RUBYOPT:-} " in *" -Ku "*) ;; *) export RUBYOPT="-Ku${RUBYOPT:+ $RUBYOPT}" ;; esac; unset RUBYOPT
88
+ RUBYOPT="${RUBYOPT:+$RUBYOPT }-Kn" ruby x.rb
89
+ RUBYOPT="${RUBYOPT:+$RUBYOPT }-E ASCII" ruby x.rb
90
+ RUBYOPT="${RUBYOPT:+$RUBYOPT }--internal-encoding=US-ASCII" ruby x.rb
91
+ RUBYOPT="${RUBYOPT:+$RUBYOPT }--disable=rubyopt" ruby x.rb
92
+ RUBYOPT="${RUBYOPT:+$RUBYOPT }-r\" -Kn" ruby x.rb
93
+ export -n RUBYOPT="${RUBYOPT:+$RUBYOPT }-rdate"
94
+ declare +x RUBYOPT="${RUBYOPT:+$RUBYOPT }"
95
+ local RUBYOPT="${RUBYOPT:+$RUBYOPT }-rdate"
96
+ readonly RUBYOPT="${RUBYOPT:+$RUBYOPT }-rdate"
97
+ typeset -x RUBYOPT="${RUBYOPT:+$RUBYOPT }-rdate"
98
+ RUBYOPT="${RUBYOPT:+$RUBYOPT_EXTRA }" ruby x.rb
99
+ RUBYOPT="${RUBYOPT:+$RUBYOPT}-Kn" ruby x.rb
100
+ x=$#; unset RUBYOPT
101
+ echo a\ #b; unset RUBYOPT
102
+ local RUBYOPT
103
+ declare -x RUBYOPT=
104
+ ROWS
105
+ while IFS= read -r row; do
106
+ [ -n "$row" ] || continue
107
+ pin_kept "$row" || fail "classifier flagged an allowed line: $row"
108
+ done <<'ROWS'
109
+ out="$(RUBYOPT="${RUBYOPT:+$RUBYOPT }-rdate" ruby x.rb)"
110
+ env -u X LC_ALL=C RUBYOPT= bash "$SIZE_SCRIPT" "$REPO"
111
+ f() { # takes a RUBYOPT value
112
+ RUBYOPT= bash "$x"; ruby -e 1
113
+ RUBYOPT="${RUBYOPT:+$RUBYOPT }-r$SHIM -rdate" ruby x.rb
114
+ RUBYOPT="${RUBYOPT:+$RUBYOPT }$2" ruby "$1"
115
+ run_gate() { # <gate-path> <RUBYOPT value or empty>
116
+ locale=C RUBYOPT= bash "$x"
117
+ dir=/usr/local RUBYOPT="${RUBYOPT:+$RUBYOPT }-rdate" ruby x.rb
118
+ declared=1 RUBYOPT= bash "$s"
119
+ ROWS
120
+
121
+ # (1b) Static coverage over tracked live shell scripts.
122
+ tracked="$(git -C "$ROOT" ls-files -- 'skills/*.sh' 'hooks/*.sh' 'scripts/*.sh')" \
123
+ || fail "cannot list tracked scripts (not a git checkout?); leg 1 needs git ls-files"
124
+ [ -n "$tracked" ] || fail "git ls-files listed no tracked scripts under $ROOT; leg 1 would pass vacuously"
125
+ missing=""
126
+ dropped=""
127
+ while IFS= read -r rel; do
128
+ # A bare `ruby` word: catches `ruby -e`, `ruby<<`, `ruby;`, `${RUBY:-ruby}`;
129
+ # leaves `rubygems` / `ruby-build` alone. Over-matching only over-requires the pin.
130
+ grep -qE '(^|[^a-z_])ruby([^a-z0-9_.-]|$)' "$ROOT/$rel" || continue
131
+ grep -qxF "$PIN" "$ROOT/$rel" || missing="$missing\n $rel"
132
+ # This file names RUBYOPT in its own patterns and rows, so only its pin is checked.
133
+ [ "$rel" = "skills/skill-extraction-workflow/scripts/test_locale_independent_gates.sh" ] && continue
134
+ while IFS= read -r hit; do
135
+ [ -n "$hit" ] && dropped="$dropped\n $rel: $hit"
136
+ done < <(pin_drops "$ROOT/$rel")
137
+ done <<<"$tracked"
138
+ # Makefile targets that run ruby in a recipe must be on the target-specific
139
+ # RUBYOPT pin line. A rule line names every target before its first colon
140
+ # (`a b: deps`); `X := y` assignments and the pin line itself are not rules.
141
+ mk_pinned_of() { sed -nE 's/^([^#:]*):[[:space:]]*override RUBYOPT :=.*-Ku.*$/\1/p' "$1"; }
142
+ mk_ruby_of() {
143
+ awk '
144
+ /^\t/ { if ($0 ~ /(^|[^a-z_])ruby([^a-z0-9_.-]|$)/) for (i in cur) print cur[i]; next }
145
+ /^[^#[:space:]]/ {
146
+ c = index($0, ":"); if (c == 0) next
147
+ head = substr($0, 1, c - 1)
148
+ if (substr($0, c + 1, 1) == "=" || head ~ /=/) next
149
+ if ($0 ~ /RUBYOPT :=/) next
150
+ split("", cur); n = split(head, names, /[[:space:]]+/)
151
+ for (i = 1; i <= n; i++) if (names[i] != "") cur[i] = names[i]
152
+ }' "$1" | sort -u
153
+ }
154
+ mk_missing_of() { # <Makefile>: ruby-running targets absent from the pin line
155
+ local pinned t
156
+ grep -qxF 'export RUBYOPT' "$1" || { echo "(RUBYOPT is not exported)"; return; }
157
+ pinned="$(mk_pinned_of "$1")"
158
+ [ -n "$pinned" ] || { echo "(no target-specific RUBYOPT -Ku pin line)"; return; }
159
+ for t in $(mk_ruby_of "$1"); do
160
+ case " $pinned " in *" $t "*) ;; *) echo "$t" ;; esac
161
+ done
162
+ }
163
+ [ -n "$(mk_ruby_of "$ROOT/Makefile")" ] || fail "found no Makefile recipe that runs ruby; the Makefile check would pass vacuously"
164
+ for t in $(mk_missing_of "$ROOT/Makefile"); do missing="$missing\n Makefile target $t"; done
165
+ # The tracker itself, on a fixture: a multi-target ruby rule after a pinned one
166
+ # must be reported under its own names, and an assignment line must not reset it.
167
+ mk_fx="$(mktemp "${TMPDIR:-/tmp}/locale-mk.XXXXXX")"
168
+ printf 'X := 1\nexport RUBYOPT\np1 p2: override RUBYOPT := -Ku\np1:\n\truby a.rb\nnew-a new-b: dep\n\t@ruby b.rb\nY := 2\n\techo no\n' >"$mk_fx"
169
+ mk_fx_out="$(mk_missing_of "$mk_fx" | tr '\n' ' ')"
170
+ rm -f "$mk_fx"
171
+ [ "$mk_fx_out" = "new-a new-b " ] || fail "Makefile tracker must report exactly new-a new-b on its fixture, got: $mk_fx_out"
172
+ [ -z "$missing" ] || fail "ruby-invoking scripts without the RUBYOPT UTF-8 pin:$missing"
173
+ [ -z "$dropped" ] || fail "RUBYOPT mentions that drop the UTF-8 pin (use the keep idiom):$dropped"
174
+
175
+ TMP="$(mktemp -d "${TMPDIR:-/tmp}/locale-gates.XXXXXX")"
176
+ trap 'rm -rf "$TMP"' EXIT
177
+
178
+ # Exercise the actual Makefile declarations using inert recipes. In particular,
179
+ # command-line RUBYOPT must not override the UTF-8 pin. Replacing just the recipes
180
+ # keeps live evals offline and preserves Make's assignment/export semantics.
181
+ make_targets="$(mk_ruby_of "$ROOT/Makefile" | tr '\n' ' ')"
182
+ printf '%s:\n' "$make_targets" >"$TMP/probe.mk"
183
+ printf '\t@ruby -e '\''abort "make recipe lost UTF-8" unless Encoding.default_external == Encoding::UTF_8; abort "make recipe lost caller option" if ENV["CCL_MAKE_CALLER_OPTION"] == "yes" && !ENV.fetch("RUBYOPT").split.include?("-W0")'\''\n' >>"$TMP/probe.mk"
184
+ run_make_pin() {
185
+ local makefile="$1"
186
+ shift
187
+ env -u LANGUAGE -u RUBYOPT LC_ALL=C LANG=C make --no-print-directory \
188
+ -f "$makefile" -f "$TMP/probe.mk" "$@" $make_targets
189
+ }
190
+ if ! make_out="$(run_make_pin "$ROOT/Makefile" RUBYOPT=-W0 CCL_MAKE_CALLER_OPTION=yes 2>&1)"; then
191
+ fail "Makefile Ruby targets failed under LC_ALL=C with CLI RUBYOPT:\n$make_out"
192
+ fi
193
+ if ! make_out="$(run_make_pin "$ROOT/Makefile" 2>&1)"; then
194
+ fail "Makefile Ruby targets failed with RUBYOPT absent:\n$make_out"
195
+ fi
196
+ # Applied mutations prove both declarations matter at runtime, as well as in
197
+ # the static tracker. CLI variables are auto-exported by Make, so the export
198
+ # mutation must run without the CLI override to avoid masking a missing export.
199
+ sed 's/: override RUBYOPT :=/: RUBYOPT :=/' "$ROOT/Makefile" >"$TMP/no-override.mk"
200
+ sed '/^export RUBYOPT$/d' "$ROOT/Makefile" >"$TMP/no-export.mk"
201
+ for mutant in no-override no-export; do
202
+ cmp -s "$ROOT/Makefile" "$TMP/$mutant.mk" && fail "$mutant mutation was not applied"
203
+ [ -n "$(mk_missing_of "$TMP/$mutant.mk")" ] || fail "Makefile tracker accepted $mutant mutation"
204
+ make_args=()
205
+ [ "$mutant" != no-override ] || make_args=(RUBYOPT=-W0)
206
+ # Bash 3.2 treats an empty array as unset under nounset.
207
+ if make_out="$(run_make_pin "$TMP/$mutant.mk" ${make_args[@]+"${make_args[@]}"} 2>&1)"; then
208
+ fail "Makefile $mutant mutation still passed the UTF-8 assertion"
209
+ fi
210
+ case "$make_out" in *'make recipe lost UTF-8'*) : ;; *) fail "$mutant failed for an unrelated reason:\n$make_out" ;; esac
211
+ done
212
+
213
+ # (1c) The scan as a whole, on a fixture file: only a line that drops the pin is
214
+ # reported, including one that also contains the pin text.
215
+ {
216
+ printf '%s\n' "$PIN"
217
+ printf '%s; unset RUBYOPT\n' "$PIN"
218
+ printf '# unset RUBYOPT in a comment\n'
219
+ printf 'out="$(RUBYOPT="${RUBYOPT:+$RUBYOPT }-rdate" ruby x.rb)"\n'
220
+ } >"$TMP/scan-fixture.sh"
221
+ scan_out="$(pin_drops "$TMP/scan-fixture.sh")"
222
+ [ "${scan_out%%:*}" = "2" ] && [ "$(printf '%s\n' "$scan_out" | grep -c .)" = "1" ] \
223
+ || fail "pin_drops must report only line 2 of the scan fixture, got:\n$scan_out"
224
+ mkdir -p "$TMP/skill/references"
225
+ cat >"$TMP/skill/SKILL.md" <<'EOF'
226
+ ---
227
+ name: locale-fixture
228
+ description: 合成夹具 — synthetic fixture for locale independence.
229
+ ---
230
+
231
+ # 夹具
232
+
233
+ 见 `references/detail.md`。
234
+ EOF
235
+ printf '# 细节\n\n中文正文。\n' >"$TMP/skill/references/detail.md"
236
+
237
+ run_c_locale() { env -u LANGUAGE LC_ALL=C LANG=C RUBYOPT= bash "$1" "$TMP/skill"; }
238
+
239
+ # (2) The shipped gate passes under the C locale.
240
+ if ! out="$(run_c_locale "$SCRIPT_DIR/validate-skill.sh" 2>&1)"; then
241
+ fail "validate-skill.sh failed under LC_ALL=C:\n$out"
242
+ fi
243
+ case "$out" in *markdown_references_ok*) : ;; *) fail "validate-skill.sh did not reach the reference check under LC_ALL=C:\n$out" ;; esac
244
+
245
+ # (3) Applied mutation: the same script without the pin reds for the encoding reason.
246
+ cp -R "$SCRIPT_DIR" "$TMP/scripts"
247
+ grep -vxF "$PIN" "$SCRIPT_DIR/validate-skill.sh" >"$TMP/scripts/validate-skill.sh"
248
+ if mout="$(run_c_locale "$TMP/scripts/validate-skill.sh" 2>&1)"; then
249
+ # Unevaluated only when the host itself explains the pass: Ruby already reads
250
+ # UTF-8 under the C locale, so the pin is not load-bearing here. Any other
251
+ # reason the unpinned run passed means leg 2 may be vacuous.
252
+ if ! env -u LANGUAGE LC_ALL=C LANG=C ruby -e 'exit(Encoding.find("locale") == Encoding::UTF_8 ? 0 : 1)'; then
253
+ fail "pin removed, yet validate-skill.sh still passed under LC_ALL=C on a host whose Ruby reads US-ASCII there, so leg 2 proves nothing:\n$mout"
254
+ fi
255
+ echo "test_locale_independent_gates_leg3_unevaluated: this host's Ruby reads UTF-8 under LC_ALL=C, so removing the pin cannot be shown to matter here" >&2
256
+ echo "test_locale_independent_gates: ok (legs 1-2; leg 3 unevaluated on this host)"
257
+ exit 0
258
+ fi
259
+ case "$mout" in
260
+ *"invalid byte sequence"*|*"invalid multibyte char"*) : ;;
261
+ *) fail "pin removed: run failed, but not for the encoding reason:\n$mout" ;;
262
+ esac
263
+
264
+ echo "test_locale_independent_gates: ok"
@@ -13,6 +13,8 @@
13
13
  # locator. A test that only ever asserts "clean stays clean" would pass with the
14
14
  # whole detection deleted, so every RED case here has a paired GREEN control.
15
15
  set -euo pipefail
16
+ # Ruby takes its encoding from the locale; under a POSIX/unset locale it reads the UTF-8 skill text as US-ASCII and crashes. Pin UTF-8, as CI runs.
17
+ case " ${RUBYOPT:-} " in *" -Ku "*) ;; *) export RUBYOPT="-Ku${RUBYOPT:+ $RUBYOPT}" ;; esac
16
18
 
17
19
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
18
20
  GATE="$SCRIPT_DIR/register-firing-path-resolution.rb"
@@ -11,6 +11,8 @@
11
11
  # ALIAS_AUDIT_CMD=true stands in for a passing private R0 audit so the assertion
12
12
  # isolates firing-path enforcement from the separate R0 interim path.
13
13
  set -euo pipefail
14
+ # Ruby takes its encoding from the locale; under a POSIX/unset locale it reads the UTF-8 skill text as US-ASCII and crashes. Pin UTF-8, as CI runs.
15
+ case " ${RUBYOPT:-} " in *" -Ku "*) ;; *) export RUBYOPT="-Ku${RUBYOPT:+ $RUBYOPT}" ;; esac
14
16
 
15
17
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
16
18
  CHECK_SCRIPT="$SCRIPT_DIR/check-ccl-skills.sh"
@@ -3,6 +3,8 @@
3
3
  # The canonical reference owns the protocol; each participating skill keeps
4
4
  # only its local responsibility and a pointer to that reference.
5
5
  set -euo pipefail
6
+ # Ruby takes its encoding from the locale; under a POSIX/unset locale it reads the UTF-8 skill text as US-ASCII and crashes. Pin UTF-8, as CI runs.
7
+ case " ${RUBYOPT:-} " in *" -Ku "*) ;; *) export RUBYOPT="-Ku${RUBYOPT:+ $RUBYOPT}" ;; esac
6
8
 
7
9
  ROOT="${UIUX_CONTRACT_ROOT:-$(cd "$(dirname "$0")/../../.." && pwd -P)}"
8
10
  CONTRACT="$ROOT/skills/product-ui-ux-design/references/delivery-contract.md"
@@ -7,6 +7,8 @@
7
7
  # entry and every routed reference local and resolvable. Real provider load,
8
8
  # token, latency, correction, and task-quality claims still require task trials.
9
9
  set -euo pipefail
10
+ # Ruby takes its encoding from the locale; under a POSIX/unset locale it reads the UTF-8 skill text as US-ASCII and crashes. Pin UTF-8, as CI runs.
11
+ case " ${RUBYOPT:-} " in *" -Ku "*) ;; *) export RUBYOPT="-Ku${RUBYOPT:+ $RUBYOPT}" ;; esac
10
12
 
11
13
  ROOT="${UIUX_LOADING_ROOT:-$(cd "$(dirname "$0")/../../.." && pwd -P)}"
12
14
  DEFAULT_BASE_REF="e322db47abe5736e6e1fdf0e73e2ed3eb32c006b"
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
+ # Ruby takes its encoding from the locale; under a POSIX/unset locale it reads the UTF-8 skill text as US-ASCII and crashes. Pin UTF-8, as CI runs.
4
+ case " ${RUBYOPT:-} " in *" -Ku "*) ;; *) export RUBYOPT="-Ku${RUBYOPT:+ $RUBYOPT}" ;; esac
3
5
 
4
6
  target="${1:-.}"
5
7