@softspark/ai-toolkit 1.0.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/AGENTS.md +412 -0
- package/CHANGELOG.md +68 -0
- package/LICENSE +21 -0
- package/README.md +632 -0
- package/action.yml +53 -0
- package/app/.claude-plugin/plugin.json +44 -0
- package/app/ARCHITECTURE.md +306 -0
- package/app/CLAUDE.md.template +23 -0
- package/app/agents/ai-engineer.md +128 -0
- package/app/agents/backend-specialist.md +193 -0
- package/app/agents/business-intelligence.md +54 -0
- package/app/agents/chaos-monkey.md +67 -0
- package/app/agents/chief-of-staff.md +51 -0
- package/app/agents/code-archaeologist.md +127 -0
- package/app/agents/code-reviewer.md +184 -0
- package/app/agents/command-expert.md +131 -0
- package/app/agents/data-analyst.md +205 -0
- package/app/agents/data-scientist.md +151 -0
- package/app/agents/database-architect.md +317 -0
- package/app/agents/debugger.md +238 -0
- package/app/agents/devops-implementer.md +194 -0
- package/app/agents/documenter.md +364 -0
- package/app/agents/explorer-agent.md +145 -0
- package/app/agents/fact-checker.md +172 -0
- package/app/agents/frontend-specialist.md +209 -0
- package/app/agents/game-developer.md +216 -0
- package/app/agents/incident-responder.md +226 -0
- package/app/agents/infrastructure-architect.md +127 -0
- package/app/agents/infrastructure-validator.md +247 -0
- package/app/agents/llm-ops-engineer.md +237 -0
- package/app/agents/mcp-expert.md +228 -0
- package/app/agents/mcp-server-architect.md +195 -0
- package/app/agents/mcp-testing-engineer.md +292 -0
- package/app/agents/meta-architect.md +58 -0
- package/app/agents/ml-engineer.md +136 -0
- package/app/agents/mobile-developer.md +190 -0
- package/app/agents/night-watchman.md +55 -0
- package/app/agents/nlp-engineer.md +154 -0
- package/app/agents/orchestrator.md +437 -0
- package/app/agents/performance-optimizer.md +254 -0
- package/app/agents/predictive-analyst.md +57 -0
- package/app/agents/product-manager.md +194 -0
- package/app/agents/project-planner.md +287 -0
- package/app/agents/prompt-engineer.md +103 -0
- package/app/agents/qa-automation-engineer.md +182 -0
- package/app/agents/rag-engineer.md +201 -0
- package/app/agents/research-synthesizer.md +138 -0
- package/app/agents/search-specialist.md +101 -0
- package/app/agents/security-architect.md +62 -0
- package/app/agents/security-auditor.md +293 -0
- package/app/agents/seo-specialist.md +111 -0
- package/app/agents/system-governor.md +57 -0
- package/app/agents/tech-lead.md +62 -0
- package/app/agents/technical-researcher.md +103 -0
- package/app/agents/test-engineer.md +264 -0
- package/app/constitution.md +38 -0
- package/app/hooks/_profile-check.sh +11 -0
- package/app/hooks/guard-destructive.sh +74 -0
- package/app/hooks/guard-path.sh +73 -0
- package/app/hooks/post-tool-use.sh +35 -0
- package/app/hooks/pre-compact.sh +31 -0
- package/app/hooks/quality-check.sh +22 -0
- package/app/hooks/quality-gate.sh +49 -0
- package/app/hooks/save-session.sh +24 -0
- package/app/hooks/session-end.sh +37 -0
- package/app/hooks/session-start.sh +29 -0
- package/app/hooks/subagent-start.sh +16 -0
- package/app/hooks/subagent-stop.sh +16 -0
- package/app/hooks/track-usage.sh +50 -0
- package/app/hooks/user-prompt-submit.sh +25 -0
- package/app/hooks.json +178 -0
- package/app/mcp-defaults.json +23 -0
- package/app/output-styles/golden-rules.md +43 -0
- package/app/plugins/README.md +19 -0
- package/app/plugins/csharp-pack/README.md +11 -0
- package/app/plugins/csharp-pack/plugin.json +18 -0
- package/app/plugins/enterprise-pack/README.md +16 -0
- package/app/plugins/enterprise-pack/hooks/output-style.sh +6 -0
- package/app/plugins/enterprise-pack/hooks/status-line.sh +8 -0
- package/app/plugins/enterprise-pack/plugin.json +24 -0
- package/app/plugins/frontend-pack/README.md +14 -0
- package/app/plugins/frontend-pack/plugin.json +22 -0
- package/app/plugins/java-pack/README.md +11 -0
- package/app/plugins/java-pack/plugin.json +18 -0
- package/app/plugins/kotlin-pack/README.md +11 -0
- package/app/plugins/kotlin-pack/plugin.json +18 -0
- package/app/plugins/memory-pack/README.md +24 -0
- package/app/plugins/memory-pack/hooks/observation-capture.sh +67 -0
- package/app/plugins/memory-pack/hooks/session-summary.sh +71 -0
- package/app/plugins/memory-pack/plugin.json +22 -0
- package/app/plugins/memory-pack/scripts/init_db.py +81 -0
- package/app/plugins/memory-pack/scripts/strip_private.py +22 -0
- package/app/plugins/memory-pack/skills/mem-search/SKILL.md +70 -0
- package/app/plugins/research-pack/README.md +14 -0
- package/app/plugins/research-pack/plugin.json +22 -0
- package/app/plugins/ruby-pack/README.md +11 -0
- package/app/plugins/ruby-pack/plugin.json +18 -0
- package/app/plugins/rust-pack/README.md +11 -0
- package/app/plugins/rust-pack/plugin.json +18 -0
- package/app/plugins/security-pack/README.md +15 -0
- package/app/plugins/security-pack/plugin.json +23 -0
- package/app/plugins/swift-pack/README.md +11 -0
- package/app/plugins/swift-pack/plugin.json +18 -0
- package/app/rules/claude-toolkit-rules.md +21 -0
- package/app/rules/git-conventions.md +5 -0
- package/app/rules/quality-gates.md +10 -0
- package/app/skills/_lib/__init__.py +1 -0
- package/app/skills/_lib/detect_utils.py +150 -0
- package/app/skills/agent-creator/SKILL.md +82 -0
- package/app/skills/analyze/SKILL.md +92 -0
- package/app/skills/analyze/scripts/complexity.py +165 -0
- package/app/skills/api-patterns/SKILL.md +305 -0
- package/app/skills/app-builder/SKILL.md +187 -0
- package/app/skills/architecture-audit/SKILL.md +141 -0
- package/app/skills/architecture-decision/SKILL.md +55 -0
- package/app/skills/architecture-decision/templates/adr-template.md +36 -0
- package/app/skills/biz-scan/SKILL.md +30 -0
- package/app/skills/briefing/SKILL.md +27 -0
- package/app/skills/build/SKILL.md +97 -0
- package/app/skills/build/scripts/detect-build.py +151 -0
- package/app/skills/chaos/SKILL.md +32 -0
- package/app/skills/ci/SKILL.md +77 -0
- package/app/skills/ci/scripts/ci-detect.py +135 -0
- package/app/skills/ci/templates/github-actions-node.yml +38 -0
- package/app/skills/ci/templates/github-actions-python.yml +42 -0
- package/app/skills/ci-cd-patterns/SKILL.md +299 -0
- package/app/skills/clean-code/SKILL.md +110 -0
- package/app/skills/clean-code/reference/dart.md +18 -0
- package/app/skills/clean-code/reference/go.md +23 -0
- package/app/skills/clean-code/reference/php.md +32 -0
- package/app/skills/clean-code/reference/python.md +180 -0
- package/app/skills/clean-code/reference/typescript.md +26 -0
- package/app/skills/command-creator/SKILL.md +83 -0
- package/app/skills/commit/SKILL.md +98 -0
- package/app/skills/commit/scripts/pre-commit-check.py +87 -0
- package/app/skills/commit/templates/conventional-commit.md +52 -0
- package/app/skills/csharp-patterns/SKILL.md +450 -0
- package/app/skills/database-patterns/SKILL.md +297 -0
- package/app/skills/debug/SKILL.md +154 -0
- package/app/skills/debug/scripts/error-parser.py +187 -0
- package/app/skills/debugging-tactics/SKILL.md +136 -0
- package/app/skills/deploy/SKILL.md +130 -0
- package/app/skills/deploy/scripts/pre_deploy_check.py +171 -0
- package/app/skills/deploy/templates/deployment-checklist.md +31 -0
- package/app/skills/design-an-interface/SKILL.md +105 -0
- package/app/skills/design-engineering/SKILL.md +260 -0
- package/app/skills/docker-devops/SKILL.md +303 -0
- package/app/skills/docs/SKILL.md +145 -0
- package/app/skills/docs/scripts/doc-inventory.py +176 -0
- package/app/skills/docs/templates/adr-template.md +36 -0
- package/app/skills/docs/templates/readme-template.md +67 -0
- package/app/skills/documentation-standards/SKILL.md +191 -0
- package/app/skills/ecommerce-patterns/SKILL.md +209 -0
- package/app/skills/evaluate/SKILL.md +132 -0
- package/app/skills/evolve/SKILL.md +27 -0
- package/app/skills/explain/SKILL.md +54 -0
- package/app/skills/explain/scripts/dependency-graph.py +215 -0
- package/app/skills/explore/SKILL.md +112 -0
- package/app/skills/explore/scripts/visualize.py +117 -0
- package/app/skills/fix/SKILL.md +78 -0
- package/app/skills/fix/scripts/error-classifier.py +191 -0
- package/app/skills/flutter-patterns/SKILL.md +254 -0
- package/app/skills/git-mastery/SKILL.md +70 -0
- package/app/skills/grill-me/SKILL.md +38 -0
- package/app/skills/health/SKILL.md +91 -0
- package/app/skills/health/scripts/health_check.py +162 -0
- package/app/skills/hive-mind/SKILL.md +56 -0
- package/app/skills/hook-creator/SKILL.md +107 -0
- package/app/skills/index/SKILL.md +74 -0
- package/app/skills/instinct-review/SKILL.md +77 -0
- package/app/skills/java-patterns/SKILL.md +442 -0
- package/app/skills/kotlin-patterns/SKILL.md +446 -0
- package/app/skills/lint/SKILL.md +103 -0
- package/app/skills/lint/scripts/detect-linters.py +112 -0
- package/app/skills/mcp-patterns/SKILL.md +270 -0
- package/app/skills/mem-search/SKILL.md +70 -0
- package/app/skills/migrate/SKILL.md +90 -0
- package/app/skills/migrate/scripts/migration-status.py +195 -0
- package/app/skills/migration-patterns/SKILL.md +260 -0
- package/app/skills/night-watch/SKILL.md +28 -0
- package/app/skills/observability-patterns/SKILL.md +203 -0
- package/app/skills/onboard/SKILL.md +76 -0
- package/app/skills/orchestrate/SKILL.md +86 -0
- package/app/skills/panic/SKILL.md +30 -0
- package/app/skills/performance-profiling/SKILL.md +59 -0
- package/app/skills/plan/SKILL.md +110 -0
- package/app/skills/plan/templates/plan-template.md +40 -0
- package/app/skills/plan-writing/SKILL.md +201 -0
- package/app/skills/plugin-creator/SKILL.md +78 -0
- package/app/skills/pr/SKILL.md +129 -0
- package/app/skills/pr/scripts/pr-summary.py +175 -0
- package/app/skills/prd-to-issues/SKILL.md +108 -0
- package/app/skills/prd-to-plan/SKILL.md +120 -0
- package/app/skills/predict/SKILL.md +30 -0
- package/app/skills/qa-session/SKILL.md +110 -0
- package/app/skills/rag-patterns/SKILL.md +203 -0
- package/app/skills/refactor/SKILL.md +124 -0
- package/app/skills/refactor/scripts/refactor-scan.py +210 -0
- package/app/skills/refactor-plan/SKILL.md +112 -0
- package/app/skills/repeat/SKILL.md +149 -0
- package/app/skills/research-mastery/SKILL.md +56 -0
- package/app/skills/review/SKILL.md +141 -0
- package/app/skills/review/scripts/diff-analyzer.py +170 -0
- package/app/skills/rollback/SKILL.md +87 -0
- package/app/skills/rollback/scripts/rollback_info.py +149 -0
- package/app/skills/ruby-patterns/SKILL.md +454 -0
- package/app/skills/rust-patterns/SKILL.md +446 -0
- package/app/skills/search/SKILL.md +64 -0
- package/app/skills/security-patterns/SKILL.md +91 -0
- package/app/skills/security-patterns/reference/authentication.md +37 -0
- package/app/skills/security-patterns/reference/authorization.md +22 -0
- package/app/skills/security-patterns/reference/input-validation.md +30 -0
- package/app/skills/security-patterns/reference/oauth-csrf-audit.md +131 -0
- package/app/skills/skill-creator/SKILL.md +154 -0
- package/app/skills/skill-creator/templates/dashboard/index.html +130 -0
- package/app/skills/skill-creator/templates/reasoning-engine/assets/example.json +12 -0
- package/app/skills/skill-creator/templates/reasoning-engine/search.py +110 -0
- package/app/skills/subagent-development/SKILL.md +225 -0
- package/app/skills/subagent-development/reference/code-quality-reviewer-prompt.md +145 -0
- package/app/skills/subagent-development/reference/implementer-prompt.md +118 -0
- package/app/skills/subagent-development/reference/spec-reviewer-prompt.md +100 -0
- package/app/skills/swarm/SKILL.md +81 -0
- package/app/skills/swift-patterns/SKILL.md +500 -0
- package/app/skills/tdd/SKILL.md +174 -0
- package/app/skills/tdd/reference/deep-modules.md +32 -0
- package/app/skills/tdd/reference/interface-design.md +32 -0
- package/app/skills/tdd/reference/mocking.md +52 -0
- package/app/skills/tdd/reference/refactoring.md +10 -0
- package/app/skills/tdd/reference/tests.md +59 -0
- package/app/skills/teams/SKILL.md +101 -0
- package/app/skills/test/SKILL.md +107 -0
- package/app/skills/test/scripts/detect-runner.py +113 -0
- package/app/skills/testing-patterns/SKILL.md +73 -0
- package/app/skills/testing-patterns/reference/flutter-testing.md +33 -0
- package/app/skills/testing-patterns/reference/go-testing.md +52 -0
- package/app/skills/testing-patterns/reference/php-phpunit.md +39 -0
- package/app/skills/testing-patterns/reference/python-pytest.md +228 -0
- package/app/skills/testing-patterns/reference/typescript-vitest.md +50 -0
- package/app/skills/triage-issue/SKILL.md +120 -0
- package/app/skills/typescript-patterns/SKILL.md +256 -0
- package/app/skills/ubiquitous-language/SKILL.md +74 -0
- package/app/skills/verification-before-completion/SKILL.md +108 -0
- package/app/skills/workflow/SKILL.md +250 -0
- package/app/skills/write-a-prd/SKILL.md +129 -0
- package/app/skills/write-a-prd/reference/visual-companion.md +78 -0
- package/app/skills/write-a-prd/scripts/frame-template.html +111 -0
- package/app/skills/write-a-prd/scripts/visual-server.cjs +79 -0
- package/app/templates/skill/generator/SKILL.md.template +40 -0
- package/app/templates/skill/knowledge/SKILL.md.template +52 -0
- package/app/templates/skill/linter/SKILL.md.template +34 -0
- package/app/templates/skill/reviewer/SKILL.md.template +51 -0
- package/app/templates/skill/workflow/SKILL.md.template +49 -0
- package/benchmarks/README.md +111 -0
- package/benchmarks/ecosystem-dashboard.json +148 -0
- package/benchmarks/ecosystem-harvest.json +148 -0
- package/benchmarks/results.json +38 -0
- package/benchmarks/run.py +351 -0
- package/bin/ai-toolkit.js +345 -0
- package/kb/best-practices/README.md +11 -0
- package/kb/howto/README.md +11 -0
- package/kb/procedures/maintenance-sop.md +306 -0
- package/kb/reference/agents-catalog.md +124 -0
- package/kb/reference/anti-pattern-registry-format.md +221 -0
- package/kb/reference/architecture-overview.md +232 -0
- package/kb/reference/benchmark-config.md +62 -0
- package/kb/reference/ci-integration.md +66 -0
- package/kb/reference/claude-ecosystem-benchmark-snapshot.md +80 -0
- package/kb/reference/claude-ecosystem-expansion-foundations.md +102 -0
- package/kb/reference/commands-catalog.md +21 -0
- package/kb/reference/distribution-model.md +63 -0
- package/kb/reference/global-install-model.md +56 -0
- package/kb/reference/hierarchical-override-pattern.md +200 -0
- package/kb/reference/hooks-catalog.md +306 -0
- package/kb/reference/integrations.md +88 -0
- package/kb/reference/language-packs.md +52 -0
- package/kb/reference/merge-friendly-install-model.md +58 -0
- package/kb/reference/plugin-pack-conventions.md +151 -0
- package/kb/reference/quick-wins-implementation-summary.md +70 -0
- package/kb/reference/skill-templates.md +50 -0
- package/kb/reference/skills-catalog.md +215 -0
- package/kb/reference/skills-unification.md +57 -0
- package/kb/reference/stats.md +69 -0
- package/kb/reference/sync.md +76 -0
- package/kb/troubleshooting/README.md +11 -0
- package/llms-full.txt +3068 -0
- package/llms.txt +39 -0
- package/package.json +75 -0
- package/scripts/_common.py +160 -0
- package/scripts/add_rule.py +50 -0
- package/scripts/benchmark_config.py +127 -0
- package/scripts/benchmark_ecosystem.py +288 -0
- package/scripts/check_deps.py +260 -0
- package/scripts/create_skill.py +118 -0
- package/scripts/doctor.py +504 -0
- package/scripts/eject.py +113 -0
- package/scripts/emission.py +256 -0
- package/scripts/evaluate_skills.py +260 -0
- package/scripts/frontmatter.py +58 -0
- package/scripts/generate_agents_md.py +91 -0
- package/scripts/generate_aider_conf.py +51 -0
- package/scripts/generate_cline.py +35 -0
- package/scripts/generate_copilot.py +30 -0
- package/scripts/generate_cursor_rules.py +35 -0
- package/scripts/generate_gemini.py +28 -0
- package/scripts/generate_llms_txt.py +164 -0
- package/scripts/generate_roo_modes.py +80 -0
- package/scripts/generate_windsurf.py +35 -0
- package/scripts/generator_base.py +140 -0
- package/scripts/harvest_ecosystem.py +50 -0
- package/scripts/inject_rule_cli.py +101 -0
- package/scripts/inject_section_cli.py +47 -0
- package/scripts/injection.py +180 -0
- package/scripts/install.py +236 -0
- package/scripts/install_git_hooks.py +71 -0
- package/scripts/install_steps/__init__.py +5 -0
- package/scripts/install_steps/ai_tools.py +261 -0
- package/scripts/install_steps/hooks.py +90 -0
- package/scripts/install_steps/markers.py +79 -0
- package/scripts/install_steps/symlinks.py +87 -0
- package/scripts/merge-hooks.py +192 -0
- package/scripts/plugin.py +642 -0
- package/scripts/plugin_schema.py +138 -0
- package/scripts/remove_rule.py +58 -0
- package/scripts/stats.py +81 -0
- package/scripts/sync.py +215 -0
- package/scripts/uninstall.py +292 -0
- package/scripts/validate.py +700 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Markdown emission helpers for agent/skill listing and generator content blocks.
|
|
2
|
+
|
|
3
|
+
Provides functions to emit agents and skills as markdown headings or
|
|
4
|
+
bullet lists, plus shared content blocks (guidelines, quality standards)
|
|
5
|
+
used by the various ``generate_*.py`` scripts.
|
|
6
|
+
|
|
7
|
+
Stdlib-only.
|
|
8
|
+
|
|
9
|
+
Usage::
|
|
10
|
+
|
|
11
|
+
from emission import emit_agents_headings, emit_skills_bullets
|
|
12
|
+
from emission import generate_general_guidelines
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from frontmatter import frontmatter_field
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _resolve_toolkit_dir() -> Path:
|
|
22
|
+
"""Resolve the toolkit root directory (parent of scripts/)."""
|
|
23
|
+
return Path(__file__).resolve().parent.parent
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
toolkit_dir: Path = _resolve_toolkit_dir()
|
|
27
|
+
app_dir: Path = toolkit_dir / "app"
|
|
28
|
+
agents_dir: Path = app_dir / "agents"
|
|
29
|
+
skills_dir: Path = app_dir / "skills"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Counting
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
def agent_count() -> int:
|
|
37
|
+
"""Count agent .md files in app/agents/."""
|
|
38
|
+
if not agents_dir.is_dir():
|
|
39
|
+
return 0
|
|
40
|
+
return sum(1 for f in agents_dir.iterdir() if f.suffix == ".md" and f.is_file())
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def skill_count() -> int:
|
|
44
|
+
"""Count skill directories containing SKILL.md in app/skills/.
|
|
45
|
+
|
|
46
|
+
Directories starting with ``_`` (like ``_lib``) are excluded.
|
|
47
|
+
"""
|
|
48
|
+
if not skills_dir.is_dir():
|
|
49
|
+
return 0
|
|
50
|
+
return sum(1 for d in skills_dir.iterdir()
|
|
51
|
+
if d.is_dir() and not d.name.startswith("_") and (d / "SKILL.md").is_file())
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def count_agents_and_skills() -> tuple[int, int]:
|
|
55
|
+
"""Return (agent_count, skill_count) as a convenience tuple."""
|
|
56
|
+
return agent_count(), skill_count()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Markdown emission
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
def emit_agents_headings(level: str = "##") -> str:
|
|
64
|
+
"""Emit agents as markdown headings with descriptions."""
|
|
65
|
+
lines: list[str] = []
|
|
66
|
+
for agent_file in sorted(agents_dir.glob("*.md")):
|
|
67
|
+
name = frontmatter_field(agent_file, "name")
|
|
68
|
+
description = frontmatter_field(agent_file, "description")
|
|
69
|
+
if not name or not description:
|
|
70
|
+
continue
|
|
71
|
+
lines.append(f"{level} {name}")
|
|
72
|
+
lines.append(description)
|
|
73
|
+
lines.append("")
|
|
74
|
+
return "\n".join(lines)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def emit_agents_bullets() -> str:
|
|
78
|
+
"""Emit agents as bullet list: - **name**: description."""
|
|
79
|
+
lines: list[str] = []
|
|
80
|
+
for agent_file in sorted(agents_dir.glob("*.md")):
|
|
81
|
+
name = frontmatter_field(agent_file, "name")
|
|
82
|
+
description = frontmatter_field(agent_file, "description")
|
|
83
|
+
if not name or not description:
|
|
84
|
+
continue
|
|
85
|
+
lines.append(f"- **{name}**: {description}")
|
|
86
|
+
return "\n".join(lines)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def emit_skills_headings(level: str = "##") -> str:
|
|
90
|
+
"""Emit skills as markdown headings with descriptions."""
|
|
91
|
+
lines: list[str] = []
|
|
92
|
+
for skill_dir in sorted(skills_dir.iterdir()):
|
|
93
|
+
if skill_dir.name.startswith("_"):
|
|
94
|
+
continue
|
|
95
|
+
skill_file = skill_dir / "SKILL.md"
|
|
96
|
+
if not skill_file.is_file():
|
|
97
|
+
continue
|
|
98
|
+
name = frontmatter_field(skill_file, "name")
|
|
99
|
+
description = frontmatter_field(skill_file, "description")
|
|
100
|
+
if not name or not description:
|
|
101
|
+
continue
|
|
102
|
+
lines.append(f"{level} {name}")
|
|
103
|
+
lines.append(description)
|
|
104
|
+
lines.append("")
|
|
105
|
+
return "\n".join(lines)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def emit_skills_bullets() -> str:
|
|
109
|
+
"""Emit skills as bullet list: - **name**: description."""
|
|
110
|
+
lines: list[str] = []
|
|
111
|
+
for skill_dir in sorted(skills_dir.iterdir()):
|
|
112
|
+
if skill_dir.name.startswith("_"):
|
|
113
|
+
continue
|
|
114
|
+
skill_file = skill_dir / "SKILL.md"
|
|
115
|
+
if not skill_file.is_file():
|
|
116
|
+
continue
|
|
117
|
+
name = frontmatter_field(skill_file, "name")
|
|
118
|
+
description = frontmatter_field(skill_file, "description")
|
|
119
|
+
if not name or not description:
|
|
120
|
+
continue
|
|
121
|
+
lines.append(f"- **{name}**: {description}")
|
|
122
|
+
return "\n".join(lines)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
# Toolkit markers (print helpers)
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
def print_toolkit_start() -> None:
|
|
130
|
+
"""Print the standard toolkit start marker and auto-generated comment."""
|
|
131
|
+
print("<!-- TOOLKIT:ai-toolkit START -->")
|
|
132
|
+
print("<!-- Auto-generated by ai-toolkit. Re-run to update. -->")
|
|
133
|
+
print()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def print_toolkit_end() -> None:
|
|
137
|
+
"""Print the standard toolkit end marker."""
|
|
138
|
+
print("<!-- TOOLKIT:ai-toolkit END -->")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
# Shared content blocks for generators
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
def generate_general_guidelines() -> str:
|
|
146
|
+
"""Return the general guidelines block used by cursor/cline/windsurf generators."""
|
|
147
|
+
lines = [
|
|
148
|
+
"## General Guidelines",
|
|
149
|
+
"",
|
|
150
|
+
'- Apply "Safety First": no data loss, no blind execution, max 3 loop iterations',
|
|
151
|
+
"- Research before acting: check existing code and context before proposing changes",
|
|
152
|
+
"- Use structured commits: feat/fix/docs/refactor/test/chore prefixes",
|
|
153
|
+
"- Quality gates: lint must pass, types must check, tests must be green before done",
|
|
154
|
+
"- Prefer editing existing files over creating new ones",
|
|
155
|
+
"- Never commit secrets or credentials",
|
|
156
|
+
]
|
|
157
|
+
return "\n".join(lines)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def generate_quality_standards() -> str:
|
|
161
|
+
"""Return the full constitution-based quality standards for Gemini."""
|
|
162
|
+
lines = [
|
|
163
|
+
"## Quality Standards",
|
|
164
|
+
"",
|
|
165
|
+
"Derived from the immutable safety constitution (5 articles):",
|
|
166
|
+
"",
|
|
167
|
+
"**Article I — Safety First**",
|
|
168
|
+
"- No data loss: never delete files without backup verification"
|
|
169
|
+
" or using reversible operations",
|
|
170
|
+
"- No blind execution: never run LLM-generated code without"
|
|
171
|
+
" static analysis or review",
|
|
172
|
+
"- No infinite loops: all autonomous loops must have a maximum"
|
|
173
|
+
" iteration count (max 3)",
|
|
174
|
+
"",
|
|
175
|
+
"**Article II — Hierarchy of Truth**",
|
|
176
|
+
"- The Knowledge Base (`kb/`) is the source of truth;"
|
|
177
|
+
" if code contradicts KB, check KB freshness",
|
|
178
|
+
"- Use the research-mastery skill before any major decision;"
|
|
179
|
+
" guessing is forbidden",
|
|
180
|
+
"",
|
|
181
|
+
"**Article III — Operational Integrity**",
|
|
182
|
+
'- "Green Tests" is the only definition of Done;'
|
|
183
|
+
" forced merges on red tests are unacceptable",
|
|
184
|
+
"- Never delete audit logs or KB archives without explicit"
|
|
185
|
+
" user approval and backup verification",
|
|
186
|
+
"- Agents cannot change their own model or tool permissions"
|
|
187
|
+
" without user approval",
|
|
188
|
+
"",
|
|
189
|
+
"**Article IV — Self-Preservation**",
|
|
190
|
+
"- The constitution file is read-only for all agents except the user",
|
|
191
|
+
"- If a constitutional violation is detected, halt the offending"
|
|
192
|
+
" operation immediately",
|
|
193
|
+
"",
|
|
194
|
+
"**Article V — Resource Governance**",
|
|
195
|
+
"- Commands like `rm -rf`, `DROP TABLE`, `FORMAT` require explicit"
|
|
196
|
+
" user confirmation",
|
|
197
|
+
"- Operate within assigned model tiers; model tier changes"
|
|
198
|
+
" require user approval",
|
|
199
|
+
]
|
|
200
|
+
return "\n".join(lines)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def generate_workflow_guidelines() -> str:
|
|
204
|
+
"""Return the workflow guidelines block used by the Gemini generator."""
|
|
205
|
+
lines = [
|
|
206
|
+
"## Workflow Guidelines",
|
|
207
|
+
"",
|
|
208
|
+
"- **Plan First**: Tasks longer than 1 hour require a plan,"
|
|
209
|
+
" success criteria, and pre-mortem",
|
|
210
|
+
"- **Multi-Agent**: Use minimum 3 agents for complex tasks;"
|
|
211
|
+
" single-agent for simple tasks",
|
|
212
|
+
"- **2-Phase Execution**: Plan \u2192 User Approval \u2192 Implement"
|
|
213
|
+
" (never skip the approval checkpoint)",
|
|
214
|
+
"- **KB-First Research**: Search the knowledge base before writing"
|
|
215
|
+
" code or answering questions",
|
|
216
|
+
"- **Structured Commits**: Use `feat/fix/docs/refactor/test/chore`"
|
|
217
|
+
" prefixes (Conventional Commits)",
|
|
218
|
+
"- **Quality Gates**: Run `ruff check .` (Python), `tsc` (TypeScript),"
|
|
219
|
+
" `go vet` (Go) before marking done",
|
|
220
|
+
"- **Cite Sources**: Always reference `[PATH: ...]` when making"
|
|
221
|
+
" decisions based on existing knowledge",
|
|
222
|
+
"- **Read-Only Exploration**: Discovery agents never write;"
|
|
223
|
+
" writing agents never explore blindly",
|
|
224
|
+
"- **No Secrets in Code**: Never commit credentials, API keys,"
|
|
225
|
+
" or sensitive configuration values",
|
|
226
|
+
]
|
|
227
|
+
return "\n".join(lines)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def generate_quality_guidelines() -> str:
|
|
231
|
+
"""Return the quality guidelines block used by the Copilot generator."""
|
|
232
|
+
lines = [
|
|
233
|
+
"## Quality Guidelines",
|
|
234
|
+
"",
|
|
235
|
+
'- **Safety First**: No data loss, no blind execution,'
|
|
236
|
+
" maximum 3 autonomous loop iterations",
|
|
237
|
+
"- **No Blind Execution**: Never run LLM-generated code"
|
|
238
|
+
" without static analysis or review",
|
|
239
|
+
'- **Tests are Sacred**: "Green Tests" is the only definition of Done;'
|
|
240
|
+
" never force-merge on red tests",
|
|
241
|
+
"- **No Destructive Commands**: Commands like `rm -rf`, `DROP TABLE`,"
|
|
242
|
+
" `FORMAT` require explicit user confirmation",
|
|
243
|
+
"- **No Secrets in Code**: Never commit secrets, credentials,"
|
|
244
|
+
" or API keys to the repository",
|
|
245
|
+
"- **Research Before Acting**: Check existing code and context"
|
|
246
|
+
" before proposing changes",
|
|
247
|
+
"- **Structured Commits**: Use `feat/fix/docs/refactor/test/chore`"
|
|
248
|
+
" prefixes (Conventional Commits)",
|
|
249
|
+
"- **Quality Gates**: Lint must pass, types must check,"
|
|
250
|
+
" tests must be green before marking done",
|
|
251
|
+
"- **Prefer Editing**: Edit existing files over creating new ones;"
|
|
252
|
+
" avoid unnecessary churn",
|
|
253
|
+
"- **Cite Sources**: Always reference where information came from"
|
|
254
|
+
" when making architectural decisions",
|
|
255
|
+
]
|
|
256
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""AI Toolkit Skill Evaluation.
|
|
3
|
+
|
|
4
|
+
Validates all skills against the Agent Skills standard.
|
|
5
|
+
Checks frontmatter fields, naming, classification, reference links,
|
|
6
|
+
template directories, and dependency resolution.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
evaluate_skills.py
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
18
|
+
from _common import frontmatter_block, frontmatter_field, skills_dir
|
|
19
|
+
|
|
20
|
+
# Deprecated frontmatter fields and their replacements
|
|
21
|
+
_DEPRECATED_FIELDS: list[tuple[str, str]] = [
|
|
22
|
+
("version", "deprecated 'version' field in frontmatter"),
|
|
23
|
+
("color", "deprecated 'color' field in frontmatter"),
|
|
24
|
+
("tools", "deprecated 'tools' field (use 'allowed-tools')"),
|
|
25
|
+
("delegate-agent", "deprecated 'delegate-agent' field (rename to 'agent:')"),
|
|
26
|
+
("run-mode", "deprecated 'run-mode' field (rename to 'context:')"),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _fm_has(fm_text: str, field: str) -> bool:
|
|
31
|
+
"""Check if frontmatter text contains a field line."""
|
|
32
|
+
return any(
|
|
33
|
+
line.startswith(f"{field}:")
|
|
34
|
+
for line in fm_text.splitlines()
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _fm_value(fm_text: str, field: str) -> str:
|
|
39
|
+
"""Extract a field value from raw frontmatter text."""
|
|
40
|
+
for line in fm_text.splitlines():
|
|
41
|
+
if line.startswith(f"{field}:"):
|
|
42
|
+
val = line[len(field) + 1:].strip()
|
|
43
|
+
if len(val) >= 2 and val[0] == val[-1] and val[0] in ('"', "'"):
|
|
44
|
+
val = val[1:-1]
|
|
45
|
+
return val
|
|
46
|
+
return ""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _check_frontmatter_fields(fm: str, skill_path: Path, line_count: int) -> list[str]:
|
|
50
|
+
"""Check required fields, name format, description, line count, and deprecated fields."""
|
|
51
|
+
issues: list[str] = []
|
|
52
|
+
|
|
53
|
+
if not _fm_has(fm, "name"):
|
|
54
|
+
issues.append("missing name field")
|
|
55
|
+
if not _fm_has(fm, "description"):
|
|
56
|
+
issues.append("missing description field")
|
|
57
|
+
|
|
58
|
+
name_value = _fm_value(fm, "name")
|
|
59
|
+
if name_value:
|
|
60
|
+
if len(name_value) > 64:
|
|
61
|
+
issues.append(f"name exceeds 64 chars ({len(name_value)})")
|
|
62
|
+
if re.search(r"[^a-z0-9-]", name_value):
|
|
63
|
+
issues.append(f"name has invalid chars: {name_value}")
|
|
64
|
+
|
|
65
|
+
desc_value = _fm_value(fm, "description")
|
|
66
|
+
if desc_value and re.match(r"^(I |You |We )", desc_value):
|
|
67
|
+
issues.append("description not in third person")
|
|
68
|
+
|
|
69
|
+
if line_count > 500:
|
|
70
|
+
issues.append(f"SKILL.md has {line_count} lines (max: 500)")
|
|
71
|
+
|
|
72
|
+
for field, message in _DEPRECATED_FIELDS:
|
|
73
|
+
if _fm_has(fm, field):
|
|
74
|
+
issues.append(message)
|
|
75
|
+
|
|
76
|
+
return issues
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _check_references_and_templates(skill_path: Path, content: str) -> list[str]:
|
|
80
|
+
"""Check reference links, orphan files, and template directories."""
|
|
81
|
+
issues: list[str] = []
|
|
82
|
+
|
|
83
|
+
ref_dir = skill_path / "reference"
|
|
84
|
+
if ref_dir.is_dir():
|
|
85
|
+
for match in re.finditer(r"\(reference/([^)]+)\)", content):
|
|
86
|
+
ref_link = match.group(0).strip("()")
|
|
87
|
+
if not (skill_path / ref_link).is_file():
|
|
88
|
+
issues.append(f"broken reference link: {ref_link}")
|
|
89
|
+
for ref_file in sorted(ref_dir.glob("*.md")):
|
|
90
|
+
if f"reference/{ref_file.name}" not in content:
|
|
91
|
+
issues.append(f"orphan reference file: reference/{ref_file.name}")
|
|
92
|
+
|
|
93
|
+
tmpl_dir = skill_path / "templates"
|
|
94
|
+
if tmpl_dir.is_dir():
|
|
95
|
+
tmpl_files = [f for f in tmpl_dir.rglob("*") if f.is_file()]
|
|
96
|
+
if not tmpl_files:
|
|
97
|
+
issues.append("empty templates/ directory")
|
|
98
|
+
|
|
99
|
+
return issues
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _determine_type_label(fm: str) -> str:
|
|
103
|
+
"""Determine skill type from frontmatter fields."""
|
|
104
|
+
has_disable = _fm_has(fm, "disable-model-invocation") and "true" in _fm_value(fm, "disable-model-invocation")
|
|
105
|
+
has_user_invocable_false = _fm_has(fm, "user-invocable") and "false" in _fm_value(fm, "user-invocable")
|
|
106
|
+
|
|
107
|
+
if has_disable:
|
|
108
|
+
return "task"
|
|
109
|
+
elif has_user_invocable_false:
|
|
110
|
+
return "knowledge"
|
|
111
|
+
return "hybrid"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _evaluate_skill(skill_path: Path) -> tuple[str, list[str]]:
|
|
115
|
+
"""Evaluate a single skill directory.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
(type_label, issues) where type_label is "task", "knowledge", or "hybrid"
|
|
119
|
+
and issues is a list of problem descriptions (empty if passing).
|
|
120
|
+
"""
|
|
121
|
+
skill_file = skill_path / "SKILL.md"
|
|
122
|
+
|
|
123
|
+
if not skill_file.is_file():
|
|
124
|
+
return ("unknown", ["Missing SKILL.md"])
|
|
125
|
+
|
|
126
|
+
fm = frontmatter_block(skill_file)
|
|
127
|
+
content = skill_file.read_text(encoding="utf-8")
|
|
128
|
+
line_count = content.count("\n") + (1 if content and not content.endswith("\n") else 0)
|
|
129
|
+
|
|
130
|
+
issues = _check_frontmatter_fields(fm, skill_path, line_count)
|
|
131
|
+
issues.extend(_check_references_and_templates(skill_path, content))
|
|
132
|
+
|
|
133
|
+
return (_determine_type_label(fm), issues)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _evaluate_all_skills() -> tuple[int, int, int, int, int]:
|
|
137
|
+
"""Evaluate all skills and print per-skill results.
|
|
138
|
+
|
|
139
|
+
Returns (total, pass_count, fail_count, task_count, knowledge_count).
|
|
140
|
+
"""
|
|
141
|
+
pass_count = 0
|
|
142
|
+
fail_count = 0
|
|
143
|
+
total = 0
|
|
144
|
+
task_count = 0
|
|
145
|
+
knowledge_count = 0
|
|
146
|
+
|
|
147
|
+
for skill_path in sorted(skills_dir.iterdir()):
|
|
148
|
+
if not skill_path.is_dir() or skill_path.name.startswith("_"):
|
|
149
|
+
continue
|
|
150
|
+
total += 1
|
|
151
|
+
skill_file = skill_path / "SKILL.md"
|
|
152
|
+
|
|
153
|
+
if not skill_file.is_file():
|
|
154
|
+
print(f"FAIL: {skill_path.name} - Missing SKILL.md")
|
|
155
|
+
fail_count += 1
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
content = skill_file.read_text(encoding="utf-8")
|
|
159
|
+
line_count = content.count("\n") + (1 if content and not content.endswith("\n") else 0)
|
|
160
|
+
|
|
161
|
+
type_label, issues = _evaluate_skill(skill_path)
|
|
162
|
+
|
|
163
|
+
if issues:
|
|
164
|
+
print(f"FAIL: {skill_path.name} ({line_count} lines)")
|
|
165
|
+
for issue in issues:
|
|
166
|
+
print(f" {issue}")
|
|
167
|
+
print()
|
|
168
|
+
fail_count += 1
|
|
169
|
+
else:
|
|
170
|
+
print(f"PASS: {skill_path.name} ({type_label}, {line_count} lines)")
|
|
171
|
+
pass_count += 1
|
|
172
|
+
|
|
173
|
+
if type_label == "task":
|
|
174
|
+
task_count += 1
|
|
175
|
+
elif type_label == "knowledge":
|
|
176
|
+
knowledge_count += 1
|
|
177
|
+
|
|
178
|
+
return total, pass_count, fail_count, task_count, knowledge_count
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _collect_quality_metrics() -> tuple[int, int, int, int, int, int]:
|
|
182
|
+
"""Collect quality metrics across all skills.
|
|
183
|
+
|
|
184
|
+
Returns (ref_count, tmpl_count, inject_count, over500, depends_count, orphan_deps).
|
|
185
|
+
"""
|
|
186
|
+
ref_count = sum(1 for _ in skills_dir.rglob("reference/*.md"))
|
|
187
|
+
tmpl_count = sum(1 for _ in skills_dir.rglob("templates/*") if _.is_file())
|
|
188
|
+
|
|
189
|
+
inject_count = 0
|
|
190
|
+
over500 = 0
|
|
191
|
+
depends_count = 0
|
|
192
|
+
orphan_deps = 0
|
|
193
|
+
|
|
194
|
+
for sf in skills_dir.glob("*/SKILL.md"):
|
|
195
|
+
text = sf.read_text(encoding="utf-8")
|
|
196
|
+
lc = text.count("\n") + (1 if text and not text.endswith("\n") else 0)
|
|
197
|
+
if "!`" in text:
|
|
198
|
+
inject_count += 1
|
|
199
|
+
if lc > 500:
|
|
200
|
+
over500 += 1
|
|
201
|
+
fm = frontmatter_block(sf)
|
|
202
|
+
dep_line = _fm_value(fm, "depends-on")
|
|
203
|
+
if dep_line:
|
|
204
|
+
depends_count += 1
|
|
205
|
+
for dep in dep_line.split(","):
|
|
206
|
+
dep = dep.strip()
|
|
207
|
+
if not dep:
|
|
208
|
+
continue
|
|
209
|
+
if not (skills_dir / dep / "SKILL.md").is_file():
|
|
210
|
+
orphan_deps += 1
|
|
211
|
+
|
|
212
|
+
return ref_count, tmpl_count, inject_count, over500, depends_count, orphan_deps
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def main() -> None:
|
|
216
|
+
"""Run evaluation across all skills and print report."""
|
|
217
|
+
print("AI Toolkit Skill Evaluation")
|
|
218
|
+
print("================================")
|
|
219
|
+
print()
|
|
220
|
+
|
|
221
|
+
if not skills_dir.is_dir():
|
|
222
|
+
print("No skills directory found.")
|
|
223
|
+
sys.exit(1)
|
|
224
|
+
|
|
225
|
+
total, pass_count, fail_count, task_count, knowledge_count = _evaluate_all_skills()
|
|
226
|
+
hybrid_count = total - task_count - knowledge_count
|
|
227
|
+
|
|
228
|
+
print()
|
|
229
|
+
print("================================")
|
|
230
|
+
print(f"Total: {total} skills")
|
|
231
|
+
print(f"Pass: {pass_count}")
|
|
232
|
+
print(f"Fail: {fail_count}")
|
|
233
|
+
print()
|
|
234
|
+
|
|
235
|
+
print("Classification:")
|
|
236
|
+
print(f" Task: {task_count}")
|
|
237
|
+
print(f" Hybrid: {hybrid_count}")
|
|
238
|
+
print(f" Knowledge: {knowledge_count}")
|
|
239
|
+
print()
|
|
240
|
+
|
|
241
|
+
ref_count, tmpl_count, inject_count, over500, depends_count, orphan_deps = _collect_quality_metrics()
|
|
242
|
+
|
|
243
|
+
print("Quality Metrics:")
|
|
244
|
+
print(f" Reference files: {ref_count}")
|
|
245
|
+
print(f" Template files: {tmpl_count}")
|
|
246
|
+
print(f" Dynamic injection: {inject_count} skills")
|
|
247
|
+
print(f" Over 500 lines: {over500}")
|
|
248
|
+
print(f" Skills with depends-on: {depends_count}")
|
|
249
|
+
print(f" Orphan dependencies: {orphan_deps}")
|
|
250
|
+
print()
|
|
251
|
+
|
|
252
|
+
if fail_count > 0:
|
|
253
|
+
print("EVALUATION: ISSUES FOUND")
|
|
254
|
+
sys.exit(1)
|
|
255
|
+
else:
|
|
256
|
+
print("EVALUATION: ALL SKILLS PASS")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
if __name__ == "__main__":
|
|
260
|
+
main()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""YAML frontmatter parsing for agent and skill markdown files.
|
|
2
|
+
|
|
3
|
+
Stdlib-only. Extracts fields from ``---`` delimited frontmatter blocks.
|
|
4
|
+
|
|
5
|
+
Usage::
|
|
6
|
+
|
|
7
|
+
from frontmatter import frontmatter_field, frontmatter_block
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def frontmatter_field(filepath: str | Path, field: str) -> str:
|
|
15
|
+
"""Extract a YAML frontmatter field value from a file.
|
|
16
|
+
|
|
17
|
+
Reads lines between the first pair of ``---`` delimiters and returns
|
|
18
|
+
the value for the given field. Strips surrounding quotes.
|
|
19
|
+
"""
|
|
20
|
+
filepath = Path(filepath)
|
|
21
|
+
if not filepath.is_file():
|
|
22
|
+
return ""
|
|
23
|
+
in_frontmatter = False
|
|
24
|
+
with open(filepath, encoding="utf-8") as f:
|
|
25
|
+
for line in f:
|
|
26
|
+
stripped = line.rstrip("\n")
|
|
27
|
+
if stripped == "---":
|
|
28
|
+
if in_frontmatter:
|
|
29
|
+
break
|
|
30
|
+
in_frontmatter = True
|
|
31
|
+
continue
|
|
32
|
+
if in_frontmatter and stripped.startswith(f"{field}:"):
|
|
33
|
+
value = stripped[len(field) + 1:].strip()
|
|
34
|
+
# Strip surrounding quotes
|
|
35
|
+
if len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"):
|
|
36
|
+
value = value[1:-1]
|
|
37
|
+
return value
|
|
38
|
+
return ""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def frontmatter_block(filepath: str | Path) -> str:
|
|
42
|
+
"""Return the raw frontmatter text (excluding --- delimiters)."""
|
|
43
|
+
filepath = Path(filepath)
|
|
44
|
+
if not filepath.is_file():
|
|
45
|
+
return ""
|
|
46
|
+
lines: list[str] = []
|
|
47
|
+
in_fm = False
|
|
48
|
+
with open(filepath, encoding="utf-8") as f:
|
|
49
|
+
for line in f:
|
|
50
|
+
stripped = line.rstrip("\n")
|
|
51
|
+
if stripped == "---":
|
|
52
|
+
if in_fm:
|
|
53
|
+
break
|
|
54
|
+
in_fm = True
|
|
55
|
+
continue
|
|
56
|
+
if in_fm:
|
|
57
|
+
lines.append(stripped)
|
|
58
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate AGENTS.md from app/agents/*.md frontmatter.
|
|
3
|
+
|
|
4
|
+
Output is compatible with Codex, OpenCode, and Gemini CLI AGENTS.md format.
|
|
5
|
+
Usage: ./scripts/generate_agents_md.py > AGENTS.md
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
13
|
+
from _common import agents_dir, frontmatter_field
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main() -> None:
|
|
17
|
+
print("# AGENTS.md")
|
|
18
|
+
print()
|
|
19
|
+
print(
|
|
20
|
+
"This file describes the specialized AI agents bundled with ai-toolkit."
|
|
21
|
+
)
|
|
22
|
+
print(
|
|
23
|
+
"It is auto-generated from `app/agents/*.md` frontmatter"
|
|
24
|
+
" — do not edit manually."
|
|
25
|
+
)
|
|
26
|
+
print()
|
|
27
|
+
print("To regenerate: `python3 scripts/generate_agents_md.py > AGENTS.md`")
|
|
28
|
+
print()
|
|
29
|
+
print("Compatible with: Claude Code, Codex, OpenCode, Gemini CLI.")
|
|
30
|
+
print()
|
|
31
|
+
print("---")
|
|
32
|
+
print()
|
|
33
|
+
print("## Usage")
|
|
34
|
+
print()
|
|
35
|
+
print("### Claude Code")
|
|
36
|
+
print(
|
|
37
|
+
"Agents are loaded automatically from `.claude/agents/`"
|
|
38
|
+
" after running `install.sh`."
|
|
39
|
+
)
|
|
40
|
+
print("Invoke via the Agent tool:")
|
|
41
|
+
print("```")
|
|
42
|
+
print(
|
|
43
|
+
'Use subagent_type: "backend-specialist" to implement the API endpoint.'
|
|
44
|
+
)
|
|
45
|
+
print("```")
|
|
46
|
+
print()
|
|
47
|
+
print("### Codex / OpenCode")
|
|
48
|
+
print("Reference agents by name in your prompts:")
|
|
49
|
+
print("```")
|
|
50
|
+
print("@backend-specialist implement the payment API")
|
|
51
|
+
print("```")
|
|
52
|
+
print()
|
|
53
|
+
print("### Gemini CLI")
|
|
54
|
+
print("Use agent descriptions as system context:")
|
|
55
|
+
print("```")
|
|
56
|
+
print(
|
|
57
|
+
'gemini --system "$(cat .claude/agents/backend-specialist.md)"'
|
|
58
|
+
' "implement the API"'
|
|
59
|
+
)
|
|
60
|
+
print("```")
|
|
61
|
+
print()
|
|
62
|
+
print("---")
|
|
63
|
+
print()
|
|
64
|
+
print("## Agents")
|
|
65
|
+
print()
|
|
66
|
+
|
|
67
|
+
for agent_file in sorted(agents_dir.glob("*.md")):
|
|
68
|
+
if not agent_file.is_file():
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
name = frontmatter_field(agent_file, "name")
|
|
72
|
+
description = frontmatter_field(agent_file, "description")
|
|
73
|
+
tools = frontmatter_field(agent_file, "tools")
|
|
74
|
+
|
|
75
|
+
if not name:
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
print(f"### `{name}`")
|
|
79
|
+
print()
|
|
80
|
+
if description:
|
|
81
|
+
print(description)
|
|
82
|
+
print()
|
|
83
|
+
if tools:
|
|
84
|
+
print(f"**Tools:** `{tools}`")
|
|
85
|
+
print()
|
|
86
|
+
print("---")
|
|
87
|
+
print()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
main()
|