@tw93/waza 3.25.0
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.
- package/LICENSE +21 -0
- package/README.md +206 -0
- package/package.json +35 -0
- package/rules/anti-patterns.md +38 -0
- package/rules/chinese.md +18 -0
- package/rules/durable-context.md +27 -0
- package/rules/english.md +14 -0
- package/scripts/build_metadata.py +360 -0
- package/scripts/check_routing_drift.py +82 -0
- package/scripts/dispatcher-template.md +43 -0
- package/scripts/dispatcher.md +53 -0
- package/scripts/package-skill.sh +71 -0
- package/scripts/packaging_filter.py +55 -0
- package/scripts/setup-rule.sh +109 -0
- package/scripts/setup-statusline.sh +127 -0
- package/scripts/skill_checks.py +483 -0
- package/scripts/skill_frontmatter.py +110 -0
- package/scripts/statusline.sh +321 -0
- package/scripts/validate_package.py +66 -0
- package/scripts/verify_skills.py +100 -0
- package/skills/RESOLVER.md +91 -0
- package/skills/check/SKILL.md +338 -0
- package/skills/check/agents/reviewer-architecture.md +39 -0
- package/skills/check/agents/reviewer-security.md +39 -0
- package/skills/check/references/persona-catalog.md +56 -0
- package/skills/check/references/project-context.md +107 -0
- package/skills/check/references/public-reply.md +14 -0
- package/skills/check/scripts/audit_signals.py +485 -0
- package/skills/check/scripts/run-tests.sh +19 -0
- package/skills/design/SKILL.md +134 -0
- package/skills/design/references/design-aesthetic-quality.md +67 -0
- package/skills/design/references/design-data-viz.md +34 -0
- package/skills/design/references/design-reference.md +278 -0
- package/skills/design/references/design-tokens.md +53 -0
- package/skills/design/references/design-traps.md +43 -0
- package/skills/health/SKILL.md +231 -0
- package/skills/health/agents/inspector-context.md +119 -0
- package/skills/health/agents/inspector-control.md +84 -0
- package/skills/health/agents/inspector-maintainability.md +55 -0
- package/skills/health/scripts/check-agent-context.sh +5 -0
- package/skills/health/scripts/check-doc-refs.sh +8 -0
- package/skills/health/scripts/check-maintainability.sh +8 -0
- package/skills/health/scripts/check-verifier-output.sh +5 -0
- package/skills/health/scripts/check_agent_context.py +407 -0
- package/skills/health/scripts/check_doc_refs.py +110 -0
- package/skills/health/scripts/check_maintainability.py +629 -0
- package/skills/health/scripts/check_verifier_output.py +116 -0
- package/skills/health/scripts/collect-data.sh +760 -0
- package/skills/hunt/SKILL.md +197 -0
- package/skills/hunt/references/failure-patterns.md +75 -0
- package/skills/hunt/references/ime-unicode.md +58 -0
- package/skills/hunt/references/logging-techniques.md +72 -0
- package/skills/hunt/references/rendering-debug.md +34 -0
- package/skills/learn/SKILL.md +128 -0
- package/skills/read/SKILL.md +108 -0
- package/skills/read/references/read-methods.md +110 -0
- package/skills/read/references/save-paths.md +33 -0
- package/skills/read/scripts/fetch.sh +105 -0
- package/skills/read/scripts/fetch_feishu.py +246 -0
- package/skills/read/scripts/fetch_local.py +218 -0
- package/skills/read/scripts/fetch_weixin.py +107 -0
- package/skills/think/SKILL.md +155 -0
- package/skills/write/SKILL.md +129 -0
- package/skills/write/references/write-en.md +197 -0
- package/skills/write/references/write-zh-bilingual.md +60 -0
- package/skills/write/references/write-zh-prose.md +48 -0
- package/skills/write/references/write-zh-release-notes.md +38 -0
- package/skills/write/references/write-zh.md +645 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Claude Code statusline: Context % | 5h: % (reset) | 7d: % (reset)
|
|
3
|
+
# Set WAZA_STATUSLINE_DEBUG=1 to surface stderr (jq parse errors, missing tools).
|
|
4
|
+
[ "${WAZA_STATUSLINE_DEBUG:-}" = "1" ] || exec 2>/dev/null
|
|
5
|
+
|
|
6
|
+
CACHE_DIR="$HOME/.cache/waza-statusline"
|
|
7
|
+
CACHE_FILE="$CACHE_DIR/last.json"
|
|
8
|
+
HIGHWATER_FILE="$CACHE_DIR/highwater.json"
|
|
9
|
+
HIGHWATER_LOCK_DIR="$CACHE_DIR/highwater.lock"
|
|
10
|
+
CACHE_MAX_AGE=21600 # 6 hours: one full rate_limit window
|
|
11
|
+
HIGHWATER_LOCK_MAX_AGE=10
|
|
12
|
+
HIGHWATER_RESET_SKEW_MAX=7200 # tolerate session jitter, reject crossed windows
|
|
13
|
+
|
|
14
|
+
input=$(cat)
|
|
15
|
+
|
|
16
|
+
tab=$(printf '\t')
|
|
17
|
+
|
|
18
|
+
jq_full='[
|
|
19
|
+
((.context_window.current_usage.input_tokens // 0)
|
|
20
|
+
+ (.context_window.current_usage.cache_creation_input_tokens // 0)
|
|
21
|
+
+ (.context_window.current_usage.cache_read_input_tokens // 0) | tostring),
|
|
22
|
+
(.context_window.context_window_size // 0 | tostring),
|
|
23
|
+
(.rate_limits.five_hour.used_percentage // null | if . then (. | round | tostring) else "null" end),
|
|
24
|
+
(.rate_limits.five_hour.resets_at // "" | tostring),
|
|
25
|
+
(.rate_limits.seven_day.used_percentage // null | if . then (. | round | tostring) else "null" end),
|
|
26
|
+
(.rate_limits.seven_day.resets_at // "" | tostring)
|
|
27
|
+
] | @tsv'
|
|
28
|
+
|
|
29
|
+
jq_rl='[
|
|
30
|
+
(.rate_limits.five_hour.used_percentage // null | if . then (. | round | tostring) else "null" end),
|
|
31
|
+
(.rate_limits.five_hour.resets_at // "" | tostring),
|
|
32
|
+
(.rate_limits.seven_day.used_percentage // null | if . then (. | round | tostring) else "null" end),
|
|
33
|
+
(.rate_limits.seven_day.resets_at // "" | tostring)
|
|
34
|
+
] | @tsv'
|
|
35
|
+
|
|
36
|
+
cache_file_mtime() {
|
|
37
|
+
local path="$1"
|
|
38
|
+
local ts=""
|
|
39
|
+
ts=$(stat -c %Y "$path" 2>/dev/null || true)
|
|
40
|
+
if [ -z "$ts" ]; then
|
|
41
|
+
ts=$(stat -f %m "$path" 2>/dev/null || true)
|
|
42
|
+
fi
|
|
43
|
+
printf '%s\n' "${ts:-0}"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
is_uint() {
|
|
47
|
+
case "$1" in
|
|
48
|
+
''|null) return 1 ;;
|
|
49
|
+
*[!0-9]*) return 1 ;;
|
|
50
|
+
*) return 0 ;;
|
|
51
|
+
esac
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
acquire_highwater_lock() {
|
|
55
|
+
mkdir -p "$CACHE_DIR" 2>/dev/null || return 1
|
|
56
|
+
local attempts=0 lock_mtime now
|
|
57
|
+
while [ "$attempts" -lt 5 ]; do
|
|
58
|
+
attempts=$((attempts + 1))
|
|
59
|
+
if mkdir "$HIGHWATER_LOCK_DIR" 2>/dev/null; then
|
|
60
|
+
return 0
|
|
61
|
+
fi
|
|
62
|
+
lock_mtime=$(cache_file_mtime "$HIGHWATER_LOCK_DIR")
|
|
63
|
+
now=$(date +%s)
|
|
64
|
+
if [ $((now - lock_mtime)) -gt "$HIGHWATER_LOCK_MAX_AGE" ]; then
|
|
65
|
+
rmdir "$HIGHWATER_LOCK_DIR" 2>/dev/null || true
|
|
66
|
+
continue
|
|
67
|
+
fi
|
|
68
|
+
sleep 0.05
|
|
69
|
+
done
|
|
70
|
+
return 1
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
release_highwater_lock() {
|
|
74
|
+
rmdir "$HIGHWATER_LOCK_DIR" 2>/dev/null || true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
read_highwater() {
|
|
78
|
+
hw_5h_pct=""
|
|
79
|
+
hw_5h_reset=""
|
|
80
|
+
hw_7d_pct=""
|
|
81
|
+
hw_7d_reset=""
|
|
82
|
+
[ -f "$HIGHWATER_FILE" ] || return
|
|
83
|
+
hw_5h_pct=$(jq -r 'if .five_hour.used_percentage == null then "" else (.five_hour.used_percentage | round | tostring) end' "$HIGHWATER_FILE" 2>/dev/null)
|
|
84
|
+
hw_5h_reset=$(jq -r 'if .five_hour.resets_at == null then "" else (.five_hour.resets_at | tostring) end' "$HIGHWATER_FILE" 2>/dev/null)
|
|
85
|
+
hw_7d_pct=$(jq -r 'if .seven_day.used_percentage == null then "" else (.seven_day.used_percentage | round | tostring) end' "$HIGHWATER_FILE" 2>/dev/null)
|
|
86
|
+
hw_7d_reset=$(jq -r 'if .seven_day.resets_at == null then "" else (.seven_day.resets_at | tostring) end' "$HIGHWATER_FILE" 2>/dev/null)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# apply_hw: compares live vs high-water marks for a single counter (5h or 7d).
|
|
90
|
+
# Mutates four caller-scope globals by name (no return, by design):
|
|
91
|
+
# applied_pct, applied_reset — values to render in the statusline now
|
|
92
|
+
# applied_hw_pct, applied_hw_reset — values to persist back to highwater.json
|
|
93
|
+
# Caller must read these immediately after the call; the next invocation
|
|
94
|
+
# clobbers them. Side effect is intentional: bash can't return composite values
|
|
95
|
+
# cleanly, and threading four out-params through every call site was worse.
|
|
96
|
+
apply_hw() {
|
|
97
|
+
local live_pct="$1" live_reset="$2" hw_pct="$3" hw_reset="$4"
|
|
98
|
+
local now reset_diff live_ok=0 hw_ok=0
|
|
99
|
+
now=$(date +%s)
|
|
100
|
+
|
|
101
|
+
if is_uint "$hw_reset" && [ "$hw_reset" -le "$now" ]; then
|
|
102
|
+
hw_pct=""
|
|
103
|
+
hw_reset=""
|
|
104
|
+
fi
|
|
105
|
+
if is_uint "$live_reset" && [ "$live_reset" -le "$now" ]; then
|
|
106
|
+
live_pct=""
|
|
107
|
+
live_reset=""
|
|
108
|
+
fi
|
|
109
|
+
if is_uint "$live_reset" && is_uint "$hw_reset"; then
|
|
110
|
+
reset_diff=$((live_reset - hw_reset))
|
|
111
|
+
[ "$reset_diff" -lt 0 ] && reset_diff=$((-reset_diff))
|
|
112
|
+
if [ "$reset_diff" -gt "$HIGHWATER_RESET_SKEW_MAX" ]; then
|
|
113
|
+
hw_pct=""
|
|
114
|
+
hw_reset=""
|
|
115
|
+
fi
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
is_uint "$live_pct" && live_ok=1
|
|
119
|
+
is_uint "$hw_pct" && hw_ok=1
|
|
120
|
+
|
|
121
|
+
applied_pct=""
|
|
122
|
+
applied_reset=""
|
|
123
|
+
applied_hw_pct=""
|
|
124
|
+
applied_hw_reset=""
|
|
125
|
+
if [ "$live_ok" = "0" ] && [ "$hw_ok" = "0" ]; then
|
|
126
|
+
return
|
|
127
|
+
fi
|
|
128
|
+
if [ "$live_ok" = "0" ]; then
|
|
129
|
+
applied_pct="$hw_pct"
|
|
130
|
+
applied_reset="$hw_reset"
|
|
131
|
+
applied_hw_pct="$hw_pct"
|
|
132
|
+
applied_hw_reset="$hw_reset"
|
|
133
|
+
return
|
|
134
|
+
fi
|
|
135
|
+
if [ "$hw_ok" = "0" ] || [ "$live_pct" -gt "$hw_pct" ] 2>/dev/null; then
|
|
136
|
+
applied_pct="$live_pct"
|
|
137
|
+
applied_reset="$live_reset"
|
|
138
|
+
applied_hw_pct="$live_pct"
|
|
139
|
+
applied_hw_reset="$live_reset"
|
|
140
|
+
return
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
applied_pct="$hw_pct"
|
|
144
|
+
applied_reset="${live_reset:-$hw_reset}"
|
|
145
|
+
applied_hw_pct="$hw_pct"
|
|
146
|
+
applied_hw_reset="$hw_reset"
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
write_highwater() {
|
|
150
|
+
is_uint "$new_hw_5h_pct" || is_uint "$new_hw_7d_pct" || return
|
|
151
|
+
mkdir -p "$CACHE_DIR" 2>/dev/null || return
|
|
152
|
+
local r5="${new_hw_5h_reset:-0}" r7="${new_hw_7d_reset:-0}"
|
|
153
|
+
is_uint "$r5" || r5=0
|
|
154
|
+
is_uint "$r7" || r7=0
|
|
155
|
+
if ! {
|
|
156
|
+
{
|
|
157
|
+
printf '{\n'
|
|
158
|
+
if is_uint "$new_hw_5h_pct"; then
|
|
159
|
+
printf ' "five_hour": {"used_percentage": %s, "resets_at": %s}' "$new_hw_5h_pct" "$r5"
|
|
160
|
+
is_uint "$new_hw_7d_pct" && printf ','
|
|
161
|
+
printf '\n'
|
|
162
|
+
fi
|
|
163
|
+
if is_uint "$new_hw_7d_pct"; then
|
|
164
|
+
printf ' "seven_day": {"used_percentage": %s, "resets_at": %s}\n' "$new_hw_7d_pct" "$r7"
|
|
165
|
+
fi
|
|
166
|
+
printf '}\n'
|
|
167
|
+
} > "${HIGHWATER_FILE}.tmp" 2>/dev/null \
|
|
168
|
+
&& mv "${HIGHWATER_FILE}.tmp" "$HIGHWATER_FILE" 2>/dev/null
|
|
169
|
+
}; then
|
|
170
|
+
:
|
|
171
|
+
fi
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
apply_highwater_all() {
|
|
175
|
+
read_highwater
|
|
176
|
+
|
|
177
|
+
apply_hw "$five_pct" "$five_reset" "$hw_5h_pct" "$hw_5h_reset"
|
|
178
|
+
five_pct="$applied_pct"
|
|
179
|
+
five_reset="$applied_reset"
|
|
180
|
+
new_hw_5h_pct="$applied_hw_pct"
|
|
181
|
+
new_hw_5h_reset="$applied_hw_reset"
|
|
182
|
+
|
|
183
|
+
apply_hw "$seven_pct" "$seven_reset" "$hw_7d_pct" "$hw_7d_reset"
|
|
184
|
+
seven_pct="$applied_pct"
|
|
185
|
+
seven_reset="$applied_reset"
|
|
186
|
+
new_hw_7d_pct="$applied_hw_pct"
|
|
187
|
+
new_hw_7d_reset="$applied_hw_reset"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# Single jq pass for live input
|
|
191
|
+
parsed=""
|
|
192
|
+
[ -n "$input" ] && parsed=$(printf '%s' "$input" | jq -r "$jq_full" 2>/dev/null)
|
|
193
|
+
|
|
194
|
+
IFS="$tab" read -r used_tokens window_size live_five_pct live_five_reset live_seven_pct live_seven_reset <<EOF
|
|
195
|
+
$parsed
|
|
196
|
+
EOF
|
|
197
|
+
|
|
198
|
+
five_pct="${live_five_pct:-}"
|
|
199
|
+
five_reset="${live_five_reset:-}"
|
|
200
|
+
seven_pct="${live_seven_pct:-}"
|
|
201
|
+
seven_reset="${live_seven_reset:-}"
|
|
202
|
+
|
|
203
|
+
# If rate_limits missing from live input, read from cache
|
|
204
|
+
if [ "$five_pct" = "null" ] || [ -z "$five_pct" ]; then
|
|
205
|
+
if [ -f "$CACHE_FILE" ]; then
|
|
206
|
+
cache_mtime=$(cache_file_mtime "$CACHE_FILE")
|
|
207
|
+
cache_age=$(( $(date +%s) - cache_mtime ))
|
|
208
|
+
if [ "$cache_age" -lt "$CACHE_MAX_AGE" ]; then
|
|
209
|
+
cached=$(jq -r "$jq_rl" "$CACHE_FILE" 2>/dev/null)
|
|
210
|
+
IFS="$tab" read -r five_pct five_reset seven_pct seven_reset <<EOF
|
|
211
|
+
$cached
|
|
212
|
+
EOF
|
|
213
|
+
fi
|
|
214
|
+
fi
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
# Persist live rate_limits only when present (atomic write)
|
|
218
|
+
if [ "${live_five_pct:-}" != "null" ] && [ -n "${live_five_pct:-}" ] && [ -n "$input" ]; then
|
|
219
|
+
mkdir -p "$CACHE_DIR"
|
|
220
|
+
if ! {
|
|
221
|
+
printf '%s' "$input" | jq '{rate_limits: .rate_limits}' \
|
|
222
|
+
> "${CACHE_FILE}.tmp" 2>/dev/null \
|
|
223
|
+
&& mv "${CACHE_FILE}.tmp" "$CACHE_FILE" 2>/dev/null
|
|
224
|
+
}; then
|
|
225
|
+
:
|
|
226
|
+
fi
|
|
227
|
+
fi
|
|
228
|
+
|
|
229
|
+
new_hw_5h_pct=""
|
|
230
|
+
new_hw_5h_reset=""
|
|
231
|
+
new_hw_7d_pct=""
|
|
232
|
+
new_hw_7d_reset=""
|
|
233
|
+
if acquire_highwater_lock; then
|
|
234
|
+
apply_highwater_all
|
|
235
|
+
write_highwater
|
|
236
|
+
release_highwater_lock
|
|
237
|
+
else
|
|
238
|
+
apply_highwater_all
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
# --- Colors ---
|
|
242
|
+
RESET="\033[0m"
|
|
243
|
+
DIM="\033[2m"
|
|
244
|
+
GREEN="\033[32m"
|
|
245
|
+
YELLOW="\033[33m"
|
|
246
|
+
RED="\033[31m"
|
|
247
|
+
BLUE="\033[94m"
|
|
248
|
+
MAGENTA="\033[95m"
|
|
249
|
+
|
|
250
|
+
# Format seconds remaining as "4h23m" or "1d21h"
|
|
251
|
+
format_reset() {
|
|
252
|
+
local ts="$1"
|
|
253
|
+
[ -z "$ts" ] && return
|
|
254
|
+
local epoch now diff
|
|
255
|
+
epoch=$(printf '%s' "$ts" | tr -dc '0-9')
|
|
256
|
+
[ -z "$epoch" ] && return
|
|
257
|
+
now=$(date +%s)
|
|
258
|
+
diff=$((epoch - now))
|
|
259
|
+
[ "$diff" -le 0 ] && return
|
|
260
|
+
local mins=$(( diff / 60 ))
|
|
261
|
+
local hours=$(( mins / 60 ))
|
|
262
|
+
local days=$(( hours / 24 ))
|
|
263
|
+
if [ "$days" -ge 1 ]; then
|
|
264
|
+
printf "%dd%dh" "$days" $(( hours % 24 ))
|
|
265
|
+
elif [ "$hours" -ge 1 ]; then
|
|
266
|
+
printf "%dh%dm" "$hours" $(( mins % 60 ))
|
|
267
|
+
else
|
|
268
|
+
printf "%dm" "$mins"
|
|
269
|
+
fi
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
# Context %
|
|
273
|
+
ctx_pct=0
|
|
274
|
+
if [ "$window_size" -gt 0 ] 2>/dev/null; then
|
|
275
|
+
ctx_pct=$(awk -v u="${used_tokens:-0}" -v t="$window_size" 'BEGIN { printf "%d", (u/t)*100 }')
|
|
276
|
+
fi
|
|
277
|
+
if [ "$ctx_pct" -ge 85 ] 2>/dev/null; then
|
|
278
|
+
ctx_color="$RED"
|
|
279
|
+
elif [ "$ctx_pct" -ge 70 ] 2>/dev/null; then
|
|
280
|
+
ctx_color="$YELLOW"
|
|
281
|
+
else
|
|
282
|
+
ctx_color="$GREEN"
|
|
283
|
+
fi
|
|
284
|
+
context_part="${DIM}Context${RESET} ${ctx_color}${ctx_pct}%${RESET}"
|
|
285
|
+
|
|
286
|
+
# Usage color
|
|
287
|
+
usage_color() {
|
|
288
|
+
local pct="$1"
|
|
289
|
+
if [ "$pct" -ge 90 ] 2>/dev/null; then printf "%s" "$RED"
|
|
290
|
+
elif [ "$pct" -ge 70 ] 2>/dev/null; then printf "%s" "$MAGENTA"
|
|
291
|
+
else printf "%s" "$BLUE"
|
|
292
|
+
fi
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# 5h part
|
|
296
|
+
if [ "$five_pct" != "null" ] && [ -n "$five_pct" ]; then
|
|
297
|
+
color=$(usage_color "$five_pct")
|
|
298
|
+
reset_str=$(format_reset "$five_reset")
|
|
299
|
+
if [ -n "$reset_str" ]; then
|
|
300
|
+
five_part="${DIM}5h:${RESET} ${color}${five_pct}%${RESET} ${DIM}(${reset_str})${RESET}"
|
|
301
|
+
else
|
|
302
|
+
five_part="${DIM}5h:${RESET} ${color}${five_pct}%${RESET}"
|
|
303
|
+
fi
|
|
304
|
+
else
|
|
305
|
+
five_part="${DIM}5h: --${RESET}"
|
|
306
|
+
fi
|
|
307
|
+
|
|
308
|
+
# 7d part
|
|
309
|
+
if [ "$seven_pct" != "null" ] && [ -n "$seven_pct" ]; then
|
|
310
|
+
color=$(usage_color "$seven_pct")
|
|
311
|
+
reset_str=$(format_reset "$seven_reset")
|
|
312
|
+
if [ -n "$reset_str" ]; then
|
|
313
|
+
seven_part="${DIM}7d:${RESET} ${color}${seven_pct}%${RESET} ${DIM}(${reset_str})${RESET}"
|
|
314
|
+
else
|
|
315
|
+
seven_part="${DIM}7d:${RESET} ${color}${seven_pct}%${RESET}"
|
|
316
|
+
fi
|
|
317
|
+
else
|
|
318
|
+
seven_part="${DIM}7d: --${RESET}"
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
printf "%b | %b | %b\n" "$context_part" "$five_part" "$seven_part"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Post-package integrity check for the Claude Desktop dispatcher ZIP.
|
|
3
|
+
|
|
4
|
+
Invoked by scripts/package-skill.sh after the ZIP is unpacked into a temp
|
|
5
|
+
directory. Verifies the generated root SKILL.md exists, carries the ninja
|
|
6
|
+
marker, inlines every skill section, and has no broken nested SKILL.md path
|
|
7
|
+
references.
|
|
8
|
+
|
|
9
|
+
Lives in scripts/ (build-time only); never shipped to end users, so it stays
|
|
10
|
+
a real file rather than a heredoc.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
EXPECTED_SKILLS = ["think", "design", "check", "hunt", "write", "learn", "read", "health"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main() -> int:
|
|
24
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
25
|
+
parser.add_argument("stage", type=Path, help="Extracted ZIP root")
|
|
26
|
+
args = parser.parse_args()
|
|
27
|
+
|
|
28
|
+
root_skill = args.stage / "SKILL.md"
|
|
29
|
+
if not root_skill.exists():
|
|
30
|
+
print("POST-PACKAGE ERROR: SKILL.md missing from extracted ZIP", file=sys.stderr)
|
|
31
|
+
return 1
|
|
32
|
+
|
|
33
|
+
text = root_skill.read_text()
|
|
34
|
+
|
|
35
|
+
if "Prefix your first line with 🥷 inline" not in text:
|
|
36
|
+
print(
|
|
37
|
+
"POST-PACKAGE ERROR: root SKILL.md missing ninja prefix instruction",
|
|
38
|
+
file=sys.stderr,
|
|
39
|
+
)
|
|
40
|
+
return 1
|
|
41
|
+
|
|
42
|
+
for skill in EXPECTED_SKILLS:
|
|
43
|
+
if f"# SKILL: {skill}" not in text:
|
|
44
|
+
print(
|
|
45
|
+
f"POST-PACKAGE ERROR: SKILL section '{skill}' not inlined in root SKILL.md",
|
|
46
|
+
file=sys.stderr,
|
|
47
|
+
)
|
|
48
|
+
return 1
|
|
49
|
+
|
|
50
|
+
# The packager rewrites `skills/<name>/SKILL.md` references to the inlined
|
|
51
|
+
# section name. Any stragglers indicate a regex bug in the rewriter.
|
|
52
|
+
for skill in ("check", "think"):
|
|
53
|
+
if f"skills/{skill}/SKILL.md" in text:
|
|
54
|
+
print(
|
|
55
|
+
"POST-PACKAGE ERROR: root SKILL.md still contains nested "
|
|
56
|
+
f"SKILL.md path references (e.g. skills/{skill}/SKILL.md)",
|
|
57
|
+
file=sys.stderr,
|
|
58
|
+
)
|
|
59
|
+
return 1
|
|
60
|
+
|
|
61
|
+
print("ok: post-package validation passed")
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
sys.exit(main())
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Validate Waza skill metadata, references, marketplace, and resolver invariants.
|
|
3
|
+
|
|
4
|
+
Driver only. Validation logic lives in two sibling modules so both can be
|
|
5
|
+
imported and unit-tested without invoking argparse:
|
|
6
|
+
- `skill_frontmatter.py` -- parse_frontmatter, parse_when_to_use_keywords, fail
|
|
7
|
+
- `skill_checks.py` -- all check_* functions
|
|
8
|
+
|
|
9
|
+
Run as: python3 scripts/verify_skills.py [--root PATH] [--skills-only]
|
|
10
|
+
|
|
11
|
+
Default --root is the repository root inferred from this file's location.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
# Allow `from skill_frontmatter import ...` style imports when invoked as a
|
|
21
|
+
# standalone script. Tests and codegen import directly via sys.path tricks.
|
|
22
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
23
|
+
|
|
24
|
+
from skill_frontmatter import fail # noqa: E402
|
|
25
|
+
from skill_checks import ( # noqa: E402
|
|
26
|
+
check_attribution_leak,
|
|
27
|
+
check_description_conformance,
|
|
28
|
+
check_durable_context_and_paths,
|
|
29
|
+
check_english_coaching_guard,
|
|
30
|
+
check_marketplace,
|
|
31
|
+
check_markdown_links,
|
|
32
|
+
check_no_root_skill,
|
|
33
|
+
check_readme_install_command,
|
|
34
|
+
check_references,
|
|
35
|
+
check_resolver,
|
|
36
|
+
check_rules_files_present,
|
|
37
|
+
check_skill_files,
|
|
38
|
+
check_table_pipes,
|
|
39
|
+
check_trigger_overlap,
|
|
40
|
+
collect_all_md,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def main() -> int:
|
|
45
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
"--root",
|
|
48
|
+
type=Path,
|
|
49
|
+
default=Path(__file__).resolve().parent.parent,
|
|
50
|
+
help="Repository root (default: parent of scripts/)",
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"--skills-only",
|
|
54
|
+
action="store_true",
|
|
55
|
+
help=(
|
|
56
|
+
"Verify only per-skill frontmatter under <root>/skills/. Skips "
|
|
57
|
+
"marketplace.json, RESOLVER.md, README, and root-level checks. "
|
|
58
|
+
"Use when validating an installed copy that does not ship the "
|
|
59
|
+
"build-only files."
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
args = parser.parse_args()
|
|
63
|
+
root = args.root.resolve()
|
|
64
|
+
|
|
65
|
+
skill_files, skill_descriptions, skill_keywords = check_skill_files(root)
|
|
66
|
+
skill_names = set(skill_descriptions)
|
|
67
|
+
check_description_conformance(skill_descriptions)
|
|
68
|
+
if (root / "rules" / "durable-context.md").exists():
|
|
69
|
+
check_durable_context_and_paths(root, skill_files)
|
|
70
|
+
|
|
71
|
+
if args.skills_only:
|
|
72
|
+
# Installed copies (e.g. ~/.claude/skills/) don't ship VERSION,
|
|
73
|
+
# marketplace.json, RESOLVER.md, or the repo README. Stop here.
|
|
74
|
+
print(f"ok: skills-only verification passed for {len(skill_files)} skills")
|
|
75
|
+
return 0
|
|
76
|
+
|
|
77
|
+
version_file = root / "VERSION"
|
|
78
|
+
if not version_file.exists():
|
|
79
|
+
fail("MISSING VERSION FILE: expected top-level VERSION with one line like '3.24.0'")
|
|
80
|
+
expected_version = version_file.read_text().strip()
|
|
81
|
+
if not expected_version:
|
|
82
|
+
fail("EMPTY VERSION FILE: VERSION must contain one line like '3.24.0'")
|
|
83
|
+
|
|
84
|
+
check_marketplace(root, expected_version, skill_names, skill_descriptions)
|
|
85
|
+
check_references(root, skill_files)
|
|
86
|
+
resolver_path = check_resolver(root, skill_names)
|
|
87
|
+
all_md = collect_all_md(root, skill_names, resolver_path)
|
|
88
|
+
check_markdown_links(root, all_md)
|
|
89
|
+
check_table_pipes(root, all_md)
|
|
90
|
+
check_no_root_skill(root)
|
|
91
|
+
check_trigger_overlap(skill_keywords)
|
|
92
|
+
check_rules_files_present(root)
|
|
93
|
+
check_readme_install_command(root)
|
|
94
|
+
check_english_coaching_guard(root)
|
|
95
|
+
check_attribution_leak(root)
|
|
96
|
+
return 0
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if __name__ == "__main__":
|
|
100
|
+
sys.exit(main())
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Waza Skill Resolver
|
|
2
|
+
|
|
3
|
+
## Shared Output Marker
|
|
4
|
+
|
|
5
|
+
所有技能都沿用同一个输出约定:首行内联带上 `🥷`,不要单独起段。这个约定写在各自的 `SKILL.md` 里,`verify-skills.sh` 也会校验它。
|
|
6
|
+
|
|
7
|
+
触发词到技能的路由表。Claude Code 通过每个 SKILL.md 的 `description` 自动匹配,这份文档是给人看的集中索引,也是 `verify-skills.sh` 的校验依据。改 SKILL.md 的适用范围时,同步改这里。
|
|
8
|
+
|
|
9
|
+
> **Read the skill file before acting.** 两个技能都可能匹配时,两个都读。它们设计成可串联(例:`/think` → 实现 → `/check`)。
|
|
10
|
+
|
|
11
|
+
## 按工作流阶段分路
|
|
12
|
+
|
|
13
|
+
### Pre-build(动手前)
|
|
14
|
+
|
|
15
|
+
| 触发 | 技能 |
|
|
16
|
+
|------|------|
|
|
17
|
+
| 新功能 / 架构决策 / "怎么设计" / "应该用什么方案" / "判断一下" / "有没有必要" / "值不值得" / 商业化/产品 pivot / 需要可执行计划或 handoff | `skills/think/SKILL.md` |
|
|
18
|
+
| UI / 组件 / 页面 / 视觉界面 / 前端 / 截图里说"丑"、"不清晰"、"很怪" / 真实截图视觉 polish | `skills/design/SKILL.md` |
|
|
19
|
+
|
|
20
|
+
### Post-build(交付前)
|
|
21
|
+
|
|
22
|
+
| 触发 | 技能 |
|
|
23
|
+
|------|------|
|
|
24
|
+
| 实现完成 / 合并前 / "review 一下" / "看看这段代码" / release gate / 生成产物检查 / safety sink 审查 / `code-review` | `skills/check/SKILL.md` |
|
|
25
|
+
| release / publish / push / release reaction / 发布 / 提交 / 关闭 issue / 发布前检查 / 发布表情 / registry/appcast/asset 检查 | `skills/check/SKILL.md` (Ship / Release Follow-through) |
|
|
26
|
+
| review issue / review PR / triage / 批量处理 / "看看有没有 issue" / close issue | `skills/check/SKILL.md` (Triage Mode) |
|
|
27
|
+
| 项目体检 / project audit / 项目评分 / 给项目打分 / 深入分析项目代码 / 评估项目质量 / 代码质量评分 / scorecard / linus 风格 review / rate this codebase / score this project | `skills/check/SKILL.md` (Project Audit Mode) |
|
|
28
|
+
|
|
29
|
+
### Diagnostic(出问题了)
|
|
30
|
+
|
|
31
|
+
| 触发 | 技能 |
|
|
32
|
+
|------|------|
|
|
33
|
+
| 报错 / 崩溃 / 测试失败 / 行为异常 / "为什么不工作" / 以前是好的 / 回归 / 截图回归 / 反复修不好 / stale cache / 队列或生成物边界 | `skills/hunt/SKILL.md` |
|
|
34
|
+
| Claude/Codex 忽略指令 / hook 失灵 / MCP 异常 / Codex 配置 / AGENTS.md / config.toml / agent instructions / 配置审计 / health 消耗 token / AI coding 腐化 / 代码变烂 / 维护性 / hotspot ownership / 上下文混乱 / 验证缺失 / 验证命令失真 | `skills/health/SKILL.md` |
|
|
35
|
+
|
|
36
|
+
### Content(内容进出)
|
|
37
|
+
|
|
38
|
+
| 触发 | 技能 |
|
|
39
|
+
|------|------|
|
|
40
|
+
| 消息含 http(s) URL / 任何网页链接 / PDF 路径 / "看一下这个", "读一下这个" | `skills/read/SKILL.md` |
|
|
41
|
+
| 写作 / 改稿 / 润色 / 去 AI 味(中英文) / 推特推文 / 社交媒体文案 / launch copy / release notes 文案 | `skills/write/SKILL.md` |
|
|
42
|
+
| 文档审阅 / 白皮书 / release notes prose 审核 / "审稿" / "check this document" | `skills/write/SKILL.md` (Document Review Mode) |
|
|
43
|
+
| 深度研究一个陌生领域 / 六阶段研究到成稿 / 一批材料沉淀成文章 | `skills/learn/SKILL.md` |
|
|
44
|
+
|
|
45
|
+
## Disambiguation(歧义消解)
|
|
46
|
+
|
|
47
|
+
多个技能都可能匹配时按以下规则:
|
|
48
|
+
|
|
49
|
+
1. **最具体优先**:`/design` 比 `/think` 更具体(仅限 UI 决策)。用户说"帮我设计登录页"时优先 `/design`。
|
|
50
|
+
2. **URL 按内容类型二次分流**:消息含 URL → 先走 `/read` 取回 Markdown → 如果用户要总结或分析,继续完成总结或分析;如果是长文研究性素材再接 `/learn`。
|
|
51
|
+
3. **改错 vs review**:代码已经交付或走到 PR → `/check`;代码跑不通或行为错了 → `/hunt`。两者都可能匹配"帮我看看",按"有没有具体错误现象"判断。
|
|
52
|
+
4. **配置/维护性异常 vs 代码错误**:Claude/Codex 本身不听话、hook 不触发、MCP 掉链子、AGENTS/CLAUDE/config.toml 漂移、`/health` 消耗 token、AI coding 腐化、上下文混乱、验证缺失或验证命令失真 → `/health`;用户写的代码抛异常 → `/hunt`。
|
|
53
|
+
5. **发布动作 vs 发布文案**:要写 release notes / changelog → `/write`;要提交、打 tag、publish、push、上传 release asset、补 GitHub release reactions、回复/关闭 issue → `/check`。
|
|
54
|
+
6. **截图审美 vs 截图回归**:截图里说"丑/不好看/不清晰"且是审美校准 → `/design`;截图证明以前好的现在坏了、渲染错、状态错、生成物错 → `/hunt`。
|
|
55
|
+
7. **长文产出 vs 润色**:从零到成稿 → `/learn`;已有稿子要改 → `/write`。
|
|
56
|
+
8. **判断 vs 调试**:"判断一下" + 报错/异常/不工作 → `/hunt`(诊断问题);"判断一下" + 有没有必要/该不该保留/值不值得 → `/think` Evaluation Mode(价值判断)。
|
|
57
|
+
9. **继续优化 vs 调试**:"继续优化" / "优化代码" 不含报错或异常现象 → `/check`(代码质量改善);有具体报错或回归 → `/hunt`。
|
|
58
|
+
10. **兜底**:两个都模糊时读两个 SKILL.md 的 "Not for" 段,用排除法;还是模糊就问用户。
|
|
59
|
+
|
|
60
|
+
## Chaining(常见串联)
|
|
61
|
+
|
|
62
|
+
技能之间的转换需要用户手动触发,不会自动串联。每个技能完成后会停下来,等你决定下一步。
|
|
63
|
+
|
|
64
|
+
- `/think` 出方案 → **用户说"实现"** → 实施 → **用户说"/check"** → `/check` 把关
|
|
65
|
+
- `/think` 出可执行计划 → **用户说"Implement the plan / 可以干 / 直接改"** → 按计划实施,不重新争论方向
|
|
66
|
+
- `/hunt` 修复 issue → **用户说"发布 / push / 关闭 issue"** → `/check` 做发布前检查和收尾
|
|
67
|
+
- `/read` 取回多篇 URL → **用户说"/learn"** → `/learn` 综合成文
|
|
68
|
+
- `/learn` 出初稿 → **用户说"/write"** → `/write` 去 AI 味
|
|
69
|
+
- `/hunt` 定位根因 → **用户说"修"** → 修完 → **用户说"/check"** → `/check` 确认没副作用
|
|
70
|
+
- `/health` 发现 skill 配置问题 → **用户说"修"** → 修完 → **用户说"/health"** → 再跑一次 `/health`
|
|
71
|
+
|
|
72
|
+
## Latent vs Deterministic
|
|
73
|
+
|
|
74
|
+
Waza 的技能都是 fat skill(Markdown 判断),底层的确定性约束走 `scripts/verify-skills.sh` 和 `rules/*.md`。新加能力时先问:
|
|
75
|
+
|
|
76
|
+
- 需要判断 / 适应场景 / 追问用户?→ skill
|
|
77
|
+
- 同入同出 / 只是校验和列举?→ script 或 rule
|
|
78
|
+
|
|
79
|
+
不要把 lint 检查写成 skill,也不要把"怎么研究一个陌生领域"塞进脚本。详见根目录 `AGENTS.md` 的决策表。
|
|
80
|
+
|
|
81
|
+
## Project Context
|
|
82
|
+
|
|
83
|
+
通用程序员能力沉淀在 Waza。遇到具体项目时,先从公开项目上下文提炼约束,再执行对应技能:
|
|
84
|
+
|
|
85
|
+
- `code-review` / `/check` -> 从 diff、README、manifest、CI、release notes 中提炼验证命令、生成物、风险、safety sinks 和发布规则。
|
|
86
|
+
- `github-ops` -> 复用 `skills/check/SKILL.md` 的 Triage Mode,并从 issue/PR 现场确认 repo、发布状态和回复语言。
|
|
87
|
+
- `release` -> 从项目公开发布文档、脚本和 CI 中确认前置条件、产物和验证命令。
|
|
88
|
+
|
|
89
|
+
本地 durable memory / preview 可以作为可选私有上下文来理解用户偏好、旧决策和可迁移模式;它不属于公开项目约束,且必须用当前代码、日志、测试、文档或远端状态重新验证。
|
|
90
|
+
|
|
91
|
+
不要把证书路径、私钥文件名、token、个人机器路径或未公开的机器配置写进 Waza。
|