@event4u/agent-config 1.33.0 → 1.34.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 (200) hide show
  1. package/.agent-src/commands/review-changes.md +13 -8
  2. package/.agent-src/personas/README.md +12 -21
  3. package/.agent-src/personas/_template-specialist/persona.md +89 -0
  4. package/.agent-src/personas/backend-architect.md +96 -0
  5. package/.agent-src/personas/eloquent-tamer.md +96 -0
  6. package/.agent-src/personas/frontend-engineer.md +100 -0
  7. package/.agent-src/personas/qa.md +27 -2
  8. package/.agent-src/personas/security-engineer.md +100 -0
  9. package/.agent-src/skills/accessibility-auditor/SKILL.md +132 -0
  10. package/.agent-src/skills/adr-create/SKILL.md +1 -0
  11. package/.agent-src/skills/adversarial-review/SKILL.md +1 -0
  12. package/.agent-src/skills/agent-docs-writing/SKILL.md +1 -0
  13. package/.agent-src/skills/agents-md-thin-root/SKILL.md +1 -0
  14. package/.agent-src/skills/ai-council/SKILL.md +1 -0
  15. package/.agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -0
  16. package/.agent-src/skills/analysis-skill-router/SKILL.md +1 -0
  17. package/.agent-src/skills/api-design/SKILL.md +3 -0
  18. package/.agent-src/skills/api-endpoint/SKILL.md +1 -0
  19. package/.agent-src/skills/api-testing/SKILL.md +1 -0
  20. package/.agent-src/skills/architecture-review-lens/SKILL.md +137 -0
  21. package/.agent-src/skills/artisan-commands/SKILL.md +1 -0
  22. package/.agent-src/skills/async-python-patterns/SKILL.md +1 -0
  23. package/.agent-src/skills/authz-review/SKILL.md +4 -0
  24. package/.agent-src/skills/aws-infrastructure/SKILL.md +1 -0
  25. package/.agent-src/skills/blade-ui/SKILL.md +1 -0
  26. package/.agent-src/skills/blast-radius-analyzer/SKILL.md +3 -0
  27. package/.agent-src/skills/bug-analyzer/SKILL.md +1 -0
  28. package/.agent-src/skills/check-refs/SKILL.md +1 -0
  29. package/.agent-src/skills/code-refactoring/SKILL.md +1 -0
  30. package/.agent-src/skills/code-review/SKILL.md +1 -0
  31. package/.agent-src/skills/command-routing/SKILL.md +1 -0
  32. package/.agent-src/skills/command-writing/SKILL.md +1 -0
  33. package/.agent-src/skills/composer-packages/SKILL.md +1 -0
  34. package/.agent-src/skills/context-authoring/SKILL.md +1 -0
  35. package/.agent-src/skills/context-document/SKILL.md +1 -0
  36. package/.agent-src/skills/conventional-commits-writing/SKILL.md +1 -0
  37. package/.agent-src/skills/copilot-agents-optimization/SKILL.md +1 -0
  38. package/.agent-src/skills/copilot-config/SKILL.md +1 -0
  39. package/.agent-src/skills/dashboard-design/SKILL.md +1 -0
  40. package/.agent-src/skills/data-flow-mapper/SKILL.md +1 -0
  41. package/.agent-src/skills/database/SKILL.md +3 -0
  42. package/.agent-src/skills/dcf-modeling/SKILL.md +1 -0
  43. package/.agent-src/skills/decision-record/SKILL.md +143 -0
  44. package/.agent-src/skills/deep-reading-analyst/SKILL.md +1 -0
  45. package/.agent-src/skills/defense-in-depth/SKILL.md +1 -0
  46. package/.agent-src/skills/dependency-upgrade/SKILL.md +1 -0
  47. package/.agent-src/skills/description-assist/SKILL.md +1 -0
  48. package/.agent-src/skills/design-review/SKILL.md +1 -0
  49. package/.agent-src/skills/devcontainer/SKILL.md +1 -0
  50. package/.agent-src/skills/developer-like-execution/SKILL.md +1 -0
  51. package/.agent-src/skills/docker/SKILL.md +1 -0
  52. package/.agent-src/skills/dto-creator/SKILL.md +1 -0
  53. package/.agent-src/skills/eloquent/SKILL.md +3 -0
  54. package/.agent-src/skills/error-handling-patterns/SKILL.md +1 -0
  55. package/.agent-src/skills/estimate-ticket/SKILL.md +1 -0
  56. package/.agent-src/skills/existing-ui-audit/SKILL.md +3 -0
  57. package/.agent-src/skills/fe-design/SKILL.md +4 -1
  58. package/.agent-src/skills/feature-planning/SKILL.md +1 -0
  59. package/.agent-src/skills/file-editor/SKILL.md +1 -0
  60. package/.agent-src/skills/finishing-a-development-branch/SKILL.md +1 -0
  61. package/.agent-src/skills/flux/SKILL.md +1 -0
  62. package/.agent-src/skills/form-handler/SKILL.md +145 -0
  63. package/.agent-src/skills/funnel-analysis/SKILL.md +1 -0
  64. package/.agent-src/skills/git-workflow/SKILL.md +1 -0
  65. package/.agent-src/skills/github-ci/SKILL.md +1 -0
  66. package/.agent-src/skills/grafana/SKILL.md +1 -0
  67. package/.agent-src/skills/guideline-writing/SKILL.md +1 -0
  68. package/.agent-src/skills/incident-commander/SKILL.md +140 -0
  69. package/.agent-src/skills/jira-integration/SKILL.md +1 -0
  70. package/.agent-src/skills/jobs-events/SKILL.md +1 -0
  71. package/.agent-src/skills/judge-bug-hunter/SKILL.md +1 -0
  72. package/.agent-src/skills/judge-code-quality/SKILL.md +1 -0
  73. package/.agent-src/skills/judge-security-auditor/SKILL.md +3 -0
  74. package/.agent-src/skills/judge-test-coverage/SKILL.md +1 -0
  75. package/.agent-src/skills/laravel/SKILL.md +1 -0
  76. package/.agent-src/skills/laravel-horizon/SKILL.md +1 -0
  77. package/.agent-src/skills/laravel-mail/SKILL.md +1 -0
  78. package/.agent-src/skills/laravel-middleware/SKILL.md +1 -0
  79. package/.agent-src/skills/laravel-notifications/SKILL.md +1 -0
  80. package/.agent-src/skills/laravel-pennant/SKILL.md +1 -0
  81. package/.agent-src/skills/laravel-pulse/SKILL.md +1 -0
  82. package/.agent-src/skills/laravel-reverb/SKILL.md +1 -0
  83. package/.agent-src/skills/laravel-scheduling/SKILL.md +1 -0
  84. package/.agent-src/skills/laravel-validation/SKILL.md +1 -0
  85. package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +1 -0
  86. package/.agent-src/skills/lint-skills/SKILL.md +1 -0
  87. package/.agent-src/skills/livewire/SKILL.md +1 -0
  88. package/.agent-src/skills/livewire-architect/SKILL.md +158 -0
  89. package/.agent-src/skills/logging-monitoring/SKILL.md +1 -0
  90. package/.agent-src/skills/markitdown/SKILL.md +1 -0
  91. package/.agent-src/skills/mcp/SKILL.md +1 -0
  92. package/.agent-src/skills/mcp-builder/SKILL.md +1 -0
  93. package/.agent-src/skills/md-language-check/SKILL.md +1 -0
  94. package/.agent-src/skills/merge-conflicts/SKILL.md +1 -0
  95. package/.agent-src/skills/migration-architect/SKILL.md +119 -0
  96. package/.agent-src/skills/migration-creator/SKILL.md +1 -0
  97. package/.agent-src/skills/mobile-e2e-strategy/SKILL.md +2 -1
  98. package/.agent-src/skills/module-management/SKILL.md +1 -0
  99. package/.agent-src/skills/multi-tenancy/SKILL.md +1 -0
  100. package/.agent-src/skills/okr-tree-modeling/SKILL.md +1 -0
  101. package/.agent-src/skills/openapi/SKILL.md +1 -0
  102. package/.agent-src/skills/override-management/SKILL.md +1 -0
  103. package/.agent-src/skills/performance/SKILL.md +1 -0
  104. package/.agent-src/skills/performance-analysis/SKILL.md +1 -0
  105. package/.agent-src/skills/persona-writing/SKILL.md +1 -0
  106. package/.agent-src/skills/pest-testing/SKILL.md +1 -0
  107. package/.agent-src/skills/php-coder/SKILL.md +1 -0
  108. package/.agent-src/skills/php-debugging/SKILL.md +1 -0
  109. package/.agent-src/skills/php-service/SKILL.md +1 -0
  110. package/.agent-src/skills/playwright-architect/SKILL.md +141 -0
  111. package/.agent-src/skills/playwright-testing/SKILL.md +1 -0
  112. package/.agent-src/skills/po-discovery/SKILL.md +127 -0
  113. package/.agent-src/skills/project-analysis-core/SKILL.md +1 -0
  114. package/.agent-src/skills/project-analysis-hypothesis-driven/SKILL.md +1 -0
  115. package/.agent-src/skills/project-analysis-laravel/SKILL.md +1 -0
  116. package/.agent-src/skills/project-analysis-nextjs/SKILL.md +1 -0
  117. package/.agent-src/skills/project-analysis-node-express/SKILL.md +1 -0
  118. package/.agent-src/skills/project-analysis-react/SKILL.md +1 -0
  119. package/.agent-src/skills/project-analysis-symfony/SKILL.md +1 -0
  120. package/.agent-src/skills/project-analysis-zend-laminas/SKILL.md +1 -0
  121. package/.agent-src/skills/project-analyzer/SKILL.md +1 -0
  122. package/.agent-src/skills/project-docs/SKILL.md +1 -0
  123. package/.agent-src/skills/prompt-engineering-patterns/SKILL.md +1 -0
  124. package/.agent-src/skills/prompt-optimizer/SKILL.md +1 -0
  125. package/.agent-src/skills/quality-tools/SKILL.md +1 -0
  126. package/.agent-src/skills/react-native-setup/SKILL.md +1 -0
  127. package/.agent-src/skills/react-shadcn-ui/SKILL.md +1 -0
  128. package/.agent-src/skills/readme-reviewer/SKILL.md +1 -0
  129. package/.agent-src/skills/readme-writing/SKILL.md +1 -0
  130. package/.agent-src/skills/readme-writing-package/SKILL.md +1 -0
  131. package/.agent-src/skills/receiving-code-review/SKILL.md +1 -0
  132. package/.agent-src/skills/refine-prompt/SKILL.md +1 -0
  133. package/.agent-src/skills/refine-ticket/SKILL.md +1 -0
  134. package/.agent-src/skills/repomix-packer/SKILL.md +1 -0
  135. package/.agent-src/skills/requesting-code-review/SKILL.md +1 -0
  136. package/.agent-src/skills/review-routing/SKILL.md +1 -0
  137. package/.agent-src/skills/rice-prioritization/SKILL.md +1 -0
  138. package/.agent-src/skills/risk-officer/SKILL.md +141 -0
  139. package/.agent-src/skills/roadmap-management/SKILL.md +1 -0
  140. package/.agent-src/skills/roadmap-writing/SKILL.md +1 -0
  141. package/.agent-src/skills/rtk-output-filtering/SKILL.md +1 -0
  142. package/.agent-src/skills/rule-writing/SKILL.md +1 -0
  143. package/.agent-src/skills/script-writing/SKILL.md +1 -0
  144. package/.agent-src/skills/secrets-management/SKILL.md +1 -0
  145. package/.agent-src/skills/security/SKILL.md +1 -0
  146. package/.agent-src/skills/security-audit/SKILL.md +1 -0
  147. package/.agent-src/skills/sentry-integration/SKILL.md +1 -0
  148. package/.agent-src/skills/sequential-thinking/SKILL.md +1 -0
  149. package/.agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -0
  150. package/.agent-src/skills/skill-management/SKILL.md +1 -0
  151. package/.agent-src/skills/skill-reviewer/SKILL.md +1 -0
  152. package/.agent-src/skills/skill-writing/SKILL.md +1 -0
  153. package/.agent-src/skills/sql-writing/SKILL.md +1 -0
  154. package/.agent-src/skills/stakeholder-tradeoff/SKILL.md +149 -0
  155. package/.agent-src/skills/subagent-orchestration/SKILL.md +13 -0
  156. package/.agent-src/skills/systematic-debugging/SKILL.md +1 -0
  157. package/.agent-src/skills/tailwind-engineer/SKILL.md +130 -0
  158. package/.agent-src/skills/tech-debt-tracker/SKILL.md +152 -0
  159. package/.agent-src/skills/technical-specification/SKILL.md +1 -0
  160. package/.agent-src/skills/terraform/SKILL.md +1 -0
  161. package/.agent-src/skills/terragrunt/SKILL.md +1 -0
  162. package/.agent-src/skills/test-driven-development/SKILL.md +1 -0
  163. package/.agent-src/skills/test-performance/SKILL.md +1 -0
  164. package/.agent-src/skills/testing-anti-patterns/SKILL.md +1 -0
  165. package/.agent-src/skills/threat-modeling/SKILL.md +3 -0
  166. package/.agent-src/skills/token-optimizer/SKILL.md +1 -0
  167. package/.agent-src/skills/traefik/SKILL.md +1 -0
  168. package/.agent-src/skills/ui-component-architect/SKILL.md +153 -0
  169. package/.agent-src/skills/unit-economics-modeling/SKILL.md +1 -0
  170. package/.agent-src/skills/universal-project-analysis/SKILL.md +1 -0
  171. package/.agent-src/skills/upstream-contribute/SKILL.md +1 -0
  172. package/.agent-src/skills/using-git-worktrees/SKILL.md +1 -0
  173. package/.agent-src/skills/validate-feature-fit/SKILL.md +1 -0
  174. package/.agent-src/skills/verify-completion-evidence/SKILL.md +1 -0
  175. package/.agent-src/skills/websocket/SKILL.md +1 -0
  176. package/.claude-plugin/marketplace.json +15 -1
  177. package/AGENTS.md +1 -0
  178. package/CHANGELOG.md +41 -0
  179. package/README.md +2 -2
  180. package/docs/architecture.md +1 -1
  181. package/docs/catalog.md +17 -3
  182. package/docs/contracts/file-ownership-matrix.json +506 -0
  183. package/docs/contracts/persona-schema.md +136 -0
  184. package/docs/contracts/skill-domains.md +143 -0
  185. package/docs/decisions/ADR-005-subagent-worktrees.md +120 -0
  186. package/docs/decisions/ADR-006-skill-tools-python-pilot.md +114 -0
  187. package/docs/decisions/INDEX.md +3 -0
  188. package/docs/personas.md +115 -0
  189. package/package.json +1 -1
  190. package/scripts/_backfill_skill_domains.py +140 -0
  191. package/scripts/_emit_domain_table.py +35 -0
  192. package/scripts/install-hooks.sh +21 -4
  193. package/scripts/lint_skill_tools.py +168 -0
  194. package/scripts/schemas/skill.schema.json +6 -1
  195. package/scripts/skill_linter.py +19 -4
  196. package/scripts/skill_tools/__init__.py +22 -0
  197. package/scripts/skill_tools/audit_persona_coverage.py +147 -0
  198. package/scripts/skill_tools/run_block_d_eval.py +129 -0
  199. package/scripts/skill_tools/score_skill_relevance.py +169 -0
  200. package/scripts/skill_tools/suggest_skill_for_task.py +113 -0
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env python3
2
+ """One-shot back-fill: inject `domain:` frontmatter into every SKILL.md.
3
+
4
+ Removed after B3 lands. Source-of-truth = SKILL_DOMAIN_MAP below.
5
+ Fails loudly if the map and on-disk skill set diverge.
6
+ """
7
+ from __future__ import annotations
8
+ import re
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ SKILL_DOMAIN_MAP: dict[str, str] = {
13
+ "adr-create": "process", "adversarial-review": "quality",
14
+ "agent-docs-writing": "process", "agents-md-thin-root": "process",
15
+ "ai-council": "process", "analysis-autonomous-mode": "discovery",
16
+ "analysis-skill-router": "discovery", "api-design": "engineering",
17
+ "api-endpoint": "engineering", "api-testing": "quality",
18
+ "artisan-commands": "engineering", "async-python-patterns": "engineering",
19
+ "authz-review": "quality", "aws-infrastructure": "devops",
20
+ "blade-ui": "engineering", "blast-radius-analyzer": "discovery",
21
+ "bug-analyzer": "discovery", "check-refs": "process",
22
+ "code-refactoring": "engineering", "code-review": "quality",
23
+ "command-routing": "process", "command-writing": "process",
24
+ "composer-packages": "engineering", "context-authoring": "process",
25
+ "context-document": "process", "conventional-commits-writing": "process",
26
+ "copilot-agents-optimization": "process", "copilot-config": "process",
27
+ "dashboard-design": "devops", "data-flow-mapper": "discovery",
28
+ "database": "engineering", "dcf-modeling": "product",
29
+ "deep-reading-analyst": "discovery", "defense-in-depth": "quality",
30
+ "dependency-upgrade": "engineering", "description-assist": "process",
31
+ "design-review": "quality", "devcontainer": "devops",
32
+ "developer-like-execution": "process", "docker": "devops",
33
+ "dto-creator": "engineering", "eloquent": "engineering",
34
+ "error-handling-patterns": "engineering", "estimate-ticket": "product",
35
+ "existing-ui-audit": "discovery", "fe-design": "engineering",
36
+ "feature-planning": "product", "file-editor": "process",
37
+ "finishing-a-development-branch": "process", "flux": "engineering",
38
+ "funnel-analysis": "product", "git-workflow": "process",
39
+ "github-ci": "devops", "grafana": "devops",
40
+ "guideline-writing": "process", "jira-integration": "process",
41
+ "jobs-events": "engineering", "judge-bug-hunter": "quality",
42
+ "judge-code-quality": "quality", "judge-security-auditor": "quality",
43
+ "judge-test-coverage": "quality", "laravel": "engineering",
44
+ "laravel-horizon": "engineering", "laravel-mail": "engineering",
45
+ "laravel-middleware": "engineering", "laravel-notifications": "engineering",
46
+ "laravel-pennant": "engineering", "laravel-pulse": "engineering",
47
+ "laravel-reverb": "engineering", "laravel-scheduling": "engineering",
48
+ "laravel-validation": "engineering", "learning-to-rule-or-skill": "process",
49
+ "lint-skills": "process", "livewire": "engineering",
50
+ "logging-monitoring": "devops", "markitdown": "process",
51
+ "mcp": "process", "mcp-builder": "process",
52
+ "md-language-check": "process", "merge-conflicts": "process",
53
+ "migration-creator": "engineering", "mobile-e2e-strategy": "quality",
54
+ "module-management": "process", "multi-tenancy": "engineering",
55
+ "okr-tree-modeling": "product", "openapi": "engineering",
56
+ "override-management": "process", "performance": "engineering",
57
+ "performance-analysis": "discovery", "persona-writing": "process",
58
+ "pest-testing": "quality", "php-coder": "engineering",
59
+ "php-debugging": "engineering", "php-service": "engineering",
60
+ "playwright-testing": "quality", "project-analysis-core": "discovery",
61
+ "project-analysis-hypothesis-driven": "discovery",
62
+ "project-analysis-laravel": "discovery", "project-analysis-nextjs": "discovery",
63
+ "project-analysis-node-express": "discovery",
64
+ "project-analysis-react": "discovery", "project-analysis-symfony": "discovery",
65
+ "project-analysis-zend-laminas": "discovery", "project-analyzer": "discovery",
66
+ "project-docs": "process", "prompt-engineering-patterns": "product",
67
+ "prompt-optimizer": "product", "quality-tools": "quality",
68
+ "react-native-setup": "devops", "react-shadcn-ui": "engineering",
69
+ "readme-reviewer": "quality", "readme-writing": "process",
70
+ "readme-writing-package": "process", "receiving-code-review": "process",
71
+ "refine-prompt": "product", "refine-ticket": "product",
72
+ "repomix-packer": "process", "requesting-code-review": "process",
73
+ "review-routing": "quality", "rice-prioritization": "product",
74
+ "roadmap-management": "process", "roadmap-writing": "process",
75
+ "rtk-output-filtering": "process", "rule-writing": "process",
76
+ "script-writing": "process", "secrets-management": "devops",
77
+ "security": "quality", "security-audit": "quality",
78
+ "sentry-integration": "devops", "sequential-thinking": "process",
79
+ "skill-improvement-pipeline": "process", "skill-management": "process",
80
+ "skill-reviewer": "quality", "skill-writing": "process",
81
+ "sql-writing": "engineering", "subagent-orchestration": "process",
82
+ "systematic-debugging": "discovery", "technical-specification": "product",
83
+ "terraform": "devops", "terragrunt": "devops",
84
+ "test-driven-development": "quality", "test-performance": "quality",
85
+ "testing-anti-patterns": "quality", "threat-modeling": "quality",
86
+ "token-optimizer": "process", "traefik": "devops",
87
+ "unit-economics-modeling": "product", "universal-project-analysis": "discovery",
88
+ "upstream-contribute": "process", "using-git-worktrees": "process",
89
+ "validate-feature-fit": "quality", "verify-completion-evidence": "quality",
90
+ "websocket": "engineering",
91
+ }
92
+ VALID_DOMAINS = {"engineering", "product", "quality", "devops", "process", "discovery"}
93
+
94
+
95
+ def main() -> int:
96
+ # Default: source-of-truth tree. Pass --target=compressed to mirror into .agent-src/.
97
+ target = ".agent-src.uncompressed/skills"
98
+ for arg in sys.argv[1:]:
99
+ if arg == "--target=compressed":
100
+ target = ".agent-src/skills"
101
+ skills_root = Path(target)
102
+ on_disk = sorted(p.name for p in skills_root.iterdir() if p.is_dir())
103
+ in_map = sorted(SKILL_DOMAIN_MAP.keys())
104
+ missing = set(on_disk) - set(in_map)
105
+ extra = set(in_map) - set(on_disk)
106
+ if missing or extra:
107
+ print(f"DRIFT: missing={sorted(missing)} extra={sorted(extra)}", file=sys.stderr)
108
+ return 2
109
+ bad = {k: v for k, v in SKILL_DOMAIN_MAP.items() if v not in VALID_DOMAINS}
110
+ if bad:
111
+ print(f"INVALID DOMAIN VALUES: {bad}", file=sys.stderr)
112
+ return 2
113
+
114
+ fm_pat = re.compile(r"^(---\n)(.*?)(\n---\n)", re.DOTALL)
115
+ domain_line_pat = re.compile(r"^domain:[ \t]*[A-Za-z]+[ \t]*$", re.MULTILINE)
116
+ source_line_pat = re.compile(r"^(source:[ \t]*\S+)$", re.MULTILINE)
117
+ edited = 0
118
+ for slug, domain in SKILL_DOMAIN_MAP.items():
119
+ skill_md = skills_root / slug / "SKILL.md"
120
+ text = skill_md.read_text()
121
+ m = fm_pat.match(text)
122
+ if not m:
123
+ print(f"NO FRONTMATTER: {skill_md}", file=sys.stderr)
124
+ return 2
125
+ fm_body = m.group(2)
126
+ if domain_line_pat.search(fm_body):
127
+ new_fm = domain_line_pat.sub(f"domain: {domain}", fm_body)
128
+ elif source_line_pat.search(fm_body):
129
+ new_fm = source_line_pat.sub(rf"\1\ndomain: {domain}", fm_body)
130
+ else:
131
+ new_fm = fm_body.rstrip("\n") + f"\ndomain: {domain}"
132
+ if new_fm != fm_body:
133
+ skill_md.write_text(m.group(1) + new_fm + m.group(3) + text[m.end():])
134
+ edited += 1
135
+ print(f"Back-filled {edited}/{len(SKILL_DOMAIN_MAP)} skills")
136
+ return 0
137
+
138
+
139
+ if __name__ == "__main__":
140
+ sys.exit(main())
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+ """Emit the per-domain skill list as Markdown for skill-domains.md § 4."""
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from collections import defaultdict
7
+ from pathlib import Path
8
+
9
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
10
+ from _backfill_skill_domains import SKILL_DOMAIN_MAP # noqa: E402
11
+
12
+ ORDER = ["engineering", "product", "quality", "devops", "process", "discovery"]
13
+
14
+
15
+ def main() -> int:
16
+ by_domain: dict[str, list[str]] = defaultdict(list)
17
+ for slug, dom in SKILL_DOMAIN_MAP.items():
18
+ by_domain[dom].append(slug)
19
+
20
+ lines: list[str] = []
21
+ total = 0
22
+ for dom in ORDER:
23
+ skills = sorted(by_domain[dom])
24
+ total += len(skills)
25
+ lines.append(f"### {dom} ({len(skills)})")
26
+ lines.append("")
27
+ lines.append(", ".join(f"`{s}`" for s in skills))
28
+ lines.append("")
29
+ lines.append(f"**Total: {total} skills.**")
30
+ print("\n".join(lines))
31
+ return 0
32
+
33
+
34
+ if __name__ == "__main__":
35
+ sys.exit(main())
@@ -13,14 +13,31 @@ mkdir -p "$HOOKS_DIR"
13
13
  cat > "$HOOKS_DIR/pre-push" << 'EOF'
14
14
  #!/usr/bin/env bash
15
15
  # Pre-push hook: verify .agent-src/ is in sync with .agent-src.uncompressed/
16
+ # and that the canonical command count matches README + getting-started docs.
17
+ #
18
+ # The command-count gate exists because three consecutive PRs landed
19
+ # post-CI count-drift fixes (e.g. f2fb0026 "bump command count
20
+ # 101→103"). Catching the drift pre-push stops it from flooding remote
21
+ # CI. Runtime ~0.1s.
22
+
23
+ fail=0
16
24
 
17
25
  echo "🔍 Checking .agent-src/ sync..."
18
- python3 scripts/compress.py --check
26
+ if ! python3 scripts/compress.py --check; then
27
+ echo "❌ .agent-src/ is out of sync. Run 'task sync' and compress changed .md files, then commit."
28
+ fail=1
29
+ fi
30
+
31
+ echo "🔍 Checking command count messaging..."
32
+ if ! python3 scripts/check_command_count_messaging.py; then
33
+ echo "❌ Command-count drift in README / AGENTS.md / getting-started. Run 'task counts-update', stage the changes, then re-commit."
34
+ fail=1
35
+ fi
19
36
 
20
- if [ $? -ne 0 ]; then
37
+ if [ $fail -ne 0 ]; then
21
38
  echo ""
22
- echo "Push blocked — .agent-src/ is out of sync."
23
- echo " Run 'task sync' and compress changed .md files, then commit."
39
+ echo " Push blocked — fix the failures above and re-push."
40
+ echo " Bypass for a WIP push: git push --no-verify"
24
41
  exit 1
25
42
  fi
26
43
  EOF
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env python3
2
+ """Block D · D1 — meta-linter for ``scripts/skill_tools/*.py``.
3
+
4
+ Enforces the four pilot-tool invariants locked by the Block D council
5
+ verdict (`agents/council-responses/block-d-python-tools-pilot-verdict.md`):
6
+
7
+ 1. **stdlib-only** — no third-party imports. Internal package imports
8
+ (``scripts.*``) are allowed.
9
+ 2. **--help and --json flags** — every tool must register both via
10
+ ``argparse`` so callers can introspect and machine-read.
11
+ 3. **naming** — ``snake_case_verb_noun.py`` (≥ 1 underscore, lowercase).
12
+ 4. **embedded sample data** — module must define a ``_SAMPLE`` constant
13
+ OR contain ``if __name__ == "__main__"`` with sample-mode handling
14
+ (so the tool can run without external fixtures).
15
+ 5. **size cap** — file ≤ 200 LOC (per roadmap D1, applies to D1 itself
16
+ and to D2/D3/D4 with their own caps validated externally).
17
+
18
+ Tool discovery is glob-based (``scripts/skill_tools/*.py`` excluding
19
+ ``__init__.py``) per anthropic round-2 critique — never a hardcoded list.
20
+
21
+ Run:
22
+ python3 scripts/lint_skill_tools.py # human-readable
23
+ python3 scripts/lint_skill_tools.py --json # machine-readable
24
+
25
+ Exit codes: 0 clean · 1 violations found · 2 usage error.
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import argparse
30
+ import ast
31
+ import json
32
+ import re
33
+ import sys
34
+ from pathlib import Path
35
+ from typing import Dict, List, Tuple
36
+
37
+ ROOT = Path(__file__).resolve().parents[1]
38
+ TOOLS_DIR = ROOT / "scripts" / "skill_tools"
39
+ NAME_RE = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)+\.py$")
40
+ SIZE_CAP = 200
41
+
42
+ # Python 3.9-compatible stdlib list. Kept conservative — additions are cheap,
43
+ # false negatives are not. Mirrors `sys.stdlib_module_names` from 3.10+.
44
+ STDLIB = frozenset({
45
+ "__future__", "abc", "argparse", "ast", "base64", "collections", "configparser",
46
+ "contextlib", "copy", "csv", "dataclasses", "datetime", "decimal", "difflib",
47
+ "enum", "errno", "fnmatch", "functools", "glob", "gzip", "hashlib", "heapq",
48
+ "html", "http", "importlib", "inspect", "io", "ipaddress", "itertools", "json",
49
+ "logging", "math", "mimetypes", "os", "pathlib", "pickle", "platform", "posixpath",
50
+ "pprint", "queue", "random", "re", "shlex", "shutil", "signal", "socket",
51
+ "sqlite3", "ssl", "stat", "string", "struct", "subprocess", "sys", "tempfile",
52
+ "textwrap", "threading", "time", "tomllib", "traceback", "types", "typing",
53
+ "unicodedata", "unittest", "urllib", "uuid", "venv", "warnings", "weakref",
54
+ "xml", "zipfile", "zlib",
55
+ })
56
+ PROJECT_PACKAGES = frozenset({"scripts", "skill_tools"}) # internal imports are fine.
57
+
58
+
59
+ def _violations_for(path: Path) -> List[str]:
60
+ """Return a list of violation strings for one tool (empty = clean)."""
61
+ out: List[str] = []
62
+ name = path.name
63
+ if not NAME_RE.match(name):
64
+ out.append(f"naming: `{name}` is not snake_case_verb_noun.py")
65
+
66
+ text = path.read_text(encoding="utf-8")
67
+ loc = sum(1 for ln in text.splitlines() if ln.strip() and not ln.lstrip().startswith("#"))
68
+ if loc > SIZE_CAP:
69
+ out.append(f"size: {loc} LOC > {SIZE_CAP} cap")
70
+
71
+ try:
72
+ tree = ast.parse(text, filename=str(path))
73
+ except SyntaxError as exc:
74
+ out.append(f"syntax: {exc.msg} at line {exc.lineno}")
75
+ return out
76
+
77
+ # Imports — flag any non-stdlib, non-project top-level module + record argparse use.
78
+ imported: set[str] = set()
79
+ for node in ast.walk(tree):
80
+ if isinstance(node, ast.Import):
81
+ for alias in node.names:
82
+ root = alias.name.split(".")[0]
83
+ imported.add(root)
84
+ if root not in STDLIB and root not in PROJECT_PACKAGES:
85
+ out.append(f"stdlib-only: imports `{alias.name}` (third-party)")
86
+ elif isinstance(node, ast.ImportFrom):
87
+ if node.module is None or node.level > 0:
88
+ continue # relative imports — package-internal
89
+ root = node.module.split(".")[0]
90
+ imported.add(root)
91
+ if root not in STDLIB and root not in PROJECT_PACKAGES:
92
+ out.append(f"stdlib-only: imports from `{node.module}` (third-party)")
93
+
94
+ # CLI flags — confirm argparse is imported and `--json` is registered.
95
+ has_argparse = "argparse" in imported
96
+ has_json_flag = re.search(r"['\"]--json['\"]", text) is not None
97
+ if not has_argparse:
98
+ out.append("cli: no `argparse` import detected")
99
+ if not has_json_flag:
100
+ out.append("cli: missing `--json` flag")
101
+ # `--help` is auto-registered by argparse; we sanity-check that
102
+ # add_help isn't disabled.
103
+ if re.search(r"add_help\s*=\s*False", text):
104
+ out.append("cli: `add_help=False` disables --help")
105
+
106
+ # Embedded sample data — accept either a module-level _SAMPLE constant
107
+ # or a __main__ block (the tool can self-demo).
108
+ has_sample = bool(re.search(r"^_SAMPLE\s*[:=]", text, re.MULTILINE))
109
+ has_main = '__name__ == "__main__"' in text or "__name__ == '__main__'" in text
110
+ if not (has_sample or has_main):
111
+ out.append("sample: no `_SAMPLE` constant or `__main__` block")
112
+
113
+ return out
114
+
115
+
116
+ def lint(tools_dir: Path) -> Tuple[int, Dict[str, List[str]]]:
117
+ tools_dir = tools_dir.resolve()
118
+ if not tools_dir.is_dir():
119
+ return 2, {"_error": [f"tools dir missing: {tools_dir}"]}
120
+ findings: Dict[str, List[str]] = {}
121
+ for path in sorted(tools_dir.glob("*.py")):
122
+ if path.name == "__init__.py":
123
+ continue
124
+ viols = _violations_for(path)
125
+ if viols:
126
+ try:
127
+ key = str(path.relative_to(ROOT))
128
+ except ValueError:
129
+ key = str(path)
130
+ findings[key] = viols
131
+ return (1 if findings else 0), findings
132
+
133
+
134
+ def _print_human(findings: Dict[str, List[str]]) -> None:
135
+ if not findings:
136
+ print(f"✅ scripts/skill_tools/ — all tools clean.")
137
+ return
138
+ print(f"❌ scripts/skill_tools/ — {len(findings)} tool(s) with violations:")
139
+ for fp, viols in findings.items():
140
+ print(f" {fp}:")
141
+ for v in viols:
142
+ print(f" - {v}")
143
+
144
+
145
+ def main(argv: List[str] | None = None) -> int:
146
+ parser = argparse.ArgumentParser(
147
+ description="Lint scripts/skill_tools/*.py against Block D pilot invariants.",
148
+ )
149
+ parser.add_argument("--json", action="store_true", help="emit JSON instead of text")
150
+ parser.add_argument("--quiet", action="store_true",
151
+ help="suppress the clean-pass success line (errors still print)")
152
+ parser.add_argument("--tools-dir", default=str(TOOLS_DIR),
153
+ help="directory to lint (default: scripts/skill_tools)")
154
+ args = parser.parse_args(argv)
155
+
156
+ code, findings = lint(Path(args.tools_dir))
157
+ if args.json:
158
+ json.dump({"exit_code": code, "findings": findings}, sys.stdout, indent=2)
159
+ sys.stdout.write("\n")
160
+ elif findings or not args.quiet:
161
+ _print_human(findings)
162
+ return code
163
+
164
+
165
+ _SAMPLE = {"violations": ["stdlib-only: imports `requests` (third-party)"]}
166
+
167
+ if __name__ == "__main__":
168
+ raise SystemExit(main())
@@ -5,7 +5,7 @@
5
5
  "$comment": "Source: agents/docs/frontmatter-contract.md#skills. Keep in sync with inventory_frontmatter.py and the linter's lint_skill().",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
- "required": ["name", "description", "source"],
8
+ "required": ["name", "description", "source", "domain"],
9
9
  "properties": {
10
10
  "name": {
11
11
  "type": "string",
@@ -22,6 +22,11 @@
22
22
  "type": "string",
23
23
  "enum": ["package", "project"]
24
24
  },
25
+ "domain": {
26
+ "type": "string",
27
+ "enum": ["engineering", "product", "quality", "devops", "process", "discovery"],
28
+ "description": "Skill classification axis; see docs/contracts/skill-domains.md. Required for every skill — exactly one of: engineering, product, quality, devops, process, discovery."
29
+ },
25
30
  "status": {
26
31
  "type": "string",
27
32
  "enum": ["active", "deprecated", "superseded"]
@@ -41,15 +41,22 @@ from validate_frontmatter import ( # noqa: E402
41
41
  Severity = Literal["error", "warning", "info"]
42
42
  ArtifactType = Literal["skill", "rule", "command", "guideline", "persona", "unknown"]
43
43
 
44
- REQUIRED_PERSONA_SECTIONS = [
44
+ REQUIRED_PERSONA_SECTIONS_CORE = [
45
45
  "Focus",
46
46
  "Mindset",
47
47
  "Unique Questions",
48
48
  "Output Expectations",
49
49
  "Anti-Patterns",
50
50
  ]
51
+ REQUIRED_PERSONA_SECTIONS_SPECIALIST = REQUIRED_PERSONA_SECTIONS_CORE + [
52
+ "Critical Rules",
53
+ "Workflows",
54
+ ]
55
+ # Back-compat alias — used by tier-agnostic callers; defaults to the core spine.
56
+ REQUIRED_PERSONA_SECTIONS = REQUIRED_PERSONA_SECTIONS_CORE
51
57
  VALID_PERSONA_TIERS = {"core", "specialist"}
52
- PERSONA_LINE_BUDGETS = {"core": 120, "specialist": 80}
58
+ # Locked in docs/contracts/persona-schema.md § 4: core ≤ 120, specialist ≤ 100.
59
+ PERSONA_LINE_BUDGETS = {"core": 120, "specialist": 100}
53
60
 
54
61
 
55
62
  REQUIRED_SKILL_SECTIONS = [
@@ -1437,9 +1444,17 @@ def lint_persona(path: Path, text: str) -> LintResult:
1437
1444
  f"Persona description is {len(parsed['description'])} chars (target ≤ 160)",
1438
1445
  ))
1439
1446
 
1440
- # Required sections
1447
+ # Required sections — tier-aware (per docs/contracts/persona-schema.md § 3).
1448
+ # Core: 5 sections. Specialist: Core-5 + Critical Rules + Workflows.
1441
1449
  sections = extract_sections(text)
1442
- for required_section in REQUIRED_PERSONA_SECTIONS:
1450
+ tier = parsed.get("tier")
1451
+ if tier == "specialist":
1452
+ required_sections = REQUIRED_PERSONA_SECTIONS_SPECIALIST
1453
+ else:
1454
+ # Default to core sections when tier is missing or invalid; the
1455
+ # tier-enum check above already raised an error in that case.
1456
+ required_sections = REQUIRED_PERSONA_SECTIONS_CORE
1457
+ for required_section in required_sections:
1443
1458
  if required_section not in sections:
1444
1459
  issues.append(Issue(
1445
1460
  "error",
@@ -0,0 +1,22 @@
1
+ """Block D pilot tools for skill discovery and persona auditing.
2
+
3
+ These four tools (D1 meta-linter + D2/D3/D4 functional pilots) ship under
4
+ this subdirectory per Block D council verdict (D-OQ2 → b). Each tool is:
5
+
6
+ - stdlib-only (no third-party imports)
7
+ - exposes ``--help`` and ``--json`` flags
8
+ - named ``snake_case_verb_noun.py``
9
+ - embeds sample data so it runs without external fixtures
10
+
11
+ Layout chosen to make the pilot reversible. If the D5 eval gate fails
12
+ (< 2 of 3 functional tools pass), the directory and its CI hook can be
13
+ removed in a single commit without touching the wider ``scripts/`` tree.
14
+
15
+ Public entry points:
16
+
17
+ - ``scripts/lint_skill_tools.py`` — D1 meta-linter (lives at scripts/ root
18
+ so ``task ci`` picks it up like other linters).
19
+ - ``score_skill_relevance`` — D2 (this dir).
20
+ - ``audit_persona_coverage`` — D3 (this dir).
21
+ - ``suggest_skill_for_task`` — D4 (this dir).
22
+ """
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env python3
2
+ """Block D · D3 — audit_persona_coverage.
3
+
4
+ Build a citation matrix of personas across the SKILL.md corpus and flag
5
+ under-cited personas using **tier-aware thresholds** (council iter-1
6
+ D-OQ4 verdict):
7
+
8
+ - **specialist** persona < 3 citations → under-cited
9
+ - **core** persona < 5 citations → under-cited
10
+
11
+ Inputs:
12
+ --skills-dir DIR — directory holding SKILL.md files
13
+ --personas-dir DIR — directory holding persona Markdown files
14
+ --json — machine-readable output
15
+
16
+ Output: per-persona citation count + tier + status (ok / under-cited / orphan).
17
+ Exit code: 0 always (this is an advisory tool, not a CI gate).
18
+
19
+ Stdlib-only. ≤ 120 LOC. Embedded `_SAMPLE` for self-demo.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import json
25
+ import re
26
+ import sys
27
+ from pathlib import Path
28
+ from typing import Dict, List
29
+
30
+ ROOT = Path(__file__).resolve().parents[2]
31
+ DEFAULT_SKILLS = ROOT / ".agent-src.uncompressed" / "skills"
32
+ DEFAULT_PERSONAS = ROOT / ".agent-src.uncompressed" / "personas"
33
+ THRESHOLDS = {"core": 5, "specialist": 3}
34
+
35
+
36
+ def _read_block(path: Path) -> str:
37
+ text = path.read_text(encoding="utf-8", errors="replace")
38
+ if not text.startswith("---"):
39
+ return ""
40
+ end = text.find("\n---", 3)
41
+ return text[3:end] if end != -1 else ""
42
+
43
+
44
+ def _frontmatter_value(block: str, key: str) -> str | None:
45
+ m = re.search(rf"^{re.escape(key)}\s*:\s*(.+)$", block, re.MULTILINE)
46
+ if not m:
47
+ return None
48
+ val = m.group(1).strip()
49
+ if val.startswith('"') and val.endswith('"'):
50
+ val = val[1:-1]
51
+ return val
52
+
53
+
54
+ def _frontmatter_list(block: str, key: str) -> List[str]:
55
+ m = re.search(rf"^{re.escape(key)}\s*:\s*$", block, re.MULTILINE)
56
+ if not m:
57
+ return []
58
+ items: List[str] = []
59
+ for line in block[m.end():].splitlines():
60
+ if line.startswith(" - "):
61
+ items.append(line[4:].strip())
62
+ elif line and not line.startswith(" "):
63
+ break
64
+ return items
65
+
66
+
67
+ def _load_personas(personas_dir: Path) -> Dict[str, str]:
68
+ """slug → tier (core | specialist | unknown)."""
69
+ personas: Dict[str, str] = {}
70
+ if not personas_dir.is_dir():
71
+ return personas
72
+ for md in sorted(personas_dir.glob("*.md")):
73
+ if md.name.lower() == "readme.md":
74
+ continue
75
+ block = _read_block(md)
76
+ slug = _frontmatter_value(block, "id") or md.stem
77
+ tier = _frontmatter_value(block, "tier") or "unknown"
78
+ personas[slug] = tier
79
+ return personas
80
+
81
+
82
+ def _count_citations(skills_dir: Path) -> Dict[str, int]:
83
+ counts: Dict[str, int] = {}
84
+ if not skills_dir.is_dir():
85
+ return counts
86
+ for skill_md in skills_dir.glob("*/SKILL.md"):
87
+ block = _read_block(skill_md)
88
+ for slug in _frontmatter_list(block, "personas"):
89
+ counts[slug] = counts.get(slug, 0) + 1
90
+ return counts
91
+
92
+
93
+ def audit(skills_dir: Path, personas_dir: Path) -> List[Dict[str, object]]:
94
+ personas = _load_personas(personas_dir)
95
+ citations = _count_citations(skills_dir)
96
+ rows: List[Dict[str, object]] = []
97
+ for slug, tier in sorted(personas.items()):
98
+ count = citations.get(slug, 0)
99
+ threshold = THRESHOLDS.get(tier, 3)
100
+ status = "under-cited" if count < threshold else "ok"
101
+ rows.append({"persona": slug, "tier": tier, "citations": count,
102
+ "threshold": threshold, "status": status})
103
+ # Surface citations that point at unknown personas (typos, deletions).
104
+ for slug in sorted(citations.keys()):
105
+ if slug not in personas:
106
+ rows.append({"persona": slug, "tier": "unknown",
107
+ "citations": citations[slug], "threshold": 0,
108
+ "status": "orphan"})
109
+ return rows
110
+
111
+
112
+ def _print_human(rows: List[Dict[str, object]]) -> None:
113
+ if not rows:
114
+ print("(no personas found)")
115
+ return
116
+ width = max(len(str(r["persona"])) for r in rows)
117
+ print(f" {'persona':<{width}} tier cites status")
118
+ print(f" {'-' * width} ---------- ----- -----------")
119
+ for r in rows:
120
+ print(f" {str(r['persona']):<{width}} {str(r['tier']):<10} "
121
+ f"{int(r['citations']):>5} {r['status']}")
122
+ flagged = [r for r in rows if r["status"] != "ok"]
123
+ if flagged:
124
+ print(f"\n {len(flagged)} persona(s) flagged "
125
+ f"(under-cited or orphan).")
126
+
127
+
128
+ def main(argv: List[str] | None = None) -> int:
129
+ parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0])
130
+ parser.add_argument("--skills-dir", default=str(DEFAULT_SKILLS))
131
+ parser.add_argument("--personas-dir", default=str(DEFAULT_PERSONAS))
132
+ parser.add_argument("--json", action="store_true",
133
+ help="emit JSON instead of text")
134
+ args = parser.parse_args(argv)
135
+ rows = audit(Path(args.skills_dir), Path(args.personas_dir))
136
+ if args.json:
137
+ json.dump({"rows": rows}, sys.stdout, indent=2)
138
+ sys.stdout.write("\n")
139
+ else:
140
+ _print_human(rows)
141
+ return 0
142
+
143
+
144
+ _SAMPLE = {"thresholds": THRESHOLDS}
145
+
146
+ if __name__ == "__main__":
147
+ raise SystemExit(main())