@tw93/waza 3.25.0 → 3.28.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/README.md +49 -25
- package/package.json +5 -3
- package/rules/anti-patterns.md +24 -20
- package/rules/durable-context.md +6 -0
- package/rules/waza-routing.md +18 -0
- package/scripts/build_metadata.py +28 -16
- package/scripts/check_routing_drift.py +8 -0
- package/scripts/package-skill.sh +2 -3
- package/scripts/setup-rule.sh +4 -2
- package/scripts/setup-statusline.sh +1 -1
- package/scripts/skill_checks.py +290 -2
- package/scripts/statusline.sh +6 -14
- package/scripts/validate_package.py +1 -1
- package/scripts/verify_skills.py +12 -0
- package/skills/RESOLVER.md +8 -8
- package/skills/check/SKILL.md +78 -28
- package/skills/check/references/project-context.md +14 -6
- package/skills/check/scripts/audit_signals.py +192 -11
- package/skills/design/SKILL.md +39 -2
- package/skills/design/references/design-reference.md +17 -0
- package/skills/design/references/design-tokens.md +3 -11
- package/skills/health/SKILL.md +53 -26
- package/skills/health/agents/inspector-context.md +1 -1
- package/skills/health/scripts/check_agent_context.py +38 -1
- package/skills/health/scripts/check_maintainability.py +6 -0
- package/skills/health/scripts/collect-data.sh +11 -20
- package/skills/hunt/SKILL.md +33 -1
- package/skills/hunt/references/failure-patterns.md +54 -0
- package/skills/learn/SKILL.md +13 -3
- package/skills/read/SKILL.md +40 -9
- package/skills/read/references/read-methods.md +23 -4
- package/skills/read/scripts/fetch.sh +8 -7
- package/skills/read/scripts/fetch_feishu.py +11 -6
- package/skills/think/SKILL.md +33 -8
- package/skills/write/SKILL.md +88 -10
- package/skills/write/references/write-en.md +19 -17
- package/skills/write/references/write-product-localization.md +43 -0
- package/skills/write/references/write-zh-bilingual.md +2 -3
- package/skills/write/references/write-zh-prose.md +2 -0
- package/skills/write/references/write-zh.md +144 -68
- package/skills/read/references/save-paths.md +0 -33
package/scripts/skill_checks.py
CHANGED
|
@@ -25,10 +25,32 @@ URL_PREFIXES = ("http://", "https://", "mailto:", "ftp://", "tel:", "data:")
|
|
|
25
25
|
SEP_RE = re.compile(r'^[\s|:\-]+$')
|
|
26
26
|
PERSONAL_PATH_PATTERN = re.compile(r'/(?:Users|home)/[A-Za-z0-9._-]+/')
|
|
27
27
|
SKILL_REF_RE = re.compile(r'skills/([a-z][a-z0-9_-]*)/SKILL\.md')
|
|
28
|
+
# Quoted user-utterance triggers in rules/waza-routing.md. Covers straight ("),
|
|
29
|
+
# curly (U+201C/D), and CJK corner brackets (「」 U+300C/D, 『』 U+300E/F) so a
|
|
30
|
+
# Chinese phrase in 「」 is checked just like one in straight quotes.
|
|
31
|
+
QUOTED_PHRASE_RE = re.compile(
|
|
32
|
+
r'"([^"]+)"'
|
|
33
|
+
r'|\u201c([^\u201d]+)\u201d'
|
|
34
|
+
r'|\u300c([^\u300d]+)\u300d'
|
|
35
|
+
r'|\u300e([^\u300f]+)\u300f'
|
|
36
|
+
)
|
|
37
|
+
PROJECT_RITUAL_RE = re.compile(r'\b(?:Sparkle|MAS|Homebrew tap|Xcode scheme)\b', re.IGNORECASE)
|
|
38
|
+
PRIVATE_CONTEXT_RE = re.compile(
|
|
39
|
+
r'(?:\.codex/(?:sessions|memories)|rollout_summaries/|'
|
|
40
|
+
r'thread_id|rollout_path|session_meta|owner/private-repo|'
|
|
41
|
+
r'private[-_/](?:repo|project|tool)|internal[-_/](?:repo|project|tool))',
|
|
42
|
+
re.IGNORECASE,
|
|
43
|
+
)
|
|
44
|
+
FORCED_GITHUB_TOOL_RE = re.compile(
|
|
45
|
+
r'(?:Use\s+`?gh`?\s+CLI\s+for\s+all\s+GitHub\s+interactions|'
|
|
46
|
+
r'for\s+all\s+GitHub\s+interactions,\s+not\s+MCP\s+or\s+raw\s+API)',
|
|
47
|
+
re.IGNORECASE,
|
|
48
|
+
)
|
|
28
49
|
|
|
29
50
|
DURABLE_CONTEXT_SKILLS = {"think", "check", "hunt", "design", "write", "health"}
|
|
30
51
|
|
|
31
52
|
NINJA_PREFIX = "Prefix your first line with 🥷 inline, not as its own paragraph."
|
|
53
|
+
OUTCOME_CONTRACT_FIELDS = ("Outcome:", "Done when:", "Evidence:", "Output:")
|
|
32
54
|
|
|
33
55
|
# Attribution strings that indicate AI co-authorship leaked into tracked files.
|
|
34
56
|
ATTRIBUTION_PATTERNS = (
|
|
@@ -226,6 +248,26 @@ def check_description_conformance(skill_descriptions: dict[str, str]):
|
|
|
226
248
|
print(f"ok: description {skill} ({length} chars)")
|
|
227
249
|
|
|
228
250
|
|
|
251
|
+
def check_outcome_contract(skill_files: list[Path]):
|
|
252
|
+
"""Keep skill entrypoints outcome-first instead of process-heavy."""
|
|
253
|
+
for path in skill_files:
|
|
254
|
+
text = path.read_text()
|
|
255
|
+
if "## Outcome Contract" not in text:
|
|
256
|
+
fail(
|
|
257
|
+
f"MISSING OUTCOME CONTRACT: {path}\n"
|
|
258
|
+
f" Skill entrypoints must name outcome, done state, evidence, and output before detailed workflow."
|
|
259
|
+
)
|
|
260
|
+
section = text.split("## Outcome Contract", 1)[1]
|
|
261
|
+
section = section.split("\n## ", 1)[0]
|
|
262
|
+
missing = [field for field in OUTCOME_CONTRACT_FIELDS if field not in section]
|
|
263
|
+
if missing:
|
|
264
|
+
fail(
|
|
265
|
+
f"INCOMPLETE OUTCOME CONTRACT: {path}\n"
|
|
266
|
+
f" Missing fields: {', '.join(missing)}"
|
|
267
|
+
)
|
|
268
|
+
print(f"ok: outcome contract {path.parent.name}")
|
|
269
|
+
|
|
270
|
+
|
|
229
271
|
def check_durable_context_and_paths(root: Path, skill_files: list[Path]):
|
|
230
272
|
"""Durable context rules must stay portable and evidence-bound.
|
|
231
273
|
|
|
@@ -283,6 +325,53 @@ def check_durable_context_and_paths(root: Path, skill_files: list[Path]):
|
|
|
283
325
|
print(f"ok: durable context preflight for {skill}")
|
|
284
326
|
|
|
285
327
|
|
|
328
|
+
def check_portable_skill_surface(root: Path, markdown_paths: list[Path]):
|
|
329
|
+
"""Guard Waza's public skill surface against private/project-specific drift.
|
|
330
|
+
|
|
331
|
+
Waza skills should teach transferable workflow behavior. Project-specific
|
|
332
|
+
release rituals and platform products are warning signals, while
|
|
333
|
+
one-machine paths and platform-forcing commands are hard portability failures.
|
|
334
|
+
"""
|
|
335
|
+
scan_paths = list(markdown_paths)
|
|
336
|
+
scan_paths.extend(sorted((root / "rules").glob("*.md")))
|
|
337
|
+
agents = root / "AGENTS.md"
|
|
338
|
+
if agents.exists():
|
|
339
|
+
scan_paths.append(agents)
|
|
340
|
+
|
|
341
|
+
seen: set[Path] = set()
|
|
342
|
+
warning_paths: list[str] = []
|
|
343
|
+
for path in scan_paths:
|
|
344
|
+
if path in seen or not path.exists():
|
|
345
|
+
continue
|
|
346
|
+
seen.add(path)
|
|
347
|
+
rel = path.relative_to(root)
|
|
348
|
+
text = path.read_text()
|
|
349
|
+
if "~/Downloads" in text:
|
|
350
|
+
fail(
|
|
351
|
+
f"NON-PORTABLE DEFAULT SAVE PATH: {rel}\n"
|
|
352
|
+
f" Use a user-specified directory, project scratch path, or session temp directory."
|
|
353
|
+
)
|
|
354
|
+
if FORCED_GITHUB_TOOL_RE.search(text):
|
|
355
|
+
fail(
|
|
356
|
+
f"FORCED GITHUB TOOLING IN GENERIC SURFACE: {rel}\n"
|
|
357
|
+
f" GitHub projects may prefer gh, but Waza must derive platform tools from project context."
|
|
358
|
+
)
|
|
359
|
+
if PRIVATE_CONTEXT_RE.search(text):
|
|
360
|
+
fail(
|
|
361
|
+
f"PRIVATE PROJECT OR SESSION CONTEXT IN PORTABLE SURFACE: {rel}\n"
|
|
362
|
+
f" Public skills and rules must not copy private project names, session paths, "
|
|
363
|
+
f"memory paths, rollout metadata, support vendors, or thread identifiers."
|
|
364
|
+
)
|
|
365
|
+
if PROJECT_RITUAL_RE.search(text):
|
|
366
|
+
warning_paths.append(rel.as_posix())
|
|
367
|
+
if warning_paths:
|
|
368
|
+
print(
|
|
369
|
+
"warn: project-specific names or platform products in portable surface "
|
|
370
|
+
f"({', '.join(warning_paths[:8])})"
|
|
371
|
+
)
|
|
372
|
+
print("ok: portable skill surface")
|
|
373
|
+
|
|
374
|
+
|
|
286
375
|
def check_resolver(root: Path, skill_names: set[str]):
|
|
287
376
|
"""Every skill must be referenced from skills/RESOLVER.md.
|
|
288
377
|
|
|
@@ -390,7 +479,13 @@ def check_no_root_skill(root: Path):
|
|
|
390
479
|
def check_rules_files_present(root: Path):
|
|
391
480
|
"""Required shared rule files outside skills/ that the per-skill ref check
|
|
392
481
|
doesn't cover."""
|
|
393
|
-
required = [
|
|
482
|
+
required = [
|
|
483
|
+
"english.md",
|
|
484
|
+
"chinese.md",
|
|
485
|
+
"anti-patterns.md",
|
|
486
|
+
"durable-context.md",
|
|
487
|
+
"waza-routing.md",
|
|
488
|
+
]
|
|
394
489
|
for name in required:
|
|
395
490
|
path = root / "rules" / name
|
|
396
491
|
if not path.exists():
|
|
@@ -398,6 +493,139 @@ def check_rules_files_present(root: Path):
|
|
|
398
493
|
print(f"ok: rules/ files present ({', '.join(required)})")
|
|
399
494
|
|
|
400
495
|
|
|
496
|
+
def check_anti_patterns_contract(root: Path):
|
|
497
|
+
"""Keep shared anti-pattern rules generic and mechanically sane."""
|
|
498
|
+
path = root / "rules" / "anti-patterns.md"
|
|
499
|
+
if not path.exists():
|
|
500
|
+
fail(f"MISSING ANTI-PATTERNS: expected {path}")
|
|
501
|
+
|
|
502
|
+
text = path.read_text()
|
|
503
|
+
if re.search(r"\bWaza\b", text):
|
|
504
|
+
fail(
|
|
505
|
+
"ANTI-PATTERN PROJECT NAME LEAK: rules/anti-patterns.md\n"
|
|
506
|
+
" Anti-pattern rules are shared behavior. Keep row wording generic, "
|
|
507
|
+
"without repo or product names."
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
stale_terms = (
|
|
511
|
+
"Private rule leak",
|
|
512
|
+
"Project fact promoted to global skill",
|
|
513
|
+
"public Waza rules",
|
|
514
|
+
"reusable Waza skill",
|
|
515
|
+
"Keep Waza generic",
|
|
516
|
+
)
|
|
517
|
+
for term in stale_terms:
|
|
518
|
+
if term in text:
|
|
519
|
+
fail(
|
|
520
|
+
f"ANTI-PATTERN STALE SPECIALIZATION: {term!r}\n"
|
|
521
|
+
" Merge private-context and project-fact cases into one generic public-surface rule."
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
rows: list[tuple[int, str]] = []
|
|
525
|
+
for line in text.splitlines():
|
|
526
|
+
if not line.startswith("| ") or line.startswith("|---"):
|
|
527
|
+
continue
|
|
528
|
+
cells = [cell.strip() for cell in line.strip("|").split("|")]
|
|
529
|
+
if not cells or not cells[0].isdigit():
|
|
530
|
+
continue
|
|
531
|
+
rows.append((int(cells[0]), cells[1] if len(cells) > 1 else ""))
|
|
532
|
+
|
|
533
|
+
expected_numbers = list(range(1, len(rows) + 1))
|
|
534
|
+
actual_numbers = [number for number, _pattern in rows]
|
|
535
|
+
if actual_numbers != expected_numbers:
|
|
536
|
+
fail(
|
|
537
|
+
"ANTI-PATTERN NUMBERING DRIFT: rules/anti-patterns.md\n"
|
|
538
|
+
f" Expected contiguous numbering {expected_numbers}; got {actual_numbers}."
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
pattern_names: dict[str, int] = {}
|
|
542
|
+
for number, pattern in rows:
|
|
543
|
+
key = pattern.lower()
|
|
544
|
+
if key in pattern_names:
|
|
545
|
+
fail(
|
|
546
|
+
"ANTI-PATTERN DUPLICATE NAME: rules/anti-patterns.md\n"
|
|
547
|
+
f" Rows {pattern_names[key]} and {number} both use {pattern!r}."
|
|
548
|
+
)
|
|
549
|
+
pattern_names[key] = number
|
|
550
|
+
|
|
551
|
+
print("ok: anti-patterns contract")
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def check_waza_routing_skills(root: Path, skill_names: set[str]):
|
|
555
|
+
"""rules/waza-routing.md routing table must enumerate exactly the skills
|
|
556
|
+
under skills/. Structural drift only -- trigger phrases stay hand-tuned."""
|
|
557
|
+
path = root / "rules" / "waza-routing.md"
|
|
558
|
+
if not path.exists():
|
|
559
|
+
return
|
|
560
|
+
listed: set[str] = set()
|
|
561
|
+
for line in path.read_text().splitlines():
|
|
562
|
+
if not line.startswith("|"):
|
|
563
|
+
continue
|
|
564
|
+
cells = [c.strip() for c in line.split("|")]
|
|
565
|
+
if len(cells) < 3:
|
|
566
|
+
continue
|
|
567
|
+
name = cells[1]
|
|
568
|
+
# Skip table header (literal "skill") and separator rows ("---").
|
|
569
|
+
if name == "skill" or set(name) <= {"-", ":"}:
|
|
570
|
+
continue
|
|
571
|
+
if re.fullmatch(r"[a-z][a-z0-9_-]*", name):
|
|
572
|
+
listed.add(name)
|
|
573
|
+
missing = skill_names - listed
|
|
574
|
+
extra = listed - skill_names
|
|
575
|
+
if missing:
|
|
576
|
+
fail(
|
|
577
|
+
"WAZA ROUTING MISSING SKILLS: rules/waza-routing.md table omits: "
|
|
578
|
+
f"{', '.join(sorted(missing))}"
|
|
579
|
+
)
|
|
580
|
+
if extra:
|
|
581
|
+
fail(
|
|
582
|
+
"WAZA ROUTING STALE SKILLS: rules/waza-routing.md lists skills "
|
|
583
|
+
f"not in skills/: {', '.join(sorted(extra))}"
|
|
584
|
+
)
|
|
585
|
+
print(f"ok: rules/waza-routing.md skills match ({len(listed)} skills)")
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def check_waza_routing_triggers(root: Path):
|
|
589
|
+
"""Quoted user-utterance triggers in rules/waza-routing.md must be grounded.
|
|
590
|
+
|
|
591
|
+
The routing table's prose stays hand-tuned, but any phrase in quotes is a
|
|
592
|
+
claim about what a user literally types. Each quoted phrase (split on '/',
|
|
593
|
+
whitespace-normalized) must appear in the matching skill's when_to_use, so
|
|
594
|
+
the routing hint can never advertise a trigger no skill actually claims.
|
|
595
|
+
Unquoted wording is intentionally free and is not checked here.
|
|
596
|
+
"""
|
|
597
|
+
path = root / "rules" / "waza-routing.md"
|
|
598
|
+
if not path.exists():
|
|
599
|
+
return
|
|
600
|
+
norm = lambda s: re.sub(r"\s+", "", s) # noqa: E731
|
|
601
|
+
for line in path.read_text().splitlines():
|
|
602
|
+
if not line.startswith("|"):
|
|
603
|
+
continue
|
|
604
|
+
cells = [c.strip() for c in line.split("|")]
|
|
605
|
+
if len(cells) < 3:
|
|
606
|
+
continue
|
|
607
|
+
skill = cells[1]
|
|
608
|
+
if not re.fullmatch(r"[a-z][a-z0-9_-]*", skill):
|
|
609
|
+
continue
|
|
610
|
+
skill_md = root / "skills" / skill / "SKILL.md"
|
|
611
|
+
if not skill_md.exists():
|
|
612
|
+
continue # missing skill dir is check_waza_routing_skills' job
|
|
613
|
+
when = norm(parse_frontmatter(skill_md)["when_to_use"])
|
|
614
|
+
for match in QUOTED_PHRASE_RE.finditer(cells[2]):
|
|
615
|
+
quoted = next(g for g in match.groups() if g is not None)
|
|
616
|
+
for seg in quoted.split("/"):
|
|
617
|
+
seg_norm = norm(seg)
|
|
618
|
+
if seg_norm and seg_norm not in when:
|
|
619
|
+
fail(
|
|
620
|
+
f"WAZA ROUTING UNGROUNDED TRIGGER: rules/waza-routing.md "
|
|
621
|
+
f"row '{skill}' quotes {seg!r}, but it is absent from "
|
|
622
|
+
f"skills/{skill}/SKILL.md when_to_use.\n"
|
|
623
|
+
f" Quote only phrases a user actually types; align the "
|
|
624
|
+
f"phrase with when_to_use or add it to when_to_use."
|
|
625
|
+
)
|
|
626
|
+
print("ok: rules/waza-routing.md quoted triggers grounded")
|
|
627
|
+
|
|
628
|
+
|
|
401
629
|
def check_readme_install_command(root: Path):
|
|
402
630
|
"""README must show the default install command users can copy-paste."""
|
|
403
631
|
readme = root / "README.md"
|
|
@@ -410,7 +638,67 @@ def check_readme_install_command(root: Path):
|
|
|
410
638
|
f"README INSTALL COMMAND: README.md must include {expected!r}\n"
|
|
411
639
|
f" Waza's public install path depends on this exact string."
|
|
412
640
|
)
|
|
413
|
-
|
|
641
|
+
expected_pi = "pi install npm:@tw93/waza"
|
|
642
|
+
if expected_pi not in text:
|
|
643
|
+
fail(
|
|
644
|
+
f"README PI INSTALL COMMAND: README.md must include {expected_pi!r}\n"
|
|
645
|
+
f" The Pi package install path depends on this exact string."
|
|
646
|
+
)
|
|
647
|
+
expected_agents = {
|
|
648
|
+
"Antigravity": "npx skills add tw93/Waza -a antigravity -g -y",
|
|
649
|
+
"Antigravity CLI": "npx skills add tw93/Waza -a antigravity-cli -g -y",
|
|
650
|
+
"OpenCode": "npx skills add tw93/Waza -a opencode -g -y",
|
|
651
|
+
}
|
|
652
|
+
for label, command in expected_agents.items():
|
|
653
|
+
if command not in text:
|
|
654
|
+
fail(
|
|
655
|
+
f"README {label.upper()} INSTALL COMMAND: README.md must "
|
|
656
|
+
f"include {command!r}\n"
|
|
657
|
+
f" Waza's documented agent support depends on this exact string."
|
|
658
|
+
)
|
|
659
|
+
expected_installers = {
|
|
660
|
+
"setup-rule": "https://github.com/tw93/Waza/releases/latest/download/setup-rule.sh",
|
|
661
|
+
"setup-statusline": "https://github.com/tw93/Waza/releases/latest/download/setup-statusline.sh",
|
|
662
|
+
}
|
|
663
|
+
for label, url in expected_installers.items():
|
|
664
|
+
if url not in text:
|
|
665
|
+
fail(
|
|
666
|
+
f"README {label.upper()} URL: README.md must include {url!r}\n"
|
|
667
|
+
f" Installer snippets should follow the latest release asset "
|
|
668
|
+
f"without per-release README churn."
|
|
669
|
+
)
|
|
670
|
+
print(
|
|
671
|
+
"ok: README installs nested skills, Pi package, Antigravity, OpenCode, "
|
|
672
|
+
"and latest installer assets"
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def check_release_workflow_npm_surface(root: Path):
|
|
677
|
+
"""GitHub releases must publish the npm package that Pi consumes."""
|
|
678
|
+
workflow = root / ".github" / "workflows" / "release.yml"
|
|
679
|
+
if not workflow.exists():
|
|
680
|
+
fail(f"MISSING RELEASE WORKFLOW: expected {workflow}")
|
|
681
|
+
text = workflow.read_text()
|
|
682
|
+
required = {
|
|
683
|
+
"npm publish": "publishes @tw93/waza during release",
|
|
684
|
+
"npm view @tw93/waza": "re-reads the npm registry after publish",
|
|
685
|
+
"id-token: write": "allows npm trusted publishing through GitHub OIDC",
|
|
686
|
+
"node-version: 24": "uses a Node/npm runtime that supports trusted publishing",
|
|
687
|
+
"package-manager-cache: false": "keeps release publish jobs from caching credentials or package state",
|
|
688
|
+
"github.event.release.tag_name": "checks the GitHub release tag",
|
|
689
|
+
"package.json').pi.skills[0]": "checks Pi package metadata",
|
|
690
|
+
"dist-tags.latest": "confirms the npm latest dist-tag",
|
|
691
|
+
"scripts/setup-rule.sh": "uploads the rule installer as a latest release asset",
|
|
692
|
+
"scripts/setup-statusline.sh": "uploads the statusline installer as a latest release asset",
|
|
693
|
+
}
|
|
694
|
+
missing = [label for label, reason in required.items() if label not in text]
|
|
695
|
+
if missing:
|
|
696
|
+
fail(
|
|
697
|
+
"RELEASE WORKFLOW NPM SURFACE: .github/workflows/release.yml "
|
|
698
|
+
"must publish and verify @tw93/waza for Pi installs.\n"
|
|
699
|
+
+ "\n".join(f" missing {label!r}: {required[label]}" for label in missing)
|
|
700
|
+
)
|
|
701
|
+
print("ok: release workflow publishes npm package and installer assets")
|
|
414
702
|
|
|
415
703
|
|
|
416
704
|
def check_english_coaching_guard(root: Path):
|
package/scripts/statusline.sh
CHANGED
|
@@ -88,8 +88,8 @@ read_highwater() {
|
|
|
88
88
|
|
|
89
89
|
# apply_hw: compares live vs high-water marks for a single counter (5h or 7d).
|
|
90
90
|
# Mutates four caller-scope globals by name (no return, by design):
|
|
91
|
-
# applied_pct, applied_reset
|
|
92
|
-
# applied_hw_pct, applied_hw_reset
|
|
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
93
|
# Caller must read these immediately after the call; the next invocation
|
|
94
94
|
# clobbers them. Side effect is intentional: bash can't return composite values
|
|
95
95
|
# cleanly, and threading four out-params through every call site was worse.
|
|
@@ -132,18 +132,10 @@ apply_hw() {
|
|
|
132
132
|
applied_hw_reset="$hw_reset"
|
|
133
133
|
return
|
|
134
134
|
fi
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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"
|
|
135
|
+
applied_pct="$live_pct"
|
|
136
|
+
applied_reset="$live_reset"
|
|
137
|
+
applied_hw_pct="$live_pct"
|
|
138
|
+
applied_hw_reset="$live_reset"
|
|
147
139
|
}
|
|
148
140
|
|
|
149
141
|
write_highwater() {
|
|
@@ -49,7 +49,7 @@ def main() -> int:
|
|
|
49
49
|
|
|
50
50
|
# The packager rewrites `skills/<name>/SKILL.md` references to the inlined
|
|
51
51
|
# section name. Any stragglers indicate a regex bug in the rewriter.
|
|
52
|
-
for skill in
|
|
52
|
+
for skill in EXPECTED_SKILLS:
|
|
53
53
|
if f"skills/{skill}/SKILL.md" in text:
|
|
54
54
|
print(
|
|
55
55
|
"POST-PACKAGE ERROR: root SKILL.md still contains nested "
|
package/scripts/verify_skills.py
CHANGED
|
@@ -23,6 +23,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
|
23
23
|
|
|
24
24
|
from skill_frontmatter import fail # noqa: E402
|
|
25
25
|
from skill_checks import ( # noqa: E402
|
|
26
|
+
check_anti_patterns_contract,
|
|
26
27
|
check_attribution_leak,
|
|
27
28
|
check_description_conformance,
|
|
28
29
|
check_durable_context_and_paths,
|
|
@@ -30,13 +31,18 @@ from skill_checks import ( # noqa: E402
|
|
|
30
31
|
check_marketplace,
|
|
31
32
|
check_markdown_links,
|
|
32
33
|
check_no_root_skill,
|
|
34
|
+
check_outcome_contract,
|
|
35
|
+
check_portable_skill_surface,
|
|
33
36
|
check_readme_install_command,
|
|
37
|
+
check_release_workflow_npm_surface,
|
|
34
38
|
check_references,
|
|
35
39
|
check_resolver,
|
|
36
40
|
check_rules_files_present,
|
|
37
41
|
check_skill_files,
|
|
38
42
|
check_table_pipes,
|
|
39
43
|
check_trigger_overlap,
|
|
44
|
+
check_waza_routing_skills,
|
|
45
|
+
check_waza_routing_triggers,
|
|
40
46
|
collect_all_md,
|
|
41
47
|
)
|
|
42
48
|
|
|
@@ -65,6 +71,7 @@ def main() -> int:
|
|
|
65
71
|
skill_files, skill_descriptions, skill_keywords = check_skill_files(root)
|
|
66
72
|
skill_names = set(skill_descriptions)
|
|
67
73
|
check_description_conformance(skill_descriptions)
|
|
74
|
+
check_outcome_contract(skill_files)
|
|
68
75
|
if (root / "rules" / "durable-context.md").exists():
|
|
69
76
|
check_durable_context_and_paths(root, skill_files)
|
|
70
77
|
|
|
@@ -85,12 +92,17 @@ def main() -> int:
|
|
|
85
92
|
check_references(root, skill_files)
|
|
86
93
|
resolver_path = check_resolver(root, skill_names)
|
|
87
94
|
all_md = collect_all_md(root, skill_names, resolver_path)
|
|
95
|
+
check_portable_skill_surface(root, all_md)
|
|
88
96
|
check_markdown_links(root, all_md)
|
|
89
97
|
check_table_pipes(root, all_md)
|
|
90
98
|
check_no_root_skill(root)
|
|
91
99
|
check_trigger_overlap(skill_keywords)
|
|
92
100
|
check_rules_files_present(root)
|
|
101
|
+
check_anti_patterns_contract(root)
|
|
102
|
+
check_waza_routing_skills(root, skill_names)
|
|
103
|
+
check_waza_routing_triggers(root)
|
|
93
104
|
check_readme_install_command(root)
|
|
105
|
+
check_release_workflow_npm_surface(root)
|
|
94
106
|
check_english_coaching_guard(root)
|
|
95
107
|
check_attribution_leak(root)
|
|
96
108
|
return 0
|
package/skills/RESOLVER.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Shared Output Marker
|
|
4
4
|
|
|
5
|
-
所有技能都沿用同一个输出约定:首行内联带上 `🥷`,不要单独起段。这个约定写在各自的 `SKILL.md` 里,`
|
|
5
|
+
所有技能都沿用同一个输出约定:首行内联带上 `🥷`,不要单独起段。这个约定写在各自的 `SKILL.md` 里,`scripts/verify_skills.py` 也会校验它。
|
|
6
6
|
|
|
7
|
-
触发词到技能的路由表。Claude Code 通过每个 SKILL.md 的 `description` 自动匹配,这份文档是给人看的集中索引,也是 `
|
|
7
|
+
触发词到技能的路由表。Claude Code 通过每个 SKILL.md 的 `description` 自动匹配,这份文档是给人看的集中索引,也是 `scripts/verify_skills.py` 的校验依据。改 SKILL.md 的适用范围时,同步改这里。
|
|
8
8
|
|
|
9
9
|
> **Read the skill file before acting.** 两个技能都可能匹配时,两个都读。它们设计成可串联(例:`/think` → 实现 → `/check`)。
|
|
10
10
|
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
| 触发 | 技能 |
|
|
39
39
|
|------|------|
|
|
40
40
|
| 消息含 http(s) URL / 任何网页链接 / PDF 路径 / "看一下这个", "读一下这个" | `skills/read/SKILL.md` |
|
|
41
|
-
| 写作 / 改稿 / 润色 / 去 AI 味(中英文) / 推特推文 / 社交媒体文案 / launch copy / release notes 文案 | `skills/write/SKILL.md` |
|
|
41
|
+
| 写作 / 改稿 / 润色 / 去 AI 味(中英文) / 本地化文案 / 多语言产品文案 / 推特推文 / 社交媒体文案 / launch copy / release notes 文案 | `skills/write/SKILL.md` |
|
|
42
42
|
| 文档审阅 / 白皮书 / release notes prose 审核 / "审稿" / "check this document" | `skills/write/SKILL.md` (Document Review Mode) |
|
|
43
43
|
| 深度研究一个陌生领域 / 六阶段研究到成稿 / 一批材料沉淀成文章 | `skills/learn/SKILL.md` |
|
|
44
44
|
|
|
@@ -59,19 +59,19 @@
|
|
|
59
59
|
|
|
60
60
|
## Chaining(常见串联)
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
技能之间默认不自动串联。每个技能完成后会停下来,等用户决定下一步,除非当前请求或项目公开上下文已经明确授权后续动作(例如 "implement this plan", "review then ship if green", "triage and close")。
|
|
63
63
|
|
|
64
64
|
- `/think` 出方案 → **用户说"实现"** → 实施 → **用户说"/check"** → `/check` 把关
|
|
65
65
|
- `/think` 出可执行计划 → **用户说"Implement the plan / 可以干 / 直接改"** → 按计划实施,不重新争论方向
|
|
66
66
|
- `/hunt` 修复 issue → **用户说"发布 / push / 关闭 issue"** → `/check` 做发布前检查和收尾
|
|
67
|
-
- `/read` 取回多篇 URL → **用户说"/learn"** → `/learn`
|
|
67
|
+
- `/read` 取回多篇 URL → **用户说"/learn"** → `/learn` 综合成文;如果同一回合已经明确要求总结或分析,`/read` fetch 后直接满足该请求
|
|
68
68
|
- `/learn` 出初稿 → **用户说"/write"** → `/write` 去 AI 味
|
|
69
69
|
- `/hunt` 定位根因 → **用户说"修"** → 修完 → **用户说"/check"** → `/check` 确认没副作用
|
|
70
70
|
- `/health` 发现 skill 配置问题 → **用户说"修"** → 修完 → **用户说"/health"** → 再跑一次 `/health`
|
|
71
71
|
|
|
72
72
|
## Latent vs Deterministic
|
|
73
73
|
|
|
74
|
-
Waza 的技能都是 fat skill(Markdown 判断),底层的确定性约束走 `scripts/
|
|
74
|
+
Waza 的技能都是 fat skill(Markdown 判断),底层的确定性约束走 `scripts/verify_skills.py` 和 `rules/*.md`。新加能力时先问:
|
|
75
75
|
|
|
76
76
|
- 需要判断 / 适应场景 / 追问用户?→ skill
|
|
77
77
|
- 同入同出 / 只是校验和列举?→ script 或 rule
|
|
@@ -83,8 +83,8 @@ Waza 的技能都是 fat skill(Markdown 判断),底层的确定性约束
|
|
|
83
83
|
通用程序员能力沉淀在 Waza。遇到具体项目时,先从公开项目上下文提炼约束,再执行对应技能:
|
|
84
84
|
|
|
85
85
|
- `code-review` / `/check` -> 从 diff、README、manifest、CI、release notes 中提炼验证命令、生成物、风险、safety sinks 和发布规则。
|
|
86
|
-
-
|
|
87
|
-
- `
|
|
86
|
+
- GitHub issue/PR/release intents -> 由 `skills/check/SKILL.md` 处理。目标是 GitHub 时优先使用 `gh` 或可用的 GitHub 工具;非 GitHub 平台按项目公开上下文选择对应 CLI/API。
|
|
87
|
+
- Release/publish intents -> 由 `skills/check/SKILL.md` 从项目公开发布文档、脚本和 CI 中确认前置条件、产物和验证命令。
|
|
88
88
|
|
|
89
89
|
本地 durable memory / preview 可以作为可选私有上下文来理解用户偏好、旧决策和可迁移模式;它不属于公开项目约束,且必须用当前代码、日志、测试、文档或远端状态重新验证。
|
|
90
90
|
|