@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.
- package/.agent-src/commands/research/deep.md +149 -0
- package/.agent-src/commands/research/report.md +134 -0
- package/.agent-src/commands/research.md +43 -13
- package/.agent-src/commands/review-changes.md +13 -8
- package/.agent-src/personas/README.md +12 -21
- package/.agent-src/personas/_template-specialist/persona.md +89 -0
- package/.agent-src/personas/backend-architect.md +96 -0
- package/.agent-src/personas/eloquent-tamer.md +96 -0
- package/.agent-src/personas/frontend-engineer.md +100 -0
- package/.agent-src/personas/qa.md +27 -2
- package/.agent-src/personas/security-engineer.md +100 -0
- package/.agent-src/skills/accessibility-auditor/SKILL.md +132 -0
- package/.agent-src/skills/adr-create/SKILL.md +1 -0
- package/.agent-src/skills/adversarial-review/SKILL.md +1 -0
- package/.agent-src/skills/agent-docs-writing/SKILL.md +1 -0
- package/.agent-src/skills/agents-md-thin-root/SKILL.md +1 -0
- package/.agent-src/skills/ai-council/SKILL.md +1 -0
- package/.agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -0
- package/.agent-src/skills/analysis-skill-router/SKILL.md +1 -0
- package/.agent-src/skills/api-design/SKILL.md +3 -0
- package/.agent-src/skills/api-endpoint/SKILL.md +1 -0
- package/.agent-src/skills/api-testing/SKILL.md +1 -0
- package/.agent-src/skills/architecture-review-lens/SKILL.md +137 -0
- package/.agent-src/skills/artisan-commands/SKILL.md +1 -0
- package/.agent-src/skills/async-python-patterns/SKILL.md +1 -0
- package/.agent-src/skills/authz-review/SKILL.md +4 -0
- package/.agent-src/skills/aws-infrastructure/SKILL.md +1 -0
- package/.agent-src/skills/blade-ui/SKILL.md +1 -0
- package/.agent-src/skills/blast-radius-analyzer/SKILL.md +3 -0
- package/.agent-src/skills/bug-analyzer/SKILL.md +1 -0
- package/.agent-src/skills/check-refs/SKILL.md +1 -0
- package/.agent-src/skills/code-refactoring/SKILL.md +1 -0
- package/.agent-src/skills/code-review/SKILL.md +1 -0
- package/.agent-src/skills/command-routing/SKILL.md +1 -0
- package/.agent-src/skills/command-writing/SKILL.md +1 -0
- package/.agent-src/skills/composer-packages/SKILL.md +1 -0
- package/.agent-src/skills/context-authoring/SKILL.md +1 -0
- package/.agent-src/skills/context-document/SKILL.md +1 -0
- package/.agent-src/skills/conventional-commits-writing/SKILL.md +1 -0
- package/.agent-src/skills/copilot-agents-optimization/SKILL.md +1 -0
- package/.agent-src/skills/copilot-config/SKILL.md +1 -0
- package/.agent-src/skills/dashboard-design/SKILL.md +1 -0
- package/.agent-src/skills/data-flow-mapper/SKILL.md +1 -0
- package/.agent-src/skills/database/SKILL.md +3 -0
- package/.agent-src/skills/dcf-modeling/SKILL.md +1 -0
- package/.agent-src/skills/decision-record/SKILL.md +143 -0
- package/.agent-src/skills/deep-reading-analyst/SKILL.md +1 -0
- package/.agent-src/skills/defense-in-depth/SKILL.md +1 -0
- package/.agent-src/skills/dependency-upgrade/SKILL.md +1 -0
- package/.agent-src/skills/description-assist/SKILL.md +1 -0
- package/.agent-src/skills/design-review/SKILL.md +1 -0
- package/.agent-src/skills/devcontainer/SKILL.md +1 -0
- package/.agent-src/skills/developer-like-execution/SKILL.md +1 -0
- package/.agent-src/skills/docker/SKILL.md +1 -0
- package/.agent-src/skills/dto-creator/SKILL.md +1 -0
- package/.agent-src/skills/eloquent/SKILL.md +3 -0
- package/.agent-src/skills/error-handling-patterns/SKILL.md +1 -0
- package/.agent-src/skills/estimate-ticket/SKILL.md +1 -0
- package/.agent-src/skills/existing-ui-audit/SKILL.md +3 -0
- package/.agent-src/skills/fe-design/SKILL.md +4 -1
- package/.agent-src/skills/feature-planning/SKILL.md +1 -0
- package/.agent-src/skills/file-editor/SKILL.md +1 -0
- package/.agent-src/skills/finishing-a-development-branch/SKILL.md +1 -0
- package/.agent-src/skills/flux/SKILL.md +1 -0
- package/.agent-src/skills/form-handler/SKILL.md +145 -0
- package/.agent-src/skills/funnel-analysis/SKILL.md +1 -0
- package/.agent-src/skills/git-workflow/SKILL.md +1 -0
- package/.agent-src/skills/github-ci/SKILL.md +1 -0
- package/.agent-src/skills/grafana/SKILL.md +1 -0
- package/.agent-src/skills/guideline-writing/SKILL.md +1 -0
- package/.agent-src/skills/incident-commander/SKILL.md +140 -0
- package/.agent-src/skills/jira-integration/SKILL.md +1 -0
- package/.agent-src/skills/jobs-events/SKILL.md +1 -0
- package/.agent-src/skills/judge-bug-hunter/SKILL.md +1 -0
- package/.agent-src/skills/judge-code-quality/SKILL.md +1 -0
- package/.agent-src/skills/judge-security-auditor/SKILL.md +3 -0
- package/.agent-src/skills/judge-test-coverage/SKILL.md +1 -0
- package/.agent-src/skills/laravel/SKILL.md +1 -0
- package/.agent-src/skills/laravel-horizon/SKILL.md +1 -0
- package/.agent-src/skills/laravel-mail/SKILL.md +1 -0
- package/.agent-src/skills/laravel-middleware/SKILL.md +1 -0
- package/.agent-src/skills/laravel-notifications/SKILL.md +1 -0
- package/.agent-src/skills/laravel-pennant/SKILL.md +1 -0
- package/.agent-src/skills/laravel-pulse/SKILL.md +1 -0
- package/.agent-src/skills/laravel-reverb/SKILL.md +1 -0
- package/.agent-src/skills/laravel-scheduling/SKILL.md +1 -0
- package/.agent-src/skills/laravel-validation/SKILL.md +1 -0
- package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +1 -0
- package/.agent-src/skills/lint-skills/SKILL.md +1 -0
- package/.agent-src/skills/livewire/SKILL.md +1 -0
- package/.agent-src/skills/livewire-architect/SKILL.md +158 -0
- package/.agent-src/skills/logging-monitoring/SKILL.md +1 -0
- package/.agent-src/skills/markitdown/SKILL.md +1 -0
- package/.agent-src/skills/mcp/SKILL.md +1 -0
- package/.agent-src/skills/mcp-builder/SKILL.md +1 -0
- package/.agent-src/skills/md-language-check/SKILL.md +1 -0
- package/.agent-src/skills/merge-conflicts/SKILL.md +1 -0
- package/.agent-src/skills/migration-architect/SKILL.md +119 -0
- package/.agent-src/skills/migration-creator/SKILL.md +1 -0
- package/.agent-src/skills/mobile-e2e-strategy/SKILL.md +2 -1
- package/.agent-src/skills/module-management/SKILL.md +1 -0
- package/.agent-src/skills/multi-tenancy/SKILL.md +1 -0
- package/.agent-src/skills/okr-tree-modeling/SKILL.md +1 -0
- package/.agent-src/skills/openapi/SKILL.md +1 -0
- package/.agent-src/skills/override-management/SKILL.md +1 -0
- package/.agent-src/skills/performance/SKILL.md +1 -0
- package/.agent-src/skills/performance-analysis/SKILL.md +1 -0
- package/.agent-src/skills/persona-writing/SKILL.md +1 -0
- package/.agent-src/skills/pest-testing/SKILL.md +1 -0
- package/.agent-src/skills/php-coder/SKILL.md +1 -0
- package/.agent-src/skills/php-debugging/SKILL.md +1 -0
- package/.agent-src/skills/php-service/SKILL.md +1 -0
- package/.agent-src/skills/playwright-architect/SKILL.md +141 -0
- package/.agent-src/skills/playwright-testing/SKILL.md +1 -0
- package/.agent-src/skills/po-discovery/SKILL.md +127 -0
- package/.agent-src/skills/project-analysis-core/SKILL.md +1 -0
- package/.agent-src/skills/project-analysis-hypothesis-driven/SKILL.md +1 -0
- package/.agent-src/skills/project-analysis-laravel/SKILL.md +1 -0
- package/.agent-src/skills/project-analysis-nextjs/SKILL.md +1 -0
- package/.agent-src/skills/project-analysis-node-express/SKILL.md +1 -0
- package/.agent-src/skills/project-analysis-react/SKILL.md +1 -0
- package/.agent-src/skills/project-analysis-symfony/SKILL.md +1 -0
- package/.agent-src/skills/project-analysis-zend-laminas/SKILL.md +1 -0
- package/.agent-src/skills/project-analyzer/SKILL.md +1 -0
- package/.agent-src/skills/project-docs/SKILL.md +1 -0
- package/.agent-src/skills/prompt-engineering-patterns/SKILL.md +1 -0
- package/.agent-src/skills/prompt-optimizer/SKILL.md +1 -0
- package/.agent-src/skills/quality-tools/SKILL.md +1 -0
- package/.agent-src/skills/react-native-setup/SKILL.md +1 -0
- package/.agent-src/skills/react-shadcn-ui/SKILL.md +1 -0
- package/.agent-src/skills/readme-reviewer/SKILL.md +1 -0
- package/.agent-src/skills/readme-writing/SKILL.md +1 -0
- package/.agent-src/skills/readme-writing-package/SKILL.md +1 -0
- package/.agent-src/skills/receiving-code-review/SKILL.md +1 -0
- package/.agent-src/skills/refine-prompt/SKILL.md +1 -0
- package/.agent-src/skills/refine-ticket/SKILL.md +1 -0
- package/.agent-src/skills/repomix-packer/SKILL.md +1 -0
- package/.agent-src/skills/requesting-code-review/SKILL.md +1 -0
- package/.agent-src/skills/review-routing/SKILL.md +1 -0
- package/.agent-src/skills/rice-prioritization/SKILL.md +1 -0
- package/.agent-src/skills/risk-officer/SKILL.md +141 -0
- package/.agent-src/skills/roadmap-management/SKILL.md +1 -0
- package/.agent-src/skills/roadmap-writing/SKILL.md +1 -0
- package/.agent-src/skills/rtk-output-filtering/SKILL.md +1 -0
- package/.agent-src/skills/rule-writing/SKILL.md +1 -0
- package/.agent-src/skills/script-writing/SKILL.md +1 -0
- package/.agent-src/skills/secrets-management/SKILL.md +1 -0
- package/.agent-src/skills/security/SKILL.md +1 -0
- package/.agent-src/skills/security-audit/SKILL.md +1 -0
- package/.agent-src/skills/sentry-integration/SKILL.md +1 -0
- package/.agent-src/skills/sequential-thinking/SKILL.md +1 -0
- package/.agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -0
- package/.agent-src/skills/skill-management/SKILL.md +1 -0
- package/.agent-src/skills/skill-reviewer/SKILL.md +1 -0
- package/.agent-src/skills/skill-writing/SKILL.md +1 -0
- package/.agent-src/skills/sql-writing/SKILL.md +1 -0
- package/.agent-src/skills/stakeholder-tradeoff/SKILL.md +149 -0
- package/.agent-src/skills/subagent-orchestration/SKILL.md +13 -0
- package/.agent-src/skills/systematic-debugging/SKILL.md +1 -0
- package/.agent-src/skills/tailwind-engineer/SKILL.md +130 -0
- package/.agent-src/skills/tech-debt-tracker/SKILL.md +152 -0
- package/.agent-src/skills/technical-specification/SKILL.md +1 -0
- package/.agent-src/skills/terraform/SKILL.md +1 -0
- package/.agent-src/skills/terragrunt/SKILL.md +1 -0
- package/.agent-src/skills/test-driven-development/SKILL.md +1 -0
- package/.agent-src/skills/test-performance/SKILL.md +1 -0
- package/.agent-src/skills/testing-anti-patterns/SKILL.md +1 -0
- package/.agent-src/skills/threat-modeling/SKILL.md +3 -0
- package/.agent-src/skills/token-optimizer/SKILL.md +1 -0
- package/.agent-src/skills/traefik/SKILL.md +1 -0
- package/.agent-src/skills/ui-component-architect/SKILL.md +153 -0
- package/.agent-src/skills/unit-economics-modeling/SKILL.md +1 -0
- package/.agent-src/skills/universal-project-analysis/SKILL.md +1 -0
- package/.agent-src/skills/upstream-contribute/SKILL.md +1 -0
- package/.agent-src/skills/using-git-worktrees/SKILL.md +1 -0
- package/.agent-src/skills/validate-feature-fit/SKILL.md +1 -0
- package/.agent-src/skills/verify-completion-evidence/SKILL.md +1 -0
- package/.agent-src/skills/websocket/SKILL.md +1 -0
- package/.claude-plugin/marketplace.json +17 -1
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +68 -0
- package/README.md +3 -3
- package/docs/architecture.md +3 -3
- package/docs/catalog.md +26 -5
- package/docs/contracts/command-clusters.md +1 -1
- package/docs/contracts/file-ownership-matrix.json +560 -0
- package/docs/contracts/persona-schema.md +136 -0
- package/docs/contracts/skill-domains.md +143 -0
- package/docs/decisions/ADR-005-subagent-worktrees.md +120 -0
- package/docs/decisions/ADR-006-skill-tools-python-pilot.md +114 -0
- package/docs/decisions/INDEX.md +3 -0
- package/docs/getting-started.md +1 -1
- package/docs/guidelines/agent-infra/5w2h-analysis.md +260 -0
- package/docs/guidelines/agent-infra/critical-thinking.md +156 -0
- package/docs/guidelines/agent-infra/first-principles.md +192 -0
- package/docs/guidelines/agent-infra/six-hats.md +353 -0
- package/docs/guidelines/agent-infra/systems-thinking.md +220 -0
- package/docs/personas.md +115 -0
- package/package.json +1 -1
- package/scripts/_backfill_skill_domains.py +140 -0
- package/scripts/_emit_domain_table.py +35 -0
- package/scripts/install-hooks.sh +21 -4
- package/scripts/lint_skill_tools.py +168 -0
- package/scripts/schemas/skill.schema.json +6 -1
- package/scripts/skill_linter.py +19 -4
- package/scripts/skill_tools/__init__.py +22 -0
- package/scripts/skill_tools/audit_persona_coverage.py +147 -0
- package/scripts/skill_tools/run_block_d_eval.py +129 -0
- package/scripts/skill_tools/score_skill_relevance.py +169 -0
- 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())
|