@event4u/agent-config 1.32.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 (210) hide show
  1. package/.agent-src/commands/research/deep.md +149 -0
  2. package/.agent-src/commands/research/report.md +134 -0
  3. package/.agent-src/commands/research.md +43 -13
  4. package/.agent-src/commands/review-changes.md +13 -8
  5. package/.agent-src/personas/README.md +12 -21
  6. package/.agent-src/personas/_template-specialist/persona.md +89 -0
  7. package/.agent-src/personas/backend-architect.md +96 -0
  8. package/.agent-src/personas/eloquent-tamer.md +96 -0
  9. package/.agent-src/personas/frontend-engineer.md +100 -0
  10. package/.agent-src/personas/qa.md +27 -2
  11. package/.agent-src/personas/security-engineer.md +100 -0
  12. package/.agent-src/skills/accessibility-auditor/SKILL.md +132 -0
  13. package/.agent-src/skills/adr-create/SKILL.md +1 -0
  14. package/.agent-src/skills/adversarial-review/SKILL.md +1 -0
  15. package/.agent-src/skills/agent-docs-writing/SKILL.md +1 -0
  16. package/.agent-src/skills/agents-md-thin-root/SKILL.md +1 -0
  17. package/.agent-src/skills/ai-council/SKILL.md +1 -0
  18. package/.agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -0
  19. package/.agent-src/skills/analysis-skill-router/SKILL.md +1 -0
  20. package/.agent-src/skills/api-design/SKILL.md +3 -0
  21. package/.agent-src/skills/api-endpoint/SKILL.md +1 -0
  22. package/.agent-src/skills/api-testing/SKILL.md +1 -0
  23. package/.agent-src/skills/architecture-review-lens/SKILL.md +137 -0
  24. package/.agent-src/skills/artisan-commands/SKILL.md +1 -0
  25. package/.agent-src/skills/async-python-patterns/SKILL.md +1 -0
  26. package/.agent-src/skills/authz-review/SKILL.md +4 -0
  27. package/.agent-src/skills/aws-infrastructure/SKILL.md +1 -0
  28. package/.agent-src/skills/blade-ui/SKILL.md +1 -0
  29. package/.agent-src/skills/blast-radius-analyzer/SKILL.md +3 -0
  30. package/.agent-src/skills/bug-analyzer/SKILL.md +1 -0
  31. package/.agent-src/skills/check-refs/SKILL.md +1 -0
  32. package/.agent-src/skills/code-refactoring/SKILL.md +1 -0
  33. package/.agent-src/skills/code-review/SKILL.md +1 -0
  34. package/.agent-src/skills/command-routing/SKILL.md +1 -0
  35. package/.agent-src/skills/command-writing/SKILL.md +1 -0
  36. package/.agent-src/skills/composer-packages/SKILL.md +1 -0
  37. package/.agent-src/skills/context-authoring/SKILL.md +1 -0
  38. package/.agent-src/skills/context-document/SKILL.md +1 -0
  39. package/.agent-src/skills/conventional-commits-writing/SKILL.md +1 -0
  40. package/.agent-src/skills/copilot-agents-optimization/SKILL.md +1 -0
  41. package/.agent-src/skills/copilot-config/SKILL.md +1 -0
  42. package/.agent-src/skills/dashboard-design/SKILL.md +1 -0
  43. package/.agent-src/skills/data-flow-mapper/SKILL.md +1 -0
  44. package/.agent-src/skills/database/SKILL.md +3 -0
  45. package/.agent-src/skills/dcf-modeling/SKILL.md +1 -0
  46. package/.agent-src/skills/decision-record/SKILL.md +143 -0
  47. package/.agent-src/skills/deep-reading-analyst/SKILL.md +1 -0
  48. package/.agent-src/skills/defense-in-depth/SKILL.md +1 -0
  49. package/.agent-src/skills/dependency-upgrade/SKILL.md +1 -0
  50. package/.agent-src/skills/description-assist/SKILL.md +1 -0
  51. package/.agent-src/skills/design-review/SKILL.md +1 -0
  52. package/.agent-src/skills/devcontainer/SKILL.md +1 -0
  53. package/.agent-src/skills/developer-like-execution/SKILL.md +1 -0
  54. package/.agent-src/skills/docker/SKILL.md +1 -0
  55. package/.agent-src/skills/dto-creator/SKILL.md +1 -0
  56. package/.agent-src/skills/eloquent/SKILL.md +3 -0
  57. package/.agent-src/skills/error-handling-patterns/SKILL.md +1 -0
  58. package/.agent-src/skills/estimate-ticket/SKILL.md +1 -0
  59. package/.agent-src/skills/existing-ui-audit/SKILL.md +3 -0
  60. package/.agent-src/skills/fe-design/SKILL.md +4 -1
  61. package/.agent-src/skills/feature-planning/SKILL.md +1 -0
  62. package/.agent-src/skills/file-editor/SKILL.md +1 -0
  63. package/.agent-src/skills/finishing-a-development-branch/SKILL.md +1 -0
  64. package/.agent-src/skills/flux/SKILL.md +1 -0
  65. package/.agent-src/skills/form-handler/SKILL.md +145 -0
  66. package/.agent-src/skills/funnel-analysis/SKILL.md +1 -0
  67. package/.agent-src/skills/git-workflow/SKILL.md +1 -0
  68. package/.agent-src/skills/github-ci/SKILL.md +1 -0
  69. package/.agent-src/skills/grafana/SKILL.md +1 -0
  70. package/.agent-src/skills/guideline-writing/SKILL.md +1 -0
  71. package/.agent-src/skills/incident-commander/SKILL.md +140 -0
  72. package/.agent-src/skills/jira-integration/SKILL.md +1 -0
  73. package/.agent-src/skills/jobs-events/SKILL.md +1 -0
  74. package/.agent-src/skills/judge-bug-hunter/SKILL.md +1 -0
  75. package/.agent-src/skills/judge-code-quality/SKILL.md +1 -0
  76. package/.agent-src/skills/judge-security-auditor/SKILL.md +3 -0
  77. package/.agent-src/skills/judge-test-coverage/SKILL.md +1 -0
  78. package/.agent-src/skills/laravel/SKILL.md +1 -0
  79. package/.agent-src/skills/laravel-horizon/SKILL.md +1 -0
  80. package/.agent-src/skills/laravel-mail/SKILL.md +1 -0
  81. package/.agent-src/skills/laravel-middleware/SKILL.md +1 -0
  82. package/.agent-src/skills/laravel-notifications/SKILL.md +1 -0
  83. package/.agent-src/skills/laravel-pennant/SKILL.md +1 -0
  84. package/.agent-src/skills/laravel-pulse/SKILL.md +1 -0
  85. package/.agent-src/skills/laravel-reverb/SKILL.md +1 -0
  86. package/.agent-src/skills/laravel-scheduling/SKILL.md +1 -0
  87. package/.agent-src/skills/laravel-validation/SKILL.md +1 -0
  88. package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +1 -0
  89. package/.agent-src/skills/lint-skills/SKILL.md +1 -0
  90. package/.agent-src/skills/livewire/SKILL.md +1 -0
  91. package/.agent-src/skills/livewire-architect/SKILL.md +158 -0
  92. package/.agent-src/skills/logging-monitoring/SKILL.md +1 -0
  93. package/.agent-src/skills/markitdown/SKILL.md +1 -0
  94. package/.agent-src/skills/mcp/SKILL.md +1 -0
  95. package/.agent-src/skills/mcp-builder/SKILL.md +1 -0
  96. package/.agent-src/skills/md-language-check/SKILL.md +1 -0
  97. package/.agent-src/skills/merge-conflicts/SKILL.md +1 -0
  98. package/.agent-src/skills/migration-architect/SKILL.md +119 -0
  99. package/.agent-src/skills/migration-creator/SKILL.md +1 -0
  100. package/.agent-src/skills/mobile-e2e-strategy/SKILL.md +2 -1
  101. package/.agent-src/skills/module-management/SKILL.md +1 -0
  102. package/.agent-src/skills/multi-tenancy/SKILL.md +1 -0
  103. package/.agent-src/skills/okr-tree-modeling/SKILL.md +1 -0
  104. package/.agent-src/skills/openapi/SKILL.md +1 -0
  105. package/.agent-src/skills/override-management/SKILL.md +1 -0
  106. package/.agent-src/skills/performance/SKILL.md +1 -0
  107. package/.agent-src/skills/performance-analysis/SKILL.md +1 -0
  108. package/.agent-src/skills/persona-writing/SKILL.md +1 -0
  109. package/.agent-src/skills/pest-testing/SKILL.md +1 -0
  110. package/.agent-src/skills/php-coder/SKILL.md +1 -0
  111. package/.agent-src/skills/php-debugging/SKILL.md +1 -0
  112. package/.agent-src/skills/php-service/SKILL.md +1 -0
  113. package/.agent-src/skills/playwright-architect/SKILL.md +141 -0
  114. package/.agent-src/skills/playwright-testing/SKILL.md +1 -0
  115. package/.agent-src/skills/po-discovery/SKILL.md +127 -0
  116. package/.agent-src/skills/project-analysis-core/SKILL.md +1 -0
  117. package/.agent-src/skills/project-analysis-hypothesis-driven/SKILL.md +1 -0
  118. package/.agent-src/skills/project-analysis-laravel/SKILL.md +1 -0
  119. package/.agent-src/skills/project-analysis-nextjs/SKILL.md +1 -0
  120. package/.agent-src/skills/project-analysis-node-express/SKILL.md +1 -0
  121. package/.agent-src/skills/project-analysis-react/SKILL.md +1 -0
  122. package/.agent-src/skills/project-analysis-symfony/SKILL.md +1 -0
  123. package/.agent-src/skills/project-analysis-zend-laminas/SKILL.md +1 -0
  124. package/.agent-src/skills/project-analyzer/SKILL.md +1 -0
  125. package/.agent-src/skills/project-docs/SKILL.md +1 -0
  126. package/.agent-src/skills/prompt-engineering-patterns/SKILL.md +1 -0
  127. package/.agent-src/skills/prompt-optimizer/SKILL.md +1 -0
  128. package/.agent-src/skills/quality-tools/SKILL.md +1 -0
  129. package/.agent-src/skills/react-native-setup/SKILL.md +1 -0
  130. package/.agent-src/skills/react-shadcn-ui/SKILL.md +1 -0
  131. package/.agent-src/skills/readme-reviewer/SKILL.md +1 -0
  132. package/.agent-src/skills/readme-writing/SKILL.md +1 -0
  133. package/.agent-src/skills/readme-writing-package/SKILL.md +1 -0
  134. package/.agent-src/skills/receiving-code-review/SKILL.md +1 -0
  135. package/.agent-src/skills/refine-prompt/SKILL.md +1 -0
  136. package/.agent-src/skills/refine-ticket/SKILL.md +1 -0
  137. package/.agent-src/skills/repomix-packer/SKILL.md +1 -0
  138. package/.agent-src/skills/requesting-code-review/SKILL.md +1 -0
  139. package/.agent-src/skills/review-routing/SKILL.md +1 -0
  140. package/.agent-src/skills/rice-prioritization/SKILL.md +1 -0
  141. package/.agent-src/skills/risk-officer/SKILL.md +141 -0
  142. package/.agent-src/skills/roadmap-management/SKILL.md +1 -0
  143. package/.agent-src/skills/roadmap-writing/SKILL.md +1 -0
  144. package/.agent-src/skills/rtk-output-filtering/SKILL.md +1 -0
  145. package/.agent-src/skills/rule-writing/SKILL.md +1 -0
  146. package/.agent-src/skills/script-writing/SKILL.md +1 -0
  147. package/.agent-src/skills/secrets-management/SKILL.md +1 -0
  148. package/.agent-src/skills/security/SKILL.md +1 -0
  149. package/.agent-src/skills/security-audit/SKILL.md +1 -0
  150. package/.agent-src/skills/sentry-integration/SKILL.md +1 -0
  151. package/.agent-src/skills/sequential-thinking/SKILL.md +1 -0
  152. package/.agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -0
  153. package/.agent-src/skills/skill-management/SKILL.md +1 -0
  154. package/.agent-src/skills/skill-reviewer/SKILL.md +1 -0
  155. package/.agent-src/skills/skill-writing/SKILL.md +1 -0
  156. package/.agent-src/skills/sql-writing/SKILL.md +1 -0
  157. package/.agent-src/skills/stakeholder-tradeoff/SKILL.md +149 -0
  158. package/.agent-src/skills/subagent-orchestration/SKILL.md +13 -0
  159. package/.agent-src/skills/systematic-debugging/SKILL.md +1 -0
  160. package/.agent-src/skills/tailwind-engineer/SKILL.md +130 -0
  161. package/.agent-src/skills/tech-debt-tracker/SKILL.md +152 -0
  162. package/.agent-src/skills/technical-specification/SKILL.md +1 -0
  163. package/.agent-src/skills/terraform/SKILL.md +1 -0
  164. package/.agent-src/skills/terragrunt/SKILL.md +1 -0
  165. package/.agent-src/skills/test-driven-development/SKILL.md +1 -0
  166. package/.agent-src/skills/test-performance/SKILL.md +1 -0
  167. package/.agent-src/skills/testing-anti-patterns/SKILL.md +1 -0
  168. package/.agent-src/skills/threat-modeling/SKILL.md +3 -0
  169. package/.agent-src/skills/token-optimizer/SKILL.md +1 -0
  170. package/.agent-src/skills/traefik/SKILL.md +1 -0
  171. package/.agent-src/skills/ui-component-architect/SKILL.md +153 -0
  172. package/.agent-src/skills/unit-economics-modeling/SKILL.md +1 -0
  173. package/.agent-src/skills/universal-project-analysis/SKILL.md +1 -0
  174. package/.agent-src/skills/upstream-contribute/SKILL.md +1 -0
  175. package/.agent-src/skills/using-git-worktrees/SKILL.md +1 -0
  176. package/.agent-src/skills/validate-feature-fit/SKILL.md +1 -0
  177. package/.agent-src/skills/verify-completion-evidence/SKILL.md +1 -0
  178. package/.agent-src/skills/websocket/SKILL.md +1 -0
  179. package/.claude-plugin/marketplace.json +17 -1
  180. package/AGENTS.md +1 -0
  181. package/CHANGELOG.md +68 -0
  182. package/README.md +3 -3
  183. package/docs/architecture.md +3 -3
  184. package/docs/catalog.md +26 -5
  185. package/docs/contracts/command-clusters.md +1 -1
  186. package/docs/contracts/file-ownership-matrix.json +560 -0
  187. package/docs/contracts/persona-schema.md +136 -0
  188. package/docs/contracts/skill-domains.md +143 -0
  189. package/docs/decisions/ADR-005-subagent-worktrees.md +120 -0
  190. package/docs/decisions/ADR-006-skill-tools-python-pilot.md +114 -0
  191. package/docs/decisions/INDEX.md +3 -0
  192. package/docs/getting-started.md +1 -1
  193. package/docs/guidelines/agent-infra/5w2h-analysis.md +260 -0
  194. package/docs/guidelines/agent-infra/critical-thinking.md +156 -0
  195. package/docs/guidelines/agent-infra/first-principles.md +192 -0
  196. package/docs/guidelines/agent-infra/six-hats.md +353 -0
  197. package/docs/guidelines/agent-infra/systems-thinking.md +220 -0
  198. package/docs/personas.md +115 -0
  199. package/package.json +1 -1
  200. package/scripts/_backfill_skill_domains.py +140 -0
  201. package/scripts/_emit_domain_table.py +35 -0
  202. package/scripts/install-hooks.sh +21 -4
  203. package/scripts/lint_skill_tools.py +168 -0
  204. package/scripts/schemas/skill.schema.json +6 -1
  205. package/scripts/skill_linter.py +19 -4
  206. package/scripts/skill_tools/__init__.py +22 -0
  207. package/scripts/skill_tools/audit_persona_coverage.py +147 -0
  208. package/scripts/skill_tools/run_block_d_eval.py +129 -0
  209. package/scripts/skill_tools/score_skill_relevance.py +169 -0
  210. package/scripts/skill_tools/suggest_skill_for_task.py +113 -0
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env python3
2
+ """Block D · D5 — eval gate runner.
3
+
4
+ Runs D2 (`score_skill_relevance`), D3 (`audit_persona_coverage`), and
5
+ D4 (`suggest_skill_for_task`) against the corpora in
6
+ `agents/eval-corpora/block-d/` and emits a pass/fail summary per the
7
+ council verdict targets:
8
+
9
+ - **D2**: ≥ 85 % of corpus tasks have an `expected_top3` skill in
10
+ the actual top-3 ranking.
11
+ - **D3**: ≥ 2 personas flagged as `under-cited`.
12
+ - **D4**: ≥ 3 / 5 blind tasks where suggestion #1 matches the
13
+ human-curated top-1.
14
+
15
+ Pilot pass = ≥ 2 / 3 tools pass. Anything less → kill switch.
16
+
17
+ Stdlib-only. Embedded `_SAMPLE` for self-demo.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import argparse
22
+ import json
23
+ import sys
24
+ from pathlib import Path
25
+ from typing import Dict, List
26
+
27
+ from skill_tools.audit_persona_coverage import audit # type: ignore
28
+ from skill_tools.score_skill_relevance import ( # type: ignore
29
+ DEFAULT_SKILLS_DIR,
30
+ rank,
31
+ )
32
+ from skill_tools.suggest_skill_for_task import suggest # type: ignore
33
+
34
+ ROOT = Path(__file__).resolve().parents[2]
35
+ CORPUS_DIR = ROOT / "agents" / "eval-corpora" / "block-d"
36
+ PERSONAS_DIR = ROOT / ".agent-src.uncompressed" / "personas"
37
+
38
+
39
+ def _eval_d2(corpus: Path, skills_dir: Path) -> Dict[str, object]:
40
+ data = json.loads(corpus.read_text(encoding="utf-8"))
41
+ tasks = data["tasks"]
42
+ hits, misses = 0, []
43
+ for t in tasks:
44
+ ranked = rank(t["task"], skills_dir)[:3]
45
+ names = [n for n, _, _ in ranked]
46
+ if any(e in names for e in t["expected_top3"]):
47
+ hits += 1
48
+ else:
49
+ misses.append({"id": t["id"], "expected": t["expected_top3"],
50
+ "got": names})
51
+ pct = hits / len(tasks) if tasks else 0.0
52
+ return {"hits": hits, "total": len(tasks), "pct": round(pct, 3),
53
+ "passed": pct >= 0.85, "misses": misses}
54
+
55
+
56
+ def _eval_d3(skills_dir: Path, personas_dir: Path) -> Dict[str, object]:
57
+ rows = audit(skills_dir, personas_dir)
58
+ flagged = [r["persona"] for r in rows if r["status"] == "under-cited"]
59
+ return {"flagged": flagged, "count": len(flagged),
60
+ "passed": len(flagged) >= 2}
61
+
62
+
63
+ def _eval_d4(corpus: Path, skills_dir: Path,
64
+ personas_dir: Path) -> Dict[str, object]:
65
+ data = json.loads(corpus.read_text(encoding="utf-8"))
66
+ tasks = data["tasks"]
67
+ hits, misses = 0, []
68
+ for t in tasks:
69
+ out = suggest(t["task"], skills_dir, personas_dir, top=1)
70
+ got = out[0]["skill"] if out else None
71
+ if got == t["expected_top1"]:
72
+ hits += 1
73
+ else:
74
+ misses.append({"id": t["id"], "expected": t["expected_top1"],
75
+ "got": got})
76
+ return {"hits": hits, "total": len(tasks),
77
+ "passed": hits >= 3, "misses": misses}
78
+
79
+
80
+ def run_all(skills_dir: Path, personas_dir: Path,
81
+ corpus_dir: Path) -> Dict[str, object]:
82
+ d2 = _eval_d2(corpus_dir / "d2-tasks.json", skills_dir)
83
+ d3 = _eval_d3(skills_dir, personas_dir)
84
+ d4 = _eval_d4(corpus_dir / "d4-tasks.json", skills_dir, personas_dir)
85
+ passes = sum(1 for r in (d2, d3, d4) if r["passed"])
86
+ return {"D2": d2, "D3": d3, "D4": d4,
87
+ "tools_passed": passes,
88
+ "pilot_passed": passes >= 2}
89
+
90
+
91
+ def _print_human(report: Dict[str, object]) -> None:
92
+ icons = {True: "✅", False: "❌"}
93
+ for key in ("D2", "D3", "D4"):
94
+ r: Dict[str, object] = report[key] # type: ignore[assignment]
95
+ print(f" {icons[bool(r['passed'])]} {key}: {_summary(key, r)}")
96
+ overall = bool(report["pilot_passed"])
97
+ print(f"\n pilot: {report['tools_passed']}/3 tools passed → "
98
+ f"{'PASS' if overall else 'FAIL'}")
99
+
100
+
101
+ def _summary(key: str, r: Dict[str, object]) -> str:
102
+ if key == "D2":
103
+ return f"{r['hits']}/{r['total']} ({float(r['pct']) * 100:.0f}%) ≥ 85% target"
104
+ if key == "D3":
105
+ return f"{r['count']} under-cited personas (≥ 2 target)"
106
+ return f"{r['hits']}/{r['total']} top-1 hits (≥ 3/5 target)"
107
+
108
+
109
+ def main(argv: List[str] | None = None) -> int:
110
+ parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0])
111
+ parser.add_argument("--skills-dir", default=str(DEFAULT_SKILLS_DIR))
112
+ parser.add_argument("--personas-dir", default=str(PERSONAS_DIR))
113
+ parser.add_argument("--corpus-dir", default=str(CORPUS_DIR))
114
+ parser.add_argument("--json", action="store_true")
115
+ args = parser.parse_args(argv)
116
+ report = run_all(Path(args.skills_dir), Path(args.personas_dir),
117
+ Path(args.corpus_dir))
118
+ if args.json:
119
+ json.dump(report, sys.stdout, indent=2)
120
+ sys.stdout.write("\n")
121
+ else:
122
+ _print_human(report)
123
+ return 0 if report["pilot_passed"] else 1
124
+
125
+
126
+ _SAMPLE = {"corpus_dir": str(CORPUS_DIR)}
127
+
128
+ if __name__ == "__main__":
129
+ raise SystemExit(main())
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env python3
2
+ """Block D · D2 — score_skill_relevance.
3
+
4
+ Rank skills by relevance to a free-form task description.
5
+
6
+ Heuristic (council iter-1 D-OQ1 verdict (b) — discovery-story tool 1):
7
+
8
+ score = keyword_overlap * 70 + persona_match * 30
9
+
10
+ where:
11
+ - keyword_overlap = |task_terms ∩ skill_terms| / |task_terms|
12
+ (skill_terms = tokens from `name` + `description`)
13
+ - persona_match = 1.0 if any persona on the skill is named or
14
+ role-mentioned in the task, else 0.0
15
+
16
+ Inputs:
17
+ --task TEXT — task description (required)
18
+ --skills-dir DIR — directory holding SKILL.md files (default: package skills)
19
+ --top N — emit only top-N ranked skills (default: all non-zero)
20
+ --json — machine-readable ranked output
21
+
22
+ Output: ranked list with integer scores 0–100, descending. Ties break on name.
23
+
24
+ Stdlib-only. ≤ 180 LOC. Embedded `_SAMPLE` for self-demo (`python3 -m … --json`).
25
+ """
26
+ from __future__ import annotations
27
+
28
+ import argparse
29
+ import json
30
+ import re
31
+ import sys
32
+ from pathlib import Path
33
+ from typing import Dict, Iterable, List, Tuple
34
+
35
+ ROOT = Path(__file__).resolve().parents[2]
36
+ DEFAULT_SKILLS_DIR = ROOT / ".agent-src.uncompressed" / "skills"
37
+ TOKEN_RE = re.compile(r"[a-z][a-z0-9]+")
38
+ STOPWORDS = frozenset({
39
+ "the", "a", "an", "and", "or", "but", "of", "for", "with", "to", "in",
40
+ "on", "at", "by", "from", "as", "is", "are", "was", "were", "be", "been",
41
+ "this", "that", "these", "those", "it", "its", "use", "when", "even",
42
+ "via", "via:", "into", "onto", "use:", "skill", "skills", "task", "tasks",
43
+ "code", "file", "files", "doing", "make", "do", "go", "get", "set",
44
+ "not", "no", "yes", "any", "some", "all", "one", "two", "new", "old",
45
+ "user", "users", "our", "your", "their", "they", "we", "you", "i", "me",
46
+ })
47
+
48
+
49
+ def _tokenize(text: str) -> set:
50
+ return {t for t in TOKEN_RE.findall(text.lower()) if t not in STOPWORDS and len(t) > 2}
51
+
52
+
53
+ def _parse_frontmatter(path: Path) -> Dict[str, object]:
54
+ """Minimal YAML-frontmatter reader (stdlib-only). Returns {} on parse miss."""
55
+ text = path.read_text(encoding="utf-8", errors="replace")
56
+ if not text.startswith("---"):
57
+ return {}
58
+ end = text.find("\n---", 3)
59
+ if end == -1:
60
+ return {}
61
+ block = text[3:end]
62
+ out: Dict[str, object] = {}
63
+ current_list_key: str | None = None
64
+ for raw in block.splitlines():
65
+ line = raw.rstrip()
66
+ if not line or line.startswith("#"):
67
+ continue
68
+ if current_list_key and line.startswith(" - "):
69
+ out.setdefault(current_list_key, []).append(line[4:].strip()) # type: ignore[union-attr]
70
+ continue
71
+ current_list_key = None
72
+ m = re.match(r"^([a-zA-Z_][\w-]*)\s*:\s*(.*)$", line)
73
+ if not m:
74
+ continue
75
+ key, val = m.group(1), m.group(2).strip()
76
+ if val == "":
77
+ current_list_key = key
78
+ continue
79
+ if val.startswith('"') and val.endswith('"'):
80
+ val = val[1:-1]
81
+ out[key] = val
82
+ return out
83
+
84
+
85
+ def _load_skills(skills_dir: Path) -> List[Dict[str, object]]:
86
+ skills: List[Dict[str, object]] = []
87
+ for skill_md in sorted(skills_dir.glob("*/SKILL.md")):
88
+ fm = _parse_frontmatter(skill_md)
89
+ name = str(fm.get("name") or skill_md.parent.name)
90
+ desc = str(fm.get("description") or "")
91
+ personas = fm.get("personas") or []
92
+ if isinstance(personas, str):
93
+ personas = [personas]
94
+ skills.append({
95
+ "name": name,
96
+ "description": desc,
97
+ "personas": list(personas),
98
+ "terms": _tokenize(name + " " + desc),
99
+ })
100
+ return skills
101
+
102
+
103
+ def _score(task_terms: set, skill: Dict[str, object]) -> int:
104
+ if not task_terms:
105
+ return 0
106
+ skill_terms = skill["terms"] # type: ignore[index]
107
+ overlap = len(task_terms & skill_terms) / max(len(task_terms), 1) # type: ignore[arg-type]
108
+ persona_hit = 0.0
109
+ task_lower = " ".join(task_terms)
110
+ for persona in skill["personas"]: # type: ignore[union-attr]
111
+ slug = str(persona).lower()
112
+ if slug in task_lower or any(part in task_terms for part in slug.split("-")): # type: ignore[operator]
113
+ persona_hit = 1.0
114
+ break
115
+ return round(overlap * 70 + persona_hit * 30)
116
+
117
+
118
+ def rank(task: str, skills_dir: Path) -> List[Tuple[str, int, List[str]]]:
119
+ task_terms = _tokenize(task)
120
+ skills = _load_skills(skills_dir)
121
+ rows: List[Tuple[str, int, List[str]]] = []
122
+ for s in skills:
123
+ score = _score(task_terms, s)
124
+ if score > 0:
125
+ rows.append((str(s["name"]), score, list(s["personas"]))) # type: ignore[arg-type]
126
+ rows.sort(key=lambda r: (-r[1], r[0]))
127
+ return rows
128
+
129
+
130
+ def _print_human(rows: Iterable[Tuple[str, int, List[str]]], top: int | None) -> None:
131
+ rows = list(rows)
132
+ if top:
133
+ rows = rows[:top]
134
+ if not rows:
135
+ print("(no relevant skills found)")
136
+ return
137
+ width = max(len(r[0]) for r in rows)
138
+ for name, score, personas in rows:
139
+ persona_str = ", ".join(personas) if personas else "—"
140
+ print(f" {score:3d} {name:<{width}} {persona_str}")
141
+
142
+
143
+ def main(argv: List[str] | None = None) -> int:
144
+ parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0])
145
+ parser.add_argument("--task", required=False, default="",
146
+ help="task description (required unless --sample is used)")
147
+ parser.add_argument("--skills-dir", default=str(DEFAULT_SKILLS_DIR),
148
+ help="directory holding SKILL.md files")
149
+ parser.add_argument("--top", type=int, default=0, help="emit only top-N rows")
150
+ parser.add_argument("--json", action="store_true", help="emit JSON instead of text")
151
+ parser.add_argument("--sample", action="store_true", help="run against the embedded sample task")
152
+ args = parser.parse_args(argv)
153
+ task = _SAMPLE["task"] if args.sample else args.task
154
+ if not task:
155
+ parser.error("--task is required (or pass --sample)")
156
+ rows = rank(task, Path(args.skills_dir))
157
+ if args.json:
158
+ payload = [{"name": n, "score": s, "personas": p} for n, s, p in (rows[:args.top] if args.top else rows)]
159
+ json.dump({"task": task, "ranked": payload}, sys.stdout, indent=2)
160
+ sys.stdout.write("\n")
161
+ else:
162
+ _print_human(rows, args.top or None)
163
+ return 0
164
+
165
+
166
+ _SAMPLE = {"task": "build a livewire component for the user dashboard with reactive state"}
167
+
168
+ if __name__ == "__main__":
169
+ raise SystemExit(main())
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env python3
2
+ """Block D · D4 — suggest_skill_for_task.
3
+
4
+ CLI wrapper that combines D2 (`score_skill_relevance`) with the persona
5
+ matrix from D3 (`audit_persona_coverage`) and emits the top-3 skill +
6
+ persona combos with a one-line justification each.
7
+
8
+ Inputs:
9
+ --task TEXT — task description (required)
10
+ --skills-dir DIR — SKILL.md directory
11
+ --personas-dir DIR — persona Markdown directory
12
+ --top N — emit top-N combos (default: 3)
13
+ --json — machine-readable output
14
+
15
+ Output: ranked combos with `skill`, `score`, `personas[]`, and `why`.
16
+
17
+ Stdlib-only. ≤ 100 LOC. Embedded `_SAMPLE` for self-demo via `--sample`.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import argparse
22
+ import json
23
+ import sys
24
+ from pathlib import Path
25
+ from typing import Dict, List
26
+
27
+ from skill_tools.audit_persona_coverage import audit # type: ignore
28
+ from skill_tools.score_skill_relevance import ( # type: ignore
29
+ DEFAULT_SKILLS_DIR,
30
+ rank,
31
+ )
32
+
33
+ ROOT = Path(__file__).resolve().parents[2]
34
+ DEFAULT_PERSONAS = ROOT / ".agent-src.uncompressed" / "personas"
35
+
36
+
37
+ def _persona_status(rows: List[Dict[str, object]]) -> Dict[str, str]:
38
+ return {str(r["persona"]): str(r["status"]) for r in rows}
39
+
40
+
41
+ def _justify(name: str, score: int, personas: List[str],
42
+ status: Dict[str, str]) -> str:
43
+ if score >= 70:
44
+ head = "high keyword + persona match"
45
+ elif score >= 40:
46
+ head = "strong keyword overlap"
47
+ else:
48
+ head = "partial overlap — confirm with reviewer"
49
+ if personas:
50
+ tier_hits = ", ".join(
51
+ f"{p} ({status.get(p, 'unknown')})" for p in personas
52
+ )
53
+ return f"{head}; lenses: {tier_hits}"
54
+ return f"{head}; no persona declared on `{name}`"
55
+
56
+
57
+ def suggest(task: str, skills_dir: Path, personas_dir: Path,
58
+ top: int = 3) -> List[Dict[str, object]]:
59
+ ranked = rank(task, skills_dir)[:top]
60
+ persona_rows = audit(skills_dir, personas_dir)
61
+ status = _persona_status(persona_rows)
62
+ return [
63
+ {
64
+ "skill": name,
65
+ "score": score,
66
+ "personas": personas,
67
+ "why": _justify(name, score, personas, status),
68
+ }
69
+ for name, score, personas in ranked
70
+ ]
71
+
72
+
73
+ def _print_human(combos: List[Dict[str, object]]) -> None:
74
+ if not combos:
75
+ print("(no skill suggestions for this task)")
76
+ return
77
+ for i, c in enumerate(combos, 1):
78
+ personas = ", ".join(c["personas"]) if c["personas"] else "—" # type: ignore[arg-type]
79
+ print(f" {i}. {c['skill']} ({c['score']}/100)")
80
+ print(f" personas: {personas}")
81
+ print(f" why: {c['why']}")
82
+
83
+
84
+ def main(argv: List[str] | None = None) -> int:
85
+ parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0])
86
+ parser.add_argument("--task", default="",
87
+ help="task description (required unless --sample)")
88
+ parser.add_argument("--skills-dir", default=str(DEFAULT_SKILLS_DIR))
89
+ parser.add_argument("--personas-dir", default=str(DEFAULT_PERSONAS))
90
+ parser.add_argument("--top", type=int, default=3)
91
+ parser.add_argument("--json", action="store_true",
92
+ help="emit JSON instead of text")
93
+ parser.add_argument("--sample", action="store_true",
94
+ help="run against the embedded sample task")
95
+ args = parser.parse_args(argv)
96
+ task = _SAMPLE["task"] if args.sample else args.task
97
+ if not task:
98
+ parser.error("--task is required (or pass --sample)")
99
+ combos = suggest(task, Path(args.skills_dir),
100
+ Path(args.personas_dir), args.top)
101
+ if args.json:
102
+ json.dump({"task": task, "suggestions": combos},
103
+ sys.stdout, indent=2)
104
+ sys.stdout.write("\n")
105
+ else:
106
+ _print_human(combos)
107
+ return 0
108
+
109
+
110
+ _SAMPLE = {"task": "review a livewire component for accessibility and reactive state"}
111
+
112
+ if __name__ == "__main__":
113
+ raise SystemExit(main())