@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,504 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""ai-toolkit doctor -- Installation health and configuration diagnostics.
|
|
3
|
+
|
|
4
|
+
Checks:
|
|
5
|
+
1. Environment prerequisites (node, bash, python3, bats) + check_deps
|
|
6
|
+
2. Global install integrity (symlinks, settings.json hooks)
|
|
7
|
+
3. Hook scripts (existence, executable)
|
|
8
|
+
4. Hook configuration (valid event names)
|
|
9
|
+
5. Generated artifacts (AGENTS.md, llms.txt staleness)
|
|
10
|
+
6. Planned assets
|
|
11
|
+
7. Benchmark freshness
|
|
12
|
+
8. Stale rules
|
|
13
|
+
|
|
14
|
+
Exit codes:
|
|
15
|
+
0 all checks pass
|
|
16
|
+
1 one or more checks failed
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import shutil
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
29
|
+
from _common import toolkit_dir
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Constants
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
CLAUDE_DIR = Path.home() / ".claude"
|
|
37
|
+
HOOKS_DIR = Path.home() / ".ai-toolkit" / "hooks"
|
|
38
|
+
RULES_DIR = Path.home() / ".ai-toolkit" / "rules"
|
|
39
|
+
BENCHMARK_DASHBOARD = toolkit_dir / "benchmarks" / "ecosystem-dashboard.json"
|
|
40
|
+
|
|
41
|
+
VALID_EVENTS = frozenset({
|
|
42
|
+
"SessionStart", "Notification", "PreToolUse", "PostToolUse", "Stop",
|
|
43
|
+
"PreCompact", "SubagentStop", "UserPromptSubmit", "TaskCompleted",
|
|
44
|
+
"TeammateIdle", "SubagentStart", "SessionEnd", "PermissionRequest", "Setup",
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
EXPECTED_HOOKS = [
|
|
48
|
+
"guard-destructive.sh",
|
|
49
|
+
"guard-path.sh",
|
|
50
|
+
"post-tool-use.sh",
|
|
51
|
+
"quality-check.sh",
|
|
52
|
+
"quality-gate.sh",
|
|
53
|
+
"save-session.sh",
|
|
54
|
+
"session-start.sh",
|
|
55
|
+
"pre-compact.sh",
|
|
56
|
+
"user-prompt-submit.sh",
|
|
57
|
+
"track-usage.sh",
|
|
58
|
+
"subagent-start.sh",
|
|
59
|
+
"subagent-stop.sh",
|
|
60
|
+
"session-end.sh",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
PLANNED_ASSETS = [
|
|
64
|
+
toolkit_dir / "app" / ".claude-plugin" / "plugin.json",
|
|
65
|
+
toolkit_dir / "scripts" / "benchmark_ecosystem.py",
|
|
66
|
+
toolkit_dir / "scripts" / "harvest_ecosystem.py",
|
|
67
|
+
toolkit_dir / "kb" / "reference" / "claude-ecosystem-benchmark-snapshot.md",
|
|
68
|
+
toolkit_dir / "kb" / "reference" / "plugin-pack-conventions.md",
|
|
69
|
+
toolkit_dir / "app" / "skills" / "plugin-creator" / "SKILL.md",
|
|
70
|
+
toolkit_dir / "app" / "skills" / "hook-creator" / "SKILL.md",
|
|
71
|
+
toolkit_dir / "app" / "skills" / "command-creator" / "SKILL.md",
|
|
72
|
+
toolkit_dir / "app" / "skills" / "agent-creator" / "SKILL.md",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
# Status helpers
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
class DiagResult:
|
|
81
|
+
"""Accumulates pass/fail/warn/fix/skip counts."""
|
|
82
|
+
|
|
83
|
+
def __init__(self) -> None:
|
|
84
|
+
self.errors = 0
|
|
85
|
+
self.warnings = 0
|
|
86
|
+
|
|
87
|
+
def ok(self, msg: str) -> None:
|
|
88
|
+
print(f" OK: {msg}")
|
|
89
|
+
|
|
90
|
+
def fail(self, msg: str) -> None:
|
|
91
|
+
print(f" FAIL: {msg}")
|
|
92
|
+
self.errors += 1
|
|
93
|
+
|
|
94
|
+
def warn(self, msg: str) -> None:
|
|
95
|
+
print(f" WARN: {msg}")
|
|
96
|
+
self.warnings += 1
|
|
97
|
+
|
|
98
|
+
def skip(self, msg: str) -> None:
|
|
99
|
+
print(f" SKIP: {msg}")
|
|
100
|
+
|
|
101
|
+
def fixed(self, msg: str) -> None:
|
|
102
|
+
print(f" FIXED: {msg}")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# Version extraction
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def _get_version(binary: str) -> str:
|
|
110
|
+
"""Get a version string from a binary."""
|
|
111
|
+
try:
|
|
112
|
+
result = subprocess.run(
|
|
113
|
+
[binary, "--version"],
|
|
114
|
+
capture_output=True,
|
|
115
|
+
text=True,
|
|
116
|
+
timeout=5,
|
|
117
|
+
)
|
|
118
|
+
output = result.stdout.strip() or result.stderr.strip()
|
|
119
|
+
m = re.search(r"(\d+\.\d+\.\d+)", output)
|
|
120
|
+
return m.group(1) if m else "unknown"
|
|
121
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
122
|
+
return ""
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
# Check 1: Environment
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
def check_environment(dr: DiagResult, fix_mode: bool) -> None:
|
|
130
|
+
"""Check that required and optional binaries are present."""
|
|
131
|
+
print("## Environment")
|
|
132
|
+
|
|
133
|
+
# Basic binary checks (matching original bash behavior)
|
|
134
|
+
for binary, label, required in [
|
|
135
|
+
("node", "node", True),
|
|
136
|
+
("bash", "bash", True),
|
|
137
|
+
("python3", "python3", False),
|
|
138
|
+
("bats", "bats", False),
|
|
139
|
+
]:
|
|
140
|
+
if shutil.which(binary):
|
|
141
|
+
version = _get_version(binary)
|
|
142
|
+
ver_str = f" {version}" if version else ""
|
|
143
|
+
dr.ok(f"{label}{ver_str}")
|
|
144
|
+
else:
|
|
145
|
+
if required:
|
|
146
|
+
dr.fail(f"{label} not found")
|
|
147
|
+
else:
|
|
148
|
+
dr.warn(f"{label} not found" + (" (needed for hook merge)" if binary == "python3" else " (needed for tests)"))
|
|
149
|
+
|
|
150
|
+
# Enhanced: also run check_deps logic
|
|
151
|
+
try:
|
|
152
|
+
from check_deps import check_deps as run_check_deps
|
|
153
|
+
|
|
154
|
+
results = run_check_deps(verbose=False)
|
|
155
|
+
if not results["all_ok"]:
|
|
156
|
+
for dep in results["required"]:
|
|
157
|
+
if not dep["found"] or not dep["version_ok"]:
|
|
158
|
+
hint = f" (fix: {dep['install_hint']})" if dep["install_hint"] else ""
|
|
159
|
+
dr.warn(f"check_deps: {dep['name']} -- {dep['reason']}{hint}")
|
|
160
|
+
except ImportError:
|
|
161
|
+
dr.warn("check_deps.py not available for enhanced dependency check")
|
|
162
|
+
|
|
163
|
+
print()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
# Check 2: Global Install
|
|
168
|
+
# ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
def _check_symlinks(dr: DiagResult, fix_mode: bool, directory: Path,
|
|
171
|
+
source_subdir: str, label: str, glob_pattern: str,
|
|
172
|
+
is_file_check: bool) -> None:
|
|
173
|
+
"""Check symlinks in a directory, optionally fixing broken ones."""
|
|
174
|
+
if not directory.is_dir():
|
|
175
|
+
dr.fail(f"{label} directory missing")
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
link_count = 0
|
|
179
|
+
broken_count = 0
|
|
180
|
+
for item in sorted(directory.glob(glob_pattern) if glob_pattern != "*" else directory.iterdir()):
|
|
181
|
+
if not item.is_symlink():
|
|
182
|
+
continue
|
|
183
|
+
link_count += 1
|
|
184
|
+
if not item.exists():
|
|
185
|
+
if fix_mode:
|
|
186
|
+
item.unlink()
|
|
187
|
+
source = toolkit_dir / "app" / source_subdir / item.name
|
|
188
|
+
check_fn = source.is_file if is_file_check else source.is_dir
|
|
189
|
+
if check_fn():
|
|
190
|
+
item.symlink_to(source)
|
|
191
|
+
dr.fixed(f"re-linked {label[:-1]}: {item.name}")
|
|
192
|
+
else:
|
|
193
|
+
dr.fixed(f"removed broken {label[:-1]} symlink: {item.name}")
|
|
194
|
+
else:
|
|
195
|
+
broken_count += 1
|
|
196
|
+
dr.warn(f"broken symlink: {item}")
|
|
197
|
+
|
|
198
|
+
if link_count > 0 and broken_count == 0:
|
|
199
|
+
dr.ok(f"{label}: {link_count} symlinks (0 broken)")
|
|
200
|
+
elif link_count == 0:
|
|
201
|
+
dr.fail(f"{label}: no symlinks found")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _check_settings_json(dr: DiagResult) -> None:
|
|
205
|
+
"""Check settings.json hooks and legacy hooks.json."""
|
|
206
|
+
settings_json = CLAUDE_DIR / "settings.json"
|
|
207
|
+
if settings_json.is_file():
|
|
208
|
+
content = settings_json.read_text(encoding="utf-8")
|
|
209
|
+
if '"_source"' in content and '"ai-toolkit"' in content:
|
|
210
|
+
hook_count = content.count('"ai-toolkit"')
|
|
211
|
+
dr.ok(f"settings.json: {hook_count} toolkit hook entries")
|
|
212
|
+
else:
|
|
213
|
+
dr.warn("settings.json exists but has no toolkit hooks")
|
|
214
|
+
else:
|
|
215
|
+
dr.fail("settings.json not found")
|
|
216
|
+
|
|
217
|
+
legacy_hooks = CLAUDE_DIR / "hooks.json"
|
|
218
|
+
if legacy_hooks.is_file():
|
|
219
|
+
dr.warn("legacy hooks.json still exists (run: ai-toolkit update)")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def check_global_install(dr: DiagResult, fix_mode: bool) -> None:
|
|
223
|
+
"""Check symlinks and settings.json in ~/.claude/."""
|
|
224
|
+
print("## Global Install")
|
|
225
|
+
|
|
226
|
+
if CLAUDE_DIR.is_dir():
|
|
227
|
+
dr.ok(f"{CLAUDE_DIR} exists")
|
|
228
|
+
else:
|
|
229
|
+
dr.fail(f"{CLAUDE_DIR} not found (run: ai-toolkit install)")
|
|
230
|
+
print()
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
_check_symlinks(dr, fix_mode, CLAUDE_DIR / "agents", "agents", "agents", "*.md", True)
|
|
234
|
+
_check_symlinks(dr, fix_mode, CLAUDE_DIR / "skills", "skills", "skills", "*", False)
|
|
235
|
+
_check_settings_json(dr)
|
|
236
|
+
|
|
237
|
+
print()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
# ---------------------------------------------------------------------------
|
|
241
|
+
# Check 3: Hook Scripts
|
|
242
|
+
# ---------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
def check_hook_scripts(dr: DiagResult, fix_mode: bool) -> None:
|
|
245
|
+
"""Check that expected hook scripts exist and are executable."""
|
|
246
|
+
print("## Hook Scripts")
|
|
247
|
+
|
|
248
|
+
if not HOOKS_DIR.is_dir():
|
|
249
|
+
dr.fail(f"hook scripts directory missing: {HOOKS_DIR}")
|
|
250
|
+
print()
|
|
251
|
+
return
|
|
252
|
+
|
|
253
|
+
for hook in EXPECTED_HOOKS:
|
|
254
|
+
hook_path = HOOKS_DIR / hook
|
|
255
|
+
source_path = toolkit_dir / "app" / "hooks" / hook
|
|
256
|
+
|
|
257
|
+
if hook_path.is_file() and os.access(hook_path, os.X_OK):
|
|
258
|
+
dr.ok(hook)
|
|
259
|
+
elif hook_path.is_file():
|
|
260
|
+
if fix_mode:
|
|
261
|
+
hook_path.chmod(hook_path.stat().st_mode | 0o111)
|
|
262
|
+
dr.fixed(f"{hook} made executable")
|
|
263
|
+
else:
|
|
264
|
+
dr.warn(f"{hook} exists but is not executable")
|
|
265
|
+
else:
|
|
266
|
+
if fix_mode and source_path.is_file():
|
|
267
|
+
HOOKS_DIR.mkdir(parents=True, exist_ok=True)
|
|
268
|
+
import shutil as _shutil
|
|
269
|
+
_shutil.copy2(source_path, hook_path)
|
|
270
|
+
hook_path.chmod(hook_path.stat().st_mode | 0o111)
|
|
271
|
+
dr.fixed(f"restored {hook}")
|
|
272
|
+
else:
|
|
273
|
+
dr.fail(f"{hook} missing from {HOOKS_DIR}")
|
|
274
|
+
|
|
275
|
+
print()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# ---------------------------------------------------------------------------
|
|
279
|
+
# Check 4: Hook Configuration
|
|
280
|
+
# ---------------------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
def check_hook_configuration(dr: DiagResult) -> None:
|
|
283
|
+
"""Validate event names in app/hooks.json."""
|
|
284
|
+
print("## Hook Configuration")
|
|
285
|
+
|
|
286
|
+
hooks_file = toolkit_dir / "app" / "hooks.json"
|
|
287
|
+
if not hooks_file.is_file():
|
|
288
|
+
dr.fail("app/hooks.json not found")
|
|
289
|
+
print()
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
with open(hooks_file, encoding="utf-8") as f:
|
|
294
|
+
data = json.load(f)
|
|
295
|
+
except (json.JSONDecodeError, OSError):
|
|
296
|
+
dr.warn("could not parse hook events from app/hooks.json")
|
|
297
|
+
print()
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
hooks = data.get("hooks", {})
|
|
301
|
+
if not hooks:
|
|
302
|
+
dr.warn("could not parse hook events from app/hooks.json")
|
|
303
|
+
else:
|
|
304
|
+
for event in hooks:
|
|
305
|
+
if event in VALID_EVENTS:
|
|
306
|
+
dr.ok(f"event: {event}")
|
|
307
|
+
else:
|
|
308
|
+
dr.fail(f"unknown hook event: {event}")
|
|
309
|
+
|
|
310
|
+
print()
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# ---------------------------------------------------------------------------
|
|
314
|
+
# Check 5: Generated Artifacts
|
|
315
|
+
# ---------------------------------------------------------------------------
|
|
316
|
+
|
|
317
|
+
def check_generated_artifacts(dr: DiagResult, fix_mode: bool) -> None:
|
|
318
|
+
"""Check existence and staleness of generated artifacts."""
|
|
319
|
+
print("## Generated Artifacts")
|
|
320
|
+
|
|
321
|
+
artifacts = {
|
|
322
|
+
"AGENTS.md": ("generate_agents_md.py", []),
|
|
323
|
+
"llms.txt": ("generate_llms_txt.py", []),
|
|
324
|
+
"llms-full.txt": ("generate_llms_txt.py", ["--full"]),
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
for artifact, (gen_script, gen_args) in artifacts.items():
|
|
328
|
+
path = toolkit_dir / artifact
|
|
329
|
+
if path.is_file():
|
|
330
|
+
dr.ok(f"{artifact} exists")
|
|
331
|
+
else:
|
|
332
|
+
if fix_mode:
|
|
333
|
+
result = subprocess.run(
|
|
334
|
+
["python3", str(toolkit_dir / "scripts" / gen_script)] + gen_args,
|
|
335
|
+
capture_output=True,
|
|
336
|
+
text=True,
|
|
337
|
+
)
|
|
338
|
+
if result.returncode == 0:
|
|
339
|
+
path.write_text(result.stdout, encoding="utf-8")
|
|
340
|
+
dr.fixed(f"regenerated {artifact}")
|
|
341
|
+
else:
|
|
342
|
+
dr.fail(f"could not regenerate {artifact}")
|
|
343
|
+
else:
|
|
344
|
+
dr.warn(f"{artifact} missing (run: ai-toolkit generate-all)")
|
|
345
|
+
|
|
346
|
+
# Check if AGENTS.md is stale (agents added/removed since generation)
|
|
347
|
+
agents_md = toolkit_dir / "AGENTS.md"
|
|
348
|
+
if agents_md.is_file():
|
|
349
|
+
agents_dir = toolkit_dir / "app" / "agents"
|
|
350
|
+
actual_agents = sum(1 for f in agents_dir.glob("*.md") if f.is_file()) if agents_dir.is_dir() else 0
|
|
351
|
+
content = agents_md.read_text(encoding="utf-8")
|
|
352
|
+
mentioned_agents = len(re.findall(r"^### `", content, re.MULTILINE))
|
|
353
|
+
if actual_agents != mentioned_agents:
|
|
354
|
+
if fix_mode:
|
|
355
|
+
result = subprocess.run(
|
|
356
|
+
["python3", str(toolkit_dir / "scripts" / "generate_agents_md.py")],
|
|
357
|
+
capture_output=True,
|
|
358
|
+
text=True,
|
|
359
|
+
)
|
|
360
|
+
if result.returncode == 0:
|
|
361
|
+
agents_md.write_text(result.stdout, encoding="utf-8")
|
|
362
|
+
dr.fixed(f"regenerated stale AGENTS.md ({mentioned_agents} -> {actual_agents} entries)")
|
|
363
|
+
else:
|
|
364
|
+
dr.warn(f"AGENTS.md may be stale: {mentioned_agents} entries vs {actual_agents} agent files")
|
|
365
|
+
|
|
366
|
+
print()
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
# ---------------------------------------------------------------------------
|
|
370
|
+
# Check 6: Planned Assets
|
|
371
|
+
# ---------------------------------------------------------------------------
|
|
372
|
+
|
|
373
|
+
def check_planned_assets(dr: DiagResult) -> None:
|
|
374
|
+
"""Check that planned assets exist and are non-empty."""
|
|
375
|
+
print("## Planned Assets")
|
|
376
|
+
|
|
377
|
+
for asset in PLANNED_ASSETS:
|
|
378
|
+
if asset.is_file() and asset.stat().st_size > 0:
|
|
379
|
+
dr.ok(f"{asset.name} present")
|
|
380
|
+
else:
|
|
381
|
+
rel = str(asset.relative_to(toolkit_dir))
|
|
382
|
+
dr.fail(f"missing or empty asset: {rel}")
|
|
383
|
+
|
|
384
|
+
# Plugin pack count
|
|
385
|
+
plugins_dir = toolkit_dir / "app" / "plugins"
|
|
386
|
+
if plugins_dir.is_dir():
|
|
387
|
+
pack_count = sum(1 for d in plugins_dir.iterdir() if d.is_dir())
|
|
388
|
+
if pack_count > 0:
|
|
389
|
+
dr.ok(f"plugin packs: {pack_count} experimental pack(s) present")
|
|
390
|
+
else:
|
|
391
|
+
dr.warn("no plugin packs found in app/plugins")
|
|
392
|
+
else:
|
|
393
|
+
dr.warn("no plugin packs found in app/plugins")
|
|
394
|
+
|
|
395
|
+
print()
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
# ---------------------------------------------------------------------------
|
|
399
|
+
# Check 7: Benchmark Freshness
|
|
400
|
+
# ---------------------------------------------------------------------------
|
|
401
|
+
|
|
402
|
+
def check_benchmark_freshness(dr: DiagResult) -> None:
|
|
403
|
+
"""Check benchmark dashboard freshness."""
|
|
404
|
+
print("## Benchmark Freshness")
|
|
405
|
+
|
|
406
|
+
if BENCHMARK_DASHBOARD.is_file():
|
|
407
|
+
try:
|
|
408
|
+
with open(BENCHMARK_DASHBOARD, encoding="utf-8") as f:
|
|
409
|
+
data = json.load(f)
|
|
410
|
+
freshness = data.get("freshness", {})
|
|
411
|
+
status = freshness.get("status", "unknown")
|
|
412
|
+
age = freshness.get("age_days", "?")
|
|
413
|
+
threshold = freshness.get("stale_threshold_days", "?")
|
|
414
|
+
|
|
415
|
+
if status == "fresh":
|
|
416
|
+
dr.ok(f"benchmark dashboard is fresh ({age} day(s) old, threshold {threshold})")
|
|
417
|
+
elif status == "aging":
|
|
418
|
+
dr.warn(f"benchmark dashboard is aging ({age} day(s) old, threshold {threshold})")
|
|
419
|
+
elif status == "stale":
|
|
420
|
+
dr.warn(f"benchmark dashboard is stale ({age} day(s) old, threshold {threshold}) -- run: python3 scripts/harvest_ecosystem.py")
|
|
421
|
+
else:
|
|
422
|
+
dr.warn("benchmark dashboard freshness unknown")
|
|
423
|
+
except (json.JSONDecodeError, OSError):
|
|
424
|
+
dr.warn("could not parse benchmark dashboard freshness")
|
|
425
|
+
else:
|
|
426
|
+
dr.fail("benchmark dashboard missing: benchmarks/ecosystem-dashboard.json")
|
|
427
|
+
|
|
428
|
+
harvest = toolkit_dir / "benchmarks" / "ecosystem-harvest.json"
|
|
429
|
+
if harvest.is_file():
|
|
430
|
+
dr.ok("ecosystem-harvest.json present")
|
|
431
|
+
else:
|
|
432
|
+
dr.warn("ecosystem-harvest.json missing (run: python3 scripts/harvest_ecosystem.py)")
|
|
433
|
+
|
|
434
|
+
print()
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# ---------------------------------------------------------------------------
|
|
438
|
+
# Check 8: Stale Rules
|
|
439
|
+
# ---------------------------------------------------------------------------
|
|
440
|
+
|
|
441
|
+
def check_stale_rules(dr: DiagResult, fix_mode: bool) -> None:
|
|
442
|
+
"""Check for stale symlinks and empty rule files."""
|
|
443
|
+
print()
|
|
444
|
+
print("## 8. Stale Rules")
|
|
445
|
+
|
|
446
|
+
if not RULES_DIR.is_dir():
|
|
447
|
+
dr.skip("No rules directory")
|
|
448
|
+
return
|
|
449
|
+
|
|
450
|
+
stale = 0
|
|
451
|
+
for rule_file in sorted(RULES_DIR.iterdir()):
|
|
452
|
+
# Check for stale symlinks
|
|
453
|
+
if rule_file.is_symlink() and not rule_file.exists():
|
|
454
|
+
print(f" WARNING: Stale symlink: {rule_file}")
|
|
455
|
+
stale += 1
|
|
456
|
+
if fix_mode:
|
|
457
|
+
rule_file.unlink()
|
|
458
|
+
print(" FIXED: removed stale symlink")
|
|
459
|
+
elif rule_file.is_file() and rule_file.stat().st_size == 0:
|
|
460
|
+
print(f" WARNING: Empty rule file: {rule_file}")
|
|
461
|
+
stale += 1
|
|
462
|
+
|
|
463
|
+
if stale == 0:
|
|
464
|
+
dr.ok("All rules healthy")
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
# ---------------------------------------------------------------------------
|
|
468
|
+
# Main
|
|
469
|
+
# ---------------------------------------------------------------------------
|
|
470
|
+
|
|
471
|
+
def main() -> None:
|
|
472
|
+
fix_mode = "--fix" in sys.argv[1:]
|
|
473
|
+
|
|
474
|
+
print("ai-toolkit doctor")
|
|
475
|
+
print("========================")
|
|
476
|
+
print()
|
|
477
|
+
|
|
478
|
+
dr = DiagResult()
|
|
479
|
+
|
|
480
|
+
check_environment(dr, fix_mode)
|
|
481
|
+
check_global_install(dr, fix_mode)
|
|
482
|
+
check_hook_scripts(dr, fix_mode)
|
|
483
|
+
check_hook_configuration(dr)
|
|
484
|
+
check_generated_artifacts(dr, fix_mode)
|
|
485
|
+
check_planned_assets(dr)
|
|
486
|
+
check_benchmark_freshness(dr)
|
|
487
|
+
check_stale_rules(dr, fix_mode)
|
|
488
|
+
|
|
489
|
+
# Summary
|
|
490
|
+
print("========================")
|
|
491
|
+
if fix_mode:
|
|
492
|
+
print("Mode: --fix (auto-repair enabled)")
|
|
493
|
+
print(f"Errors: {dr.errors} | Warnings: {dr.warnings}")
|
|
494
|
+
|
|
495
|
+
if dr.errors > 0:
|
|
496
|
+
print("HEALTH CHECK FAILED")
|
|
497
|
+
sys.exit(1)
|
|
498
|
+
else:
|
|
499
|
+
print("HEALTH CHECK PASSED")
|
|
500
|
+
sys.exit(0)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
if __name__ == "__main__":
|
|
504
|
+
main()
|
package/scripts/eject.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""ai-toolkit eject -- Export standalone toolkit config.
|
|
3
|
+
|
|
4
|
+
Creates a self-contained copy of the toolkit configuration in the
|
|
5
|
+
target directory with no symlinks and no dependency on ai-toolkit.
|
|
6
|
+
|
|
7
|
+
What it exports:
|
|
8
|
+
- .claude/agents/*.md (real files, not symlinks)
|
|
9
|
+
- .claude/skills/*/ (real directories, not symlinks)
|
|
10
|
+
- .claude/CLAUDE.md (inlined rules)
|
|
11
|
+
- .claude/constitution.md (full content)
|
|
12
|
+
- .claude/ARCHITECTURE.md (full content)
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
ai-toolkit eject [target-dir]
|
|
16
|
+
target-dir defaults to current working directory
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import shutil
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
25
|
+
from _common import app_dir, inject_rule
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main() -> None:
|
|
29
|
+
"""Eject toolkit into a standalone directory."""
|
|
30
|
+
target_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.cwd()
|
|
31
|
+
|
|
32
|
+
print("ai-toolkit eject")
|
|
33
|
+
print("========================")
|
|
34
|
+
print(f"Source: {app_dir.parent}")
|
|
35
|
+
print(f"Target: {target_dir.resolve()}")
|
|
36
|
+
print()
|
|
37
|
+
|
|
38
|
+
claude_dir = target_dir / ".claude"
|
|
39
|
+
claude_dir.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
|
|
41
|
+
# -- Agents: copy as real files ------------------------------------------
|
|
42
|
+
print("## Agents")
|
|
43
|
+
agents_out = claude_dir / "agents"
|
|
44
|
+
agents_out.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
agent_count = 0
|
|
46
|
+
agents_src = app_dir / "agents"
|
|
47
|
+
if agents_src.is_dir():
|
|
48
|
+
for agent in sorted(agents_src.glob("*.md")):
|
|
49
|
+
dest = agents_out / agent.name
|
|
50
|
+
if dest.is_symlink():
|
|
51
|
+
dest.unlink()
|
|
52
|
+
shutil.copy2(agent, dest)
|
|
53
|
+
agent_count += 1
|
|
54
|
+
print(f" Copied: {agent_count} agents")
|
|
55
|
+
|
|
56
|
+
# -- Skills: copy as real directories ------------------------------------
|
|
57
|
+
print("## Skills")
|
|
58
|
+
skills_out = claude_dir / "skills"
|
|
59
|
+
skills_out.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
skill_count = 0
|
|
61
|
+
skills_src = app_dir / "skills"
|
|
62
|
+
if skills_src.is_dir():
|
|
63
|
+
for skill in sorted(skills_src.iterdir()):
|
|
64
|
+
if not skill.is_dir():
|
|
65
|
+
continue
|
|
66
|
+
dest = skills_out / skill.name
|
|
67
|
+
if dest.is_symlink():
|
|
68
|
+
dest.unlink()
|
|
69
|
+
if dest.exists():
|
|
70
|
+
shutil.rmtree(dest)
|
|
71
|
+
shutil.copytree(skill, dest)
|
|
72
|
+
skill_count += 1
|
|
73
|
+
print(f" Copied: {skill_count} skills")
|
|
74
|
+
|
|
75
|
+
# -- CLAUDE.md: inline all rules -----------------------------------------
|
|
76
|
+
print("## Rules")
|
|
77
|
+
claude_md = claude_dir / "CLAUDE.md"
|
|
78
|
+
if not claude_md.exists():
|
|
79
|
+
claude_md.touch()
|
|
80
|
+
|
|
81
|
+
rule_count = 0
|
|
82
|
+
rules_src = app_dir / "rules"
|
|
83
|
+
if rules_src.is_dir():
|
|
84
|
+
for rule in sorted(rules_src.glob("*.md")):
|
|
85
|
+
inject_rule(rule, target_dir)
|
|
86
|
+
rule_count += 1
|
|
87
|
+
print(f" Inlined: {rule_count} rules into CLAUDE.md")
|
|
88
|
+
|
|
89
|
+
# -- Constitution and Architecture ---------------------------------------
|
|
90
|
+
print("## Config Files")
|
|
91
|
+
for filename in ("constitution.md", "ARCHITECTURE.md"):
|
|
92
|
+
src = app_dir / filename
|
|
93
|
+
if src.is_file():
|
|
94
|
+
dest = claude_dir / filename
|
|
95
|
+
if dest.is_symlink():
|
|
96
|
+
dest.unlink()
|
|
97
|
+
shutil.copy2(src, dest)
|
|
98
|
+
print(f" Copied: {filename}")
|
|
99
|
+
|
|
100
|
+
# -- Summary -------------------------------------------------------------
|
|
101
|
+
print()
|
|
102
|
+
print("========================")
|
|
103
|
+
print(f"Ejected: {agent_count} agents, {skill_count} skills, {rule_count} rules")
|
|
104
|
+
print()
|
|
105
|
+
print(f"The toolkit is now standalone in {target_dir.resolve()}/.claude/")
|
|
106
|
+
print("You can safely run: npm uninstall -g @softspark/ai-toolkit")
|
|
107
|
+
print()
|
|
108
|
+
print("Note: hooks are NOT ejected (they require settings.json merge).")
|
|
109
|
+
print("To keep hooks working, keep ai-toolkit installed globally.")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if __name__ == "__main__":
|
|
113
|
+
main()
|