@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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +206 -0
  3. package/package.json +35 -0
  4. package/rules/anti-patterns.md +38 -0
  5. package/rules/chinese.md +18 -0
  6. package/rules/durable-context.md +27 -0
  7. package/rules/english.md +14 -0
  8. package/scripts/build_metadata.py +360 -0
  9. package/scripts/check_routing_drift.py +82 -0
  10. package/scripts/dispatcher-template.md +43 -0
  11. package/scripts/dispatcher.md +53 -0
  12. package/scripts/package-skill.sh +71 -0
  13. package/scripts/packaging_filter.py +55 -0
  14. package/scripts/setup-rule.sh +109 -0
  15. package/scripts/setup-statusline.sh +127 -0
  16. package/scripts/skill_checks.py +483 -0
  17. package/scripts/skill_frontmatter.py +110 -0
  18. package/scripts/statusline.sh +321 -0
  19. package/scripts/validate_package.py +66 -0
  20. package/scripts/verify_skills.py +100 -0
  21. package/skills/RESOLVER.md +91 -0
  22. package/skills/check/SKILL.md +338 -0
  23. package/skills/check/agents/reviewer-architecture.md +39 -0
  24. package/skills/check/agents/reviewer-security.md +39 -0
  25. package/skills/check/references/persona-catalog.md +56 -0
  26. package/skills/check/references/project-context.md +107 -0
  27. package/skills/check/references/public-reply.md +14 -0
  28. package/skills/check/scripts/audit_signals.py +485 -0
  29. package/skills/check/scripts/run-tests.sh +19 -0
  30. package/skills/design/SKILL.md +134 -0
  31. package/skills/design/references/design-aesthetic-quality.md +67 -0
  32. package/skills/design/references/design-data-viz.md +34 -0
  33. package/skills/design/references/design-reference.md +278 -0
  34. package/skills/design/references/design-tokens.md +53 -0
  35. package/skills/design/references/design-traps.md +43 -0
  36. package/skills/health/SKILL.md +231 -0
  37. package/skills/health/agents/inspector-context.md +119 -0
  38. package/skills/health/agents/inspector-control.md +84 -0
  39. package/skills/health/agents/inspector-maintainability.md +55 -0
  40. package/skills/health/scripts/check-agent-context.sh +5 -0
  41. package/skills/health/scripts/check-doc-refs.sh +8 -0
  42. package/skills/health/scripts/check-maintainability.sh +8 -0
  43. package/skills/health/scripts/check-verifier-output.sh +5 -0
  44. package/skills/health/scripts/check_agent_context.py +407 -0
  45. package/skills/health/scripts/check_doc_refs.py +110 -0
  46. package/skills/health/scripts/check_maintainability.py +629 -0
  47. package/skills/health/scripts/check_verifier_output.py +116 -0
  48. package/skills/health/scripts/collect-data.sh +760 -0
  49. package/skills/hunt/SKILL.md +197 -0
  50. package/skills/hunt/references/failure-patterns.md +75 -0
  51. package/skills/hunt/references/ime-unicode.md +58 -0
  52. package/skills/hunt/references/logging-techniques.md +72 -0
  53. package/skills/hunt/references/rendering-debug.md +34 -0
  54. package/skills/learn/SKILL.md +128 -0
  55. package/skills/read/SKILL.md +108 -0
  56. package/skills/read/references/read-methods.md +110 -0
  57. package/skills/read/references/save-paths.md +33 -0
  58. package/skills/read/scripts/fetch.sh +105 -0
  59. package/skills/read/scripts/fetch_feishu.py +246 -0
  60. package/skills/read/scripts/fetch_local.py +218 -0
  61. package/skills/read/scripts/fetch_weixin.py +107 -0
  62. package/skills/think/SKILL.md +155 -0
  63. package/skills/write/SKILL.md +129 -0
  64. package/skills/write/references/write-en.md +197 -0
  65. package/skills/write/references/write-zh-bilingual.md +60 -0
  66. package/skills/write/references/write-zh-prose.md +48 -0
  67. package/skills/write/references/write-zh-release-notes.md +38 -0
  68. package/skills/write/references/write-zh.md +645 -0
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env python3
2
+ """Generate Waza distribution metadata from a single source of truth.
3
+
4
+ Source of truth:
5
+ - VERSION top-level version string (single source)
6
+ - skills/<name>/SKILL.md frontmatter name, description, dispatch_intent
7
+
8
+ Generated files:
9
+ - .claude-plugin/marketplace.json full plugin manifest
10
+ - README.md install URLs pinned to VERSION
11
+ - package.json npm/Pi package metadata pinned to VERSION
12
+ - scripts/setup-rule.sh default WAZA_REF pinned to VERSION
13
+ - scripts/setup-statusline.sh default WAZA_REF pinned to VERSION
14
+
15
+ Modes:
16
+ --write (default) regenerate every file from source
17
+ --check compare generated bytes against committed files;
18
+ exit non-zero with a diff on mismatch
19
+
20
+ Run as: python3 scripts/build_metadata.py [--check] [--root PATH]
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import argparse
26
+ import difflib
27
+ import json
28
+ import re
29
+ import sys
30
+ from pathlib import Path
31
+
32
+ ROOT = Path(__file__).resolve().parent.parent
33
+ sys.path.insert(0, str(ROOT / "scripts"))
34
+
35
+ from skill_frontmatter import parse_frontmatter # noqa: E402
36
+
37
+
38
+ # Hand-maintained marketplace constants. Kept here (not in frontmatter) because
39
+ # they describe the Waza project itself, not any single skill.
40
+ MARKETPLACE_TOP = {
41
+ "$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
42
+ "name": "waza",
43
+ "description": (
44
+ "Personal skill collection for Claude Code and Codex: think, check, "
45
+ "hunt, design, read, write, learn, and health for agent config and "
46
+ "AI maintainability audits."
47
+ ),
48
+ "owner": {
49
+ "name": "Tw93",
50
+ "email": "hitw93@gmail.com",
51
+ },
52
+ }
53
+
54
+ BUNDLE_DESCRIPTION = (
55
+ "Installs the full Waza toolkit. Registers all eight skills under the "
56
+ "waza namespace, callable as /waza:think, /waza:check, /waza:hunt, "
57
+ "/waza:design, /waza:read, /waza:write, /waza:learn, and /waza:health. "
58
+ "For one skill on Claude Code v2.1.143 or newer, use /plugin install "
59
+ "waza-<name>@waza."
60
+ )
61
+
62
+ CATEGORY = "development"
63
+ HOMEPAGE = "https://github.com/tw93/Waza"
64
+
65
+
66
+ def read_version(root: Path) -> str:
67
+ version_file = root / "VERSION"
68
+ if not version_file.exists():
69
+ raise SystemExit(f"ERROR: missing VERSION file at {version_file}")
70
+ version = version_file.read_text().strip()
71
+ if not version:
72
+ raise SystemExit("ERROR: VERSION file is empty")
73
+ return version
74
+
75
+
76
+ def collect_skill_metadata(root: Path) -> list[dict]:
77
+ skill_files = sorted((root / "skills").glob("*/SKILL.md"))
78
+ if not skill_files:
79
+ raise SystemExit("ERROR: no SKILL.md files found under skills/")
80
+ skills: list[dict] = []
81
+ for path in skill_files:
82
+ fields = parse_frontmatter(path)
83
+ if fields["name"] != path.parent.name:
84
+ raise SystemExit(
85
+ f"ERROR: {path} frontmatter name={fields['name']!r} "
86
+ f"!= directory {path.parent.name!r}"
87
+ )
88
+ skills.append(fields)
89
+ return skills
90
+
91
+
92
+ def build_marketplace(version: str, skills: list[dict]) -> dict:
93
+ plugins = [
94
+ {
95
+ "name": "waza",
96
+ "description": BUNDLE_DESCRIPTION,
97
+ "version": version,
98
+ "category": CATEGORY,
99
+ "source": "./",
100
+ "homepage": HOMEPAGE,
101
+ }
102
+ ]
103
+ for skill in sorted(skills, key=lambda s: s["name"]):
104
+ plugins.append(
105
+ {
106
+ "name": f"waza-{skill['name']}",
107
+ "description": skill["description"],
108
+ "version": version,
109
+ "category": CATEGORY,
110
+ "source": f"./skills/{skill['name']}",
111
+ "homepage": HOMEPAGE,
112
+ # `skills` field declares which subdirectories under `source`
113
+ # contain SKILL.md entrypoints. "./" means the source dir itself
114
+ # is the skill root. Present on every per-skill entry; absent
115
+ # on the bundle (the bundle uses Claude Code's auto-discovery).
116
+ "skills": ["./"],
117
+ "strict": False,
118
+ }
119
+ )
120
+ return {**MARKETPLACE_TOP, "plugins": plugins}
121
+
122
+
123
+ def render_marketplace(marketplace: dict) -> str:
124
+ return json.dumps(marketplace, indent=2, ensure_ascii=False) + "\n"
125
+
126
+
127
+ def build_package_json(version: str) -> str:
128
+ package = {
129
+ "name": "@tw93/waza",
130
+ "version": version,
131
+ "description": (
132
+ "Waza engineering skills for Claude Code, Codex, Pi, and "
133
+ "compatible coding agents."
134
+ ),
135
+ "license": "MIT",
136
+ "repository": {
137
+ "type": "git",
138
+ "url": "git+https://github.com/tw93/Waza.git",
139
+ },
140
+ "homepage": "https://github.com/tw93/Waza#readme",
141
+ "keywords": [
142
+ "pi-package",
143
+ "agent-skills",
144
+ "waza",
145
+ "claude-code",
146
+ "codex",
147
+ ],
148
+ "files": [
149
+ "LICENSE",
150
+ "README.md",
151
+ "rules",
152
+ "scripts",
153
+ "skills",
154
+ "!**/__pycache__/**",
155
+ "!**/*.pyc",
156
+ ],
157
+ "publishConfig": {
158
+ "access": "public",
159
+ },
160
+ "pi": {
161
+ "skills": [
162
+ "./skills",
163
+ ],
164
+ },
165
+ }
166
+ return json.dumps(package, indent=2, ensure_ascii=False) + "\n"
167
+
168
+
169
+ ROUTING_TABLE_START = "<!-- routing-table:start -->"
170
+ ROUTING_TABLE_END = "<!-- routing-table:end -->"
171
+
172
+
173
+ def render_dispatcher(template: str, skills: list[dict]) -> str:
174
+ rows = ["| Intent | Skill | File |", "|--------|-------|------|"]
175
+ for skill in sorted(skills, key=lambda s: s["name"]):
176
+ intent = skill.get("dispatch_intent") or ""
177
+ if not intent:
178
+ raise SystemExit(
179
+ f"ERROR: skill {skill['name']} missing dispatch_intent in frontmatter"
180
+ )
181
+ rows.append(f"| {intent} | {skill['name']} | `skills/{skill['name']}/SKILL.md` |")
182
+ table = "\n".join(rows)
183
+ block = f"{ROUTING_TABLE_START}\n{table}\n{ROUTING_TABLE_END}"
184
+ if ROUTING_TABLE_START not in template or ROUTING_TABLE_END not in template:
185
+ raise SystemExit(
186
+ "ERROR: dispatcher template is missing routing-table markers"
187
+ )
188
+ pattern = re.compile(
189
+ re.escape(ROUTING_TABLE_START) + r".*?" + re.escape(ROUTING_TABLE_END),
190
+ re.DOTALL,
191
+ )
192
+ return pattern.sub(block, template)
193
+
194
+
195
+ # Matches both pinned (v3.24.0) and unpinned (main) install URLs so the
196
+ # generator can rewrite either form to the current VERSION.
197
+ README_INSTALL_URL_RE = re.compile(
198
+ r"raw\.githubusercontent\.com/tw93/Waza/(?:main|v\d+\.\d+\.\d+)/scripts/"
199
+ )
200
+ README_SWAP_TAG_RE = re.compile(r"swap `v\d+\.\d+\.\d+` for `main`")
201
+ WAZA_REF_RE = re.compile(r'WAZA_REF="\$\{WAZA_REF:-(?:main|v\d+\.\d+\.\d+)\}"')
202
+
203
+
204
+ def render_readme(current: str, version: str) -> str:
205
+ pinned = f"raw.githubusercontent.com/tw93/Waza/v{version}/scripts/"
206
+ current = README_INSTALL_URL_RE.sub(pinned, current)
207
+ return README_SWAP_TAG_RE.sub(f"swap `v{version}` for `main`", current)
208
+
209
+
210
+ def render_script_ref(current: str, version: str) -> str:
211
+ return WAZA_REF_RE.sub(f'WAZA_REF="${{WAZA_REF:-v{version}}}"', current)
212
+
213
+
214
+ def diff(label: str, expected: str, actual: str) -> str:
215
+ return "".join(
216
+ difflib.unified_diff(
217
+ actual.splitlines(keepends=True),
218
+ expected.splitlines(keepends=True),
219
+ fromfile=f"committed:{label}",
220
+ tofile=f"generated:{label}",
221
+ )
222
+ )
223
+
224
+
225
+ def main() -> int:
226
+ parser = argparse.ArgumentParser(description=__doc__)
227
+ parser.add_argument(
228
+ "--root",
229
+ type=Path,
230
+ default=ROOT,
231
+ help="Repository root (default: parent of scripts/)",
232
+ )
233
+ parser.add_argument(
234
+ "--check",
235
+ action="store_true",
236
+ help="Compare generated bytes to committed files; exit non-zero on drift.",
237
+ )
238
+ args = parser.parse_args()
239
+ root = args.root.resolve()
240
+
241
+ version = read_version(root)
242
+ skills = collect_skill_metadata(root)
243
+ marketplace = build_marketplace(version, skills)
244
+ rendered = render_marketplace(marketplace)
245
+ package_rendered = build_package_json(version)
246
+
247
+ target = root / ".claude-plugin" / "marketplace.json"
248
+ package_json = root / "package.json"
249
+ package_actual = package_json.read_text() if package_json.exists() else ""
250
+ readme = root / "README.md"
251
+ readme_actual = readme.read_text() if readme.exists() else ""
252
+ readme_rendered = render_readme(readme_actual, version)
253
+ ref_scripts = [
254
+ root / "scripts" / "setup-rule.sh",
255
+ root / "scripts" / "setup-statusline.sh",
256
+ ]
257
+ script_pairs = []
258
+ for script in ref_scripts:
259
+ actual = script.read_text() if script.exists() else ""
260
+ script_pairs.append((script, actual, render_script_ref(actual, version)))
261
+
262
+ dispatcher_template = root / "scripts" / "dispatcher-template.md"
263
+ dispatcher_target = root / "scripts" / "dispatcher.md"
264
+ if not dispatcher_template.exists():
265
+ raise SystemExit(f"ERROR: missing dispatcher template at {dispatcher_template}")
266
+ dispatcher_actual = (
267
+ dispatcher_target.read_text() if dispatcher_target.exists() else ""
268
+ )
269
+ dispatcher_rendered = render_dispatcher(dispatcher_template.read_text(), skills)
270
+
271
+ if args.check:
272
+ actual = target.read_text() if target.exists() else ""
273
+ drift = False
274
+ if actual != rendered:
275
+ print(
276
+ f"DRIFT: {target.relative_to(root)} is out of sync with "
277
+ f"VERSION + SKILL.md frontmatter.\n"
278
+ f"Run scripts/build_metadata.py (no flags) to regenerate.",
279
+ file=sys.stderr,
280
+ )
281
+ sys.stderr.write(diff("marketplace.json", rendered, actual))
282
+ drift = True
283
+ if readme_actual != readme_rendered:
284
+ print(
285
+ f"DRIFT: README.md install URLs are not pinned to v{version}.\n"
286
+ f"Run scripts/build_metadata.py (no flags) to regenerate.",
287
+ file=sys.stderr,
288
+ )
289
+ sys.stderr.write(diff("README.md", readme_rendered, readme_actual))
290
+ drift = True
291
+ if package_actual != package_rendered:
292
+ print(
293
+ f"DRIFT: package.json is out of sync with VERSION v{version} "
294
+ f"and Pi package metadata.\n"
295
+ f"Run scripts/build_metadata.py (no flags) to regenerate.",
296
+ file=sys.stderr,
297
+ )
298
+ sys.stderr.write(diff("package.json", package_rendered, package_actual))
299
+ drift = True
300
+ for script, actual, rendered_script in script_pairs:
301
+ if actual != rendered_script:
302
+ label = script.relative_to(root).as_posix()
303
+ print(
304
+ f"DRIFT: {label} default WAZA_REF is not pinned to v{version}.\n"
305
+ f"Run scripts/build_metadata.py (no flags) to regenerate.",
306
+ file=sys.stderr,
307
+ )
308
+ sys.stderr.write(diff(label, rendered_script, actual))
309
+ drift = True
310
+ if dispatcher_actual != dispatcher_rendered:
311
+ label = dispatcher_target.relative_to(root).as_posix()
312
+ print(
313
+ f"DRIFT: {label} routing table is out of sync with "
314
+ f"SKILL.md dispatch_intent frontmatter.\n"
315
+ f"Run scripts/build_metadata.py (no flags) to regenerate.",
316
+ file=sys.stderr,
317
+ )
318
+ sys.stderr.write(diff(label, dispatcher_rendered, dispatcher_actual))
319
+ drift = True
320
+ if drift:
321
+ return 1
322
+ print(f"ok: {target.relative_to(root)} matches generator")
323
+ print(f"ok: README.md install URLs pinned to v{version}")
324
+ print(f"ok: package.json pinned to v{version}")
325
+ print(f"ok: installer defaults pinned to v{version}")
326
+ print(f"ok: {dispatcher_target.relative_to(root)} matches generator")
327
+ return 0
328
+
329
+ target.parent.mkdir(parents=True, exist_ok=True)
330
+ target.write_text(rendered)
331
+ print(f"wrote: {target.relative_to(root)} ({len(rendered)} bytes)")
332
+ if package_actual != package_rendered:
333
+ package_json.write_text(package_rendered)
334
+ print(f"wrote: package.json (pinned version to v{version})")
335
+ else:
336
+ print(f"ok: package.json already pinned to v{version}")
337
+ if readme_actual != readme_rendered:
338
+ readme.write_text(readme_rendered)
339
+ print(f"wrote: README.md (pinned install URLs to v{version})")
340
+ else:
341
+ print(f"ok: README.md install URLs already pinned to v{version}")
342
+ for script, actual, rendered_script in script_pairs:
343
+ if actual != rendered_script:
344
+ script.write_text(rendered_script)
345
+ print(f"wrote: {script.relative_to(root)} (default WAZA_REF=v{version})")
346
+ else:
347
+ print(f"ok: {script.relative_to(root)} default WAZA_REF already pinned")
348
+ if dispatcher_actual != dispatcher_rendered:
349
+ dispatcher_target.write_text(dispatcher_rendered)
350
+ print(
351
+ f"wrote: {dispatcher_target.relative_to(root)} "
352
+ f"({len(dispatcher_rendered)} bytes)"
353
+ )
354
+ else:
355
+ print(f"ok: {dispatcher_target.relative_to(root)} already matches generator")
356
+ return 0
357
+
358
+
359
+ if __name__ == "__main__":
360
+ sys.exit(main())
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env python3
2
+ """Verify dispatcher routing table and RESOLVER.md cover every skill.
3
+
4
+ Both files must reference the same set of skill names found under
5
+ `skills/*/SKILL.md`. Treated as a sanity tripwire alongside the codegen in
6
+ `build_metadata.py`; cheap enough to keep even after routing tables become
7
+ generated.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import re
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ SKILL_REF_RE = re.compile(r"skills/([a-z][a-z0-9_-]*)/SKILL\.md")
18
+
19
+
20
+ def main() -> int:
21
+ parser = argparse.ArgumentParser(description=__doc__)
22
+ parser.add_argument(
23
+ "--root",
24
+ type=Path,
25
+ default=Path(__file__).resolve().parent.parent,
26
+ help="Repository root (default: parent of scripts/)",
27
+ )
28
+ args = parser.parse_args()
29
+ root = args.root.resolve()
30
+
31
+ expected = {
32
+ p.parent.name for p in (root / "skills").glob("*/SKILL.md")
33
+ }
34
+ if not expected:
35
+ print("ERROR: no skills found under skills/*/SKILL.md", file=sys.stderr)
36
+ return 1
37
+
38
+ dispatcher = set(
39
+ SKILL_REF_RE.findall((root / "scripts" / "dispatcher.md").read_text())
40
+ )
41
+ resolver = set(
42
+ SKILL_REF_RE.findall((root / "skills" / "RESOLVER.md").read_text())
43
+ )
44
+
45
+ drift = False
46
+
47
+ missing_dispatcher = expected - dispatcher
48
+ if missing_dispatcher:
49
+ print(
50
+ "ROUTING DRIFT: skills missing from scripts/dispatcher.md: "
51
+ f"{sorted(missing_dispatcher)}",
52
+ file=sys.stderr,
53
+ )
54
+ drift = True
55
+
56
+ missing_resolver = expected - resolver
57
+ if missing_resolver:
58
+ print(
59
+ f"ROUTING DRIFT: skills missing from RESOLVER.md: {sorted(missing_resolver)}",
60
+ file=sys.stderr,
61
+ )
62
+ drift = True
63
+
64
+ stale_dispatcher = dispatcher - expected
65
+ if stale_dispatcher:
66
+ print(
67
+ f"ROUTING DRIFT: stale skill refs in scripts/dispatcher.md: {sorted(stale_dispatcher)}",
68
+ file=sys.stderr,
69
+ )
70
+ drift = True
71
+
72
+ if drift:
73
+ return 1
74
+
75
+ print(
76
+ f"ok: routing consistent across {len(expected)} skills (scripts/dispatcher.md + RESOLVER.md)"
77
+ )
78
+ return 0
79
+
80
+
81
+ if __name__ == "__main__":
82
+ sys.exit(main())
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: waza
3
+ description: 'Dispatcher for Waza engineering skills: think (architecture/handoff), design (artifact-grounded UI), check (review/release gates), hunt (runtime debugging/regression), write (prose/release copy), learn (research), read (URL/PDF fetch), health (agent config and AI maintainability audit).'
4
+ ---
5
+
6
+ # Waza: Engineering Skills Dispatcher
7
+
8
+ Prefix your first line with 🥷 inline, not as its own paragraph.
9
+
10
+ You have eight skills available. Match the user's intent to the right skill, read the matching section below, and execute it.
11
+
12
+ ## Routing Table
13
+
14
+ <!-- routing-table:start -->
15
+ <!-- routing-table:end -->
16
+
17
+ ## How This Works
18
+
19
+ 1. Read the user's message and match it to a skill from the table above.
20
+ 2. Read the matched skill section in full.
21
+ 3. Execute that skill's instructions exactly.
22
+
23
+ If the message could match multiple skills, use these disambiguation rules:
24
+
25
+ 1. Most specific wins: `/design` is more specific than `/think` for UI decisions.
26
+ 2. URL in message: start with `/read`. If the content is research material, chain to `/learn`.
27
+ 3. Code already done vs. code broken: done/PR -> `/check`; error/broken -> `/hunt`.
28
+ 4. Config/maintainability vs. code: Codex/Claude misbehaving, hooks/MCP, `/health` token usage, AI coding code rot, unclear context, missing verification, or stale verifier output -> `/health`; user code errors -> `/hunt`.
29
+ 5. Release action vs. release prose: commit/tag/publish/push/release reactions/close issue -> `/check`; write release notes/changelog text -> `/write`.
30
+ 6. Screenshot taste vs. screenshot regression: visual taste complaint -> `/design`; broken render/state/generated output or used-to-work evidence -> `/hunt`.
31
+ 7. From scratch vs. editing: new long-form output -> `/learn`; existing draft to polish -> `/write`.
32
+ 8. "Judge this" + error -> `/hunt`; "judge this" + should we keep it -> `/think`.
33
+ 9. Still ambiguous: read both skills' "Not for" sections; use exclusion. If still unclear, ask the user.
34
+
35
+ ## Path Resolution
36
+
37
+ In this distribution, sub-skill scripts live at `skills/{name}/scripts/`. Resolve all relative paths from this file's directory, not from a personal home-directory skill cache.
38
+
39
+ ## Chaining
40
+
41
+ Skills chain manually, not automatically. Each skill completes and waits for the user's next action.
42
+
43
+ Common chains: `/think` -> implement approved plan -> `/check` | `/hunt` -> fix -> `/check` -> release/push/issue follow-through | `/read` -> `/learn` -> `/write`
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: waza
3
+ description: 'Dispatcher for Waza engineering skills: think (architecture/handoff), design (artifact-grounded UI), check (review/release gates), hunt (runtime debugging/regression), write (prose/release copy), learn (research), read (URL/PDF fetch), health (agent config and AI maintainability audit).'
4
+ ---
5
+
6
+ # Waza: Engineering Skills Dispatcher
7
+
8
+ Prefix your first line with 🥷 inline, not as its own paragraph.
9
+
10
+ You have eight skills available. Match the user's intent to the right skill, read the matching section below, and execute it.
11
+
12
+ ## Routing Table
13
+
14
+ <!-- routing-table:start -->
15
+ | Intent | Skill | File |
16
+ |--------|-------|------|
17
+ | Code review, before merge, release gates, generated artifacts, safety sinks, publish/push/reaction follow-through, triage issues/PRs, project-wide code-quality audit scorecard | check | `skills/check/SKILL.md` |
18
+ | UI, component, page, visual interface, frontend, artifact-grounded screenshot aesthetic complaint | design | `skills/design/SKILL.md` |
19
+ | Codex/Claude/Pi ignoring instructions, agent config audit, hooks/MCP broken, health token usage, AI coding code rot, hotspot ownership, unclear context, missing verification, stale verifier output | health | `skills/health/SKILL.md` |
20
+ | Error, crash, regression, screenshot-reported defect, test failure, stale cache, runtime boundary, why broken | hunt | `skills/hunt/SKILL.md` |
21
+ | Deep research, unfamiliar domain, compile sources into output | learn | `skills/learn/SKILL.md` |
22
+ | Any URL or PDF to fetch, read this, fetch this page | read | `skills/read/SKILL.md` |
23
+ | New feature, architecture, how should I design this, value judgment, executable plan, handoff | think | `skills/think/SKILL.md` |
24
+ | Writing, editing prose, polish, release notes, launch/social copy, remove AI tone | write | `skills/write/SKILL.md` |
25
+ <!-- routing-table:end -->
26
+
27
+ ## How This Works
28
+
29
+ 1. Read the user's message and match it to a skill from the table above.
30
+ 2. Read the matched skill section in full.
31
+ 3. Execute that skill's instructions exactly.
32
+
33
+ If the message could match multiple skills, use these disambiguation rules:
34
+
35
+ 1. Most specific wins: `/design` is more specific than `/think` for UI decisions.
36
+ 2. URL in message: start with `/read`. If the content is research material, chain to `/learn`.
37
+ 3. Code already done vs. code broken: done/PR -> `/check`; error/broken -> `/hunt`.
38
+ 4. Config/maintainability vs. code: Codex/Claude misbehaving, hooks/MCP, `/health` token usage, AI coding code rot, unclear context, missing verification, or stale verifier output -> `/health`; user code errors -> `/hunt`.
39
+ 5. Release action vs. release prose: commit/tag/publish/push/release reactions/close issue -> `/check`; write release notes/changelog text -> `/write`.
40
+ 6. Screenshot taste vs. screenshot regression: visual taste complaint -> `/design`; broken render/state/generated output or used-to-work evidence -> `/hunt`.
41
+ 7. From scratch vs. editing: new long-form output -> `/learn`; existing draft to polish -> `/write`.
42
+ 8. "Judge this" + error -> `/hunt`; "judge this" + should we keep it -> `/think`.
43
+ 9. Still ambiguous: read both skills' "Not for" sections; use exclusion. If still unclear, ask the user.
44
+
45
+ ## Path Resolution
46
+
47
+ In this distribution, sub-skill scripts live at `skills/{name}/scripts/`. Resolve all relative paths from this file's directory, not from a personal home-directory skill cache.
48
+
49
+ ## Chaining
50
+
51
+ Skills chain manually, not automatically. Each skill completes and waits for the user's next action.
52
+
53
+ Common chains: `/think` -> implement approved plan -> `/check` | `/hunt` -> fix -> `/check` -> release/push/issue follow-through | `/read` -> `/learn` -> `/write`
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ OUT="${1:-"$ROOT/dist/waza.zip"}"
6
+ case "$OUT" in
7
+ /*) ;;
8
+ *) OUT="$ROOT/$OUT" ;;
9
+ esac
10
+
11
+ mkdir -p "$(dirname "$OUT")"
12
+ rm -f "$OUT"
13
+
14
+ cd "$ROOT"
15
+
16
+ MANIFEST="$(mktemp)"
17
+ FILTERED_MANIFEST="$(mktemp)"
18
+ STAGE="$(mktemp -d)"
19
+ trap 'rm -f "$MANIFEST" "$FILTERED_MANIFEST"; rm -rf "$STAGE"' EXIT
20
+
21
+ git ls-files --cached --others --exclude-standard > "$MANIFEST"
22
+
23
+ # Default-deny: only paths matching packaging.allowlist ship. Structural
24
+ # excludes (per-skill SKILL.md, __pycache__, etc.) live inside the filter.
25
+ python3 "$ROOT/scripts/packaging_filter.py" "$ROOT/packaging.allowlist" \
26
+ < "$MANIFEST" > "$FILTERED_MANIFEST"
27
+
28
+ tar -cf - -T "$FILTERED_MANIFEST" | (cd "$STAGE" && tar -xf -)
29
+
30
+ # Dispatcher body is codegen output: scripts/dispatcher.md. Source of truth is
31
+ # scripts/dispatcher-template.md plus SKILL.md frontmatter; regenerate with
32
+ # `make regenerate` if the template or any dispatch_intent changes.
33
+ if [ ! -f "$ROOT/scripts/dispatcher.md" ]; then
34
+ echo "ERROR: scripts/dispatcher.md missing; run 'make regenerate' first" >&2
35
+ exit 1
36
+ fi
37
+ cp "$ROOT/scripts/dispatcher.md" "$STAGE/SKILL.md"
38
+
39
+ find skills -mindepth 2 -maxdepth 2 -name SKILL.md | sort | while IFS= read -r path; do
40
+ skill="$(basename "$(dirname "$path")")"
41
+ {
42
+ printf '\n---\n\n# SKILL: %s\n\n' "$skill"
43
+ awk 'BEGIN{skip=0} /^---$/{if(NR==1){skip=1;next} if(skip){skip=0;next}} !skip' "$path"
44
+ } >> "$STAGE/SKILL.md"
45
+ done
46
+
47
+ perl -0pi -e 's#`skills/([a-z][a-z0-9_-]*)/SKILL\.md`#the **$1** section below#g' "$STAGE/SKILL.md"
48
+ find "$STAGE/skills" -type d -empty -delete 2>/dev/null || true
49
+
50
+ (cd "$STAGE" && find . -type f | sed 's#^\./##' | sort | zip -q "$OUT" -@)
51
+
52
+ if ! zipinfo -1 "$OUT" | awk '$0 == "SKILL.md" { found = 1 } END { exit found ? 0 : 1 }'; then
53
+ echo "ERROR: root SKILL.md missing from $OUT" >&2
54
+ exit 1
55
+ fi
56
+
57
+ SKILL_COUNT="$(zipinfo -1 "$OUT" | awk '$0 ~ /(^|\/)SKILL\.md$/ { count++ } END { print count + 0 }')"
58
+ if [ "$SKILL_COUNT" -ne 1 ]; then
59
+ echo "ERROR: expected exactly one SKILL.md in $OUT, found $SKILL_COUNT" >&2
60
+ exit 1
61
+ fi
62
+
63
+ SIZE=$(wc -c < "$OUT" | tr -d ' ')
64
+ echo "OK: wrote $OUT (${SIZE} bytes)"
65
+
66
+ # Post-package validation lives in scripts/validate_package.py so it's
67
+ # py_compile-checked in CI and unit-testable.
68
+ VALIDATE_DIR="$(mktemp -d)"
69
+ trap 'rm -rf "$VALIDATE_DIR"' EXIT
70
+ unzip -q "$OUT" -d "$VALIDATE_DIR"
71
+ python3 "$ROOT/scripts/validate_package.py" "$VALIDATE_DIR"
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env python3
2
+ """Filter stdin paths through packaging.allowlist. Default-deny."""
3
+
4
+ import fnmatch
5
+ import re
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ EXCLUDE_RE = re.compile(
10
+ r"(^skills/[^/]+/SKILL\.md$"
11
+ r"|(^|/)__pycache__/"
12
+ r"|\.pyc$"
13
+ r"|(^|/)\.DS_Store$)"
14
+ )
15
+
16
+
17
+ def load_patterns(path: Path) -> list[str]:
18
+ patterns = []
19
+ for raw in path.read_text().splitlines():
20
+ line = raw.strip()
21
+ if not line or line.startswith("#"):
22
+ continue
23
+ patterns.append(line)
24
+ return patterns
25
+
26
+
27
+ def allowed(path: str, patterns: list[str]) -> bool:
28
+ for pat in patterns:
29
+ if pat.endswith("/**"):
30
+ prefix = pat[:-3]
31
+ if path == prefix or path.startswith(prefix + "/"):
32
+ return True
33
+ elif fnmatch.fnmatch(path, pat):
34
+ return True
35
+ return False
36
+
37
+
38
+ def main() -> int:
39
+ if len(sys.argv) != 2:
40
+ print("usage: packaging_filter.py <allowlist-path>", file=sys.stderr)
41
+ return 2
42
+ patterns = load_patterns(Path(sys.argv[1]))
43
+ for line in sys.stdin:
44
+ path = line.rstrip("\n")
45
+ if not path:
46
+ continue
47
+ if EXCLUDE_RE.search(path):
48
+ continue
49
+ if allowed(path, patterns):
50
+ print(path)
51
+ return 0
52
+
53
+
54
+ if __name__ == "__main__":
55
+ raise SystemExit(main())