@techwavedev/agi-agent-kit 1.1.3
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/CHANGELOG.md +59 -0
- package/README.md +147 -0
- package/bin/init.js +471 -0
- package/package.json +36 -0
- package/templates/.agent/agents/backend-specialist.md +263 -0
- package/templates/.agent/agents/code-archaeologist.md +106 -0
- package/templates/.agent/agents/database-architect.md +226 -0
- package/templates/.agent/agents/debugger.md +225 -0
- package/templates/.agent/agents/devops-engineer.md +242 -0
- package/templates/.agent/agents/documentation-writer.md +104 -0
- package/templates/.agent/agents/explorer-agent.md +73 -0
- package/templates/.agent/agents/frontend-specialist.md +556 -0
- package/templates/.agent/agents/game-developer.md +162 -0
- package/templates/.agent/agents/mobile-developer.md +377 -0
- package/templates/.agent/agents/orchestrator.md +416 -0
- package/templates/.agent/agents/penetration-tester.md +188 -0
- package/templates/.agent/agents/performance-optimizer.md +187 -0
- package/templates/.agent/agents/product-manager.md +112 -0
- package/templates/.agent/agents/project-planner.md +403 -0
- package/templates/.agent/agents/qa-automation-engineer.md +109 -0
- package/templates/.agent/agents/security-auditor.md +170 -0
- package/templates/.agent/agents/seo-specialist.md +111 -0
- package/templates/.agent/agents/test-engineer.md +158 -0
- package/templates/.agent/rules/GEMINI.md +253 -0
- package/templates/.agent/workflows/brainstorm.md +113 -0
- package/templates/.agent/workflows/create.md +59 -0
- package/templates/.agent/workflows/debug.md +103 -0
- package/templates/.agent/workflows/deploy.md +176 -0
- package/templates/.agent/workflows/enhance.md +63 -0
- package/templates/.agent/workflows/orchestrate.md +237 -0
- package/templates/.agent/workflows/plan.md +89 -0
- package/templates/.agent/workflows/preview.md +81 -0
- package/templates/.agent/workflows/status.md +86 -0
- package/templates/.agent/workflows/test.md +144 -0
- package/templates/.agent/workflows/ui-ux-pro-max.md +296 -0
- package/templates/base/.env.example +54 -0
- package/templates/base/AGENTS.md +463 -0
- package/templates/base/requirements.txt +6 -0
- package/templates/base/skill-creator/LICENSE.txt +202 -0
- package/templates/base/skill-creator/SKILL_skillcreator.md +389 -0
- package/templates/base/skill-creator/references/output-patterns.md +82 -0
- package/templates/base/skill-creator/references/workflows.md +28 -0
- package/templates/base/skill-creator/scripts/init_skill.py +304 -0
- package/templates/base/skill-creator/scripts/package_skill.py +110 -0
- package/templates/base/skill-creator/scripts/quick_validate.py +95 -0
- package/templates/base/skill-creator/scripts/update_catalog.py +371 -0
- package/templates/skills/core/README.md +21 -0
- package/templates/skills/core/documentation/SKILL.md +351 -0
- package/templates/skills/core/documentation/references/best_practices.md +201 -0
- package/templates/skills/core/documentation/scripts/analyze_code.py +307 -0
- package/templates/skills/core/documentation/scripts/detect_changes.py +460 -0
- package/templates/skills/core/documentation/scripts/generate_changelog.py +312 -0
- package/templates/skills/core/documentation/scripts/sync_docs.py +272 -0
- package/templates/skills/core/documentation/scripts/update_skill_docs.py +366 -0
- package/templates/skills/core/pdf-reader/SKILL.md +104 -0
- package/templates/skills/core/pdf-reader/references/pdf_libraries.md +83 -0
- package/templates/skills/core/pdf-reader/scripts/extract_text.py +295 -0
- package/templates/skills/core/qdrant-memory/SKILL.md +435 -0
- package/templates/skills/core/qdrant-memory/references/advanced_patterns.md +375 -0
- package/templates/skills/core/qdrant-memory/references/collection_schemas.md +229 -0
- package/templates/skills/core/qdrant-memory/references/complete_guide.md +724 -0
- package/templates/skills/core/qdrant-memory/references/embedding_models.md +325 -0
- package/templates/skills/core/qdrant-memory/scripts/benchmark_token_savings.py +640 -0
- package/templates/skills/core/qdrant-memory/scripts/embedding_utils.py +323 -0
- package/templates/skills/core/qdrant-memory/scripts/hybrid_search.py +214 -0
- package/templates/skills/core/qdrant-memory/scripts/init_collection.py +193 -0
- package/templates/skills/core/qdrant-memory/scripts/memory_retrieval.py +345 -0
- package/templates/skills/core/qdrant-memory/scripts/semantic_cache.py +282 -0
- package/templates/skills/core/qdrant-memory/scripts/test_skill.py +655 -0
- package/templates/skills/core/webcrawler/SKILL.md +292 -0
- package/templates/skills/core/webcrawler/references/advanced_crawling.md +181 -0
- package/templates/skills/core/webcrawler/scripts/crawl_docs.py +532 -0
- package/templates/skills/core/webcrawler/scripts/extract_page.py +189 -0
- package/templates/skills/core/webcrawler/scripts/filter_docs.py +200 -0
- package/templates/skills/knowledge/api-patterns/SKILL.md +81 -0
- package/templates/skills/knowledge/api-patterns/api-style.md +42 -0
- package/templates/skills/knowledge/api-patterns/auth.md +24 -0
- package/templates/skills/knowledge/api-patterns/documentation.md +26 -0
- package/templates/skills/knowledge/api-patterns/graphql.md +41 -0
- package/templates/skills/knowledge/api-patterns/rate-limiting.md +31 -0
- package/templates/skills/knowledge/api-patterns/response.md +37 -0
- package/templates/skills/knowledge/api-patterns/rest.md +40 -0
- package/templates/skills/knowledge/api-patterns/scripts/api_validator.py +211 -0
- package/templates/skills/knowledge/api-patterns/security-testing.md +122 -0
- package/templates/skills/knowledge/api-patterns/trpc.md +41 -0
- package/templates/skills/knowledge/api-patterns/versioning.md +22 -0
- package/templates/skills/knowledge/app-builder/SKILL.md +75 -0
- package/templates/skills/knowledge/app-builder/agent-coordination.md +71 -0
- package/templates/skills/knowledge/app-builder/feature-building.md +53 -0
- package/templates/skills/knowledge/app-builder/project-detection.md +34 -0
- package/templates/skills/knowledge/app-builder/scaffolding.md +118 -0
- package/templates/skills/knowledge/app-builder/tech-stack.md +40 -0
- package/templates/skills/knowledge/app-builder/templates/SKILL.md +39 -0
- package/templates/skills/knowledge/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/templates/skills/knowledge/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/templates/skills/knowledge/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/templates/skills/knowledge/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/templates/skills/knowledge/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/templates/skills/knowledge/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/templates/skills/knowledge/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/templates/skills/knowledge/app-builder/templates/nextjs-fullstack/TEMPLATE.md +82 -0
- package/templates/skills/knowledge/app-builder/templates/nextjs-saas/TEMPLATE.md +100 -0
- package/templates/skills/knowledge/app-builder/templates/nextjs-static/TEMPLATE.md +106 -0
- package/templates/skills/knowledge/app-builder/templates/nuxt-app/TEMPLATE.md +101 -0
- package/templates/skills/knowledge/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/templates/skills/knowledge/app-builder/templates/react-native-app/TEMPLATE.md +93 -0
- package/templates/skills/knowledge/architecture/SKILL.md +55 -0
- package/templates/skills/knowledge/architecture/context-discovery.md +43 -0
- package/templates/skills/knowledge/architecture/examples.md +94 -0
- package/templates/skills/knowledge/architecture/pattern-selection.md +68 -0
- package/templates/skills/knowledge/architecture/patterns-reference.md +50 -0
- package/templates/skills/knowledge/architecture/trade-off-analysis.md +77 -0
- package/templates/skills/knowledge/bash-linux/SKILL.md +199 -0
- package/templates/skills/knowledge/behavioral-modes/SKILL.md +242 -0
- package/templates/skills/knowledge/brainstorming/SKILL.md +163 -0
- package/templates/skills/knowledge/brainstorming/dynamic-questioning.md +350 -0
- package/templates/skills/knowledge/clean-code/SKILL.md +201 -0
- package/templates/skills/knowledge/code-review-checklist/SKILL.md +109 -0
- package/templates/skills/knowledge/database-design/SKILL.md +52 -0
- package/templates/skills/knowledge/database-design/database-selection.md +43 -0
- package/templates/skills/knowledge/database-design/indexing.md +39 -0
- package/templates/skills/knowledge/database-design/migrations.md +48 -0
- package/templates/skills/knowledge/database-design/optimization.md +36 -0
- package/templates/skills/knowledge/database-design/orm-selection.md +30 -0
- package/templates/skills/knowledge/database-design/schema-design.md +56 -0
- package/templates/skills/knowledge/database-design/scripts/schema_validator.py +172 -0
- package/templates/skills/knowledge/deployment-procedures/SKILL.md +241 -0
- package/templates/skills/knowledge/doc.md +177 -0
- package/templates/skills/knowledge/documentation-templates/SKILL.md +194 -0
- package/templates/skills/knowledge/frontend-design/SKILL.md +396 -0
- package/templates/skills/knowledge/frontend-design/animation-guide.md +331 -0
- package/templates/skills/knowledge/frontend-design/color-system.md +311 -0
- package/templates/skills/knowledge/frontend-design/decision-trees.md +418 -0
- package/templates/skills/knowledge/frontend-design/motion-graphics.md +306 -0
- package/templates/skills/knowledge/frontend-design/scripts/accessibility_checker.py +183 -0
- package/templates/skills/knowledge/frontend-design/scripts/ux_audit.py +722 -0
- package/templates/skills/knowledge/frontend-design/typography-system.md +345 -0
- package/templates/skills/knowledge/frontend-design/ux-psychology.md +541 -0
- package/templates/skills/knowledge/frontend-design/visual-effects.md +383 -0
- package/templates/skills/knowledge/game-development/2d-games/SKILL.md +119 -0
- package/templates/skills/knowledge/game-development/3d-games/SKILL.md +135 -0
- package/templates/skills/knowledge/game-development/SKILL.md +167 -0
- package/templates/skills/knowledge/game-development/game-art/SKILL.md +185 -0
- package/templates/skills/knowledge/game-development/game-audio/SKILL.md +190 -0
- package/templates/skills/knowledge/game-development/game-design/SKILL.md +129 -0
- package/templates/skills/knowledge/game-development/mobile-games/SKILL.md +108 -0
- package/templates/skills/knowledge/game-development/multiplayer/SKILL.md +132 -0
- package/templates/skills/knowledge/game-development/pc-games/SKILL.md +144 -0
- package/templates/skills/knowledge/game-development/vr-ar/SKILL.md +123 -0
- package/templates/skills/knowledge/game-development/web-games/SKILL.md +150 -0
- package/templates/skills/knowledge/geo-fundamentals/SKILL.md +156 -0
- package/templates/skills/knowledge/geo-fundamentals/scripts/geo_checker.py +289 -0
- package/templates/skills/knowledge/i18n-localization/SKILL.md +154 -0
- package/templates/skills/knowledge/i18n-localization/scripts/i18n_checker.py +241 -0
- package/templates/skills/knowledge/intelligent-routing/SKILL.md +334 -0
- package/templates/skills/knowledge/lint-and-validate/SKILL.md +45 -0
- package/templates/skills/knowledge/lint-and-validate/scripts/lint_runner.py +172 -0
- package/templates/skills/knowledge/lint-and-validate/scripts/type_coverage.py +173 -0
- package/templates/skills/knowledge/mcp-builder/SKILL.md +176 -0
- package/templates/skills/knowledge/mobile-design/SKILL.md +394 -0
- package/templates/skills/knowledge/mobile-design/decision-trees.md +516 -0
- package/templates/skills/knowledge/mobile-design/mobile-backend.md +491 -0
- package/templates/skills/knowledge/mobile-design/mobile-color-system.md +420 -0
- package/templates/skills/knowledge/mobile-design/mobile-debugging.md +122 -0
- package/templates/skills/knowledge/mobile-design/mobile-design-thinking.md +357 -0
- package/templates/skills/knowledge/mobile-design/mobile-navigation.md +458 -0
- package/templates/skills/knowledge/mobile-design/mobile-performance.md +767 -0
- package/templates/skills/knowledge/mobile-design/mobile-testing.md +356 -0
- package/templates/skills/knowledge/mobile-design/mobile-typography.md +433 -0
- package/templates/skills/knowledge/mobile-design/platform-android.md +666 -0
- package/templates/skills/knowledge/mobile-design/platform-ios.md +561 -0
- package/templates/skills/knowledge/mobile-design/scripts/mobile_audit.py +670 -0
- package/templates/skills/knowledge/mobile-design/touch-psychology.md +537 -0
- package/templates/skills/knowledge/nextjs-best-practices/SKILL.md +203 -0
- package/templates/skills/knowledge/nodejs-best-practices/SKILL.md +333 -0
- package/templates/skills/knowledge/parallel-agents/SKILL.md +175 -0
- package/templates/skills/knowledge/performance-profiling/SKILL.md +143 -0
- package/templates/skills/knowledge/performance-profiling/scripts/lighthouse_audit.py +76 -0
- package/templates/skills/knowledge/plan-writing/SKILL.md +152 -0
- package/templates/skills/knowledge/powershell-windows/SKILL.md +167 -0
- package/templates/skills/knowledge/python-patterns/SKILL.md +441 -0
- package/templates/skills/knowledge/react-patterns/SKILL.md +198 -0
- package/templates/skills/knowledge/red-team-tactics/SKILL.md +199 -0
- package/templates/skills/knowledge/seo-fundamentals/SKILL.md +129 -0
- package/templates/skills/knowledge/seo-fundamentals/scripts/seo_checker.py +219 -0
- package/templates/skills/knowledge/server-management/SKILL.md +161 -0
- package/templates/skills/knowledge/systematic-debugging/SKILL.md +109 -0
- package/templates/skills/knowledge/tailwind-patterns/SKILL.md +269 -0
- package/templates/skills/knowledge/tdd-workflow/SKILL.md +149 -0
- package/templates/skills/knowledge/testing-patterns/SKILL.md +178 -0
- package/templates/skills/knowledge/testing-patterns/scripts/test_runner.py +219 -0
- package/templates/skills/knowledge/vulnerability-scanner/SKILL.md +276 -0
- package/templates/skills/knowledge/vulnerability-scanner/checklists.md +121 -0
- package/templates/skills/knowledge/vulnerability-scanner/scripts/security_scan.py +458 -0
- package/templates/skills/knowledge/webapp-testing/SKILL.md +187 -0
- package/templates/skills/knowledge/webapp-testing/scripts/playwright_runner.py +173 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Changelog Generator
|
|
4
|
+
|
|
5
|
+
Creates structured changelog entries from detected code changes.
|
|
6
|
+
Follows the Keep a Changelog format (https://keepachangelog.com/).
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python generate_changelog.py --scope <path> --since <commit|date> [options]
|
|
10
|
+
|
|
11
|
+
Arguments:
|
|
12
|
+
--scope Directory to analyze (required)
|
|
13
|
+
--since Changes since commit/date (required)
|
|
14
|
+
--output Output file (default: stdout)
|
|
15
|
+
--format Changelog format (default: keep-a-changelog)
|
|
16
|
+
--prepend Prepend to existing changelog (optional)
|
|
17
|
+
|
|
18
|
+
Exit Codes:
|
|
19
|
+
0 - Success
|
|
20
|
+
1 - Invalid arguments
|
|
21
|
+
2 - Directory not found
|
|
22
|
+
3 - Output error
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import argparse
|
|
26
|
+
import json
|
|
27
|
+
import re
|
|
28
|
+
import subprocess
|
|
29
|
+
import sys
|
|
30
|
+
from datetime import datetime
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Dict, List, Optional, Tuple
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def run_git_command(args: List[str], cwd: Path) -> Tuple[bool, str]:
|
|
36
|
+
"""Execute a git command and return (success, output)."""
|
|
37
|
+
try:
|
|
38
|
+
result = subprocess.run(
|
|
39
|
+
['git'] + args,
|
|
40
|
+
cwd=cwd,
|
|
41
|
+
capture_output=True,
|
|
42
|
+
text=True,
|
|
43
|
+
timeout=30
|
|
44
|
+
)
|
|
45
|
+
return result.returncode == 0, result.stdout.strip()
|
|
46
|
+
except Exception as e:
|
|
47
|
+
return False, str(e)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_git_log(scope: Path, since: str) -> List[Dict]:
|
|
51
|
+
"""Get git log entries since a specific commit or date."""
|
|
52
|
+
# Format: hash|author|date|subject
|
|
53
|
+
success, output = run_git_command(
|
|
54
|
+
['log', '--format=%H|%an|%ai|%s', since + '..HEAD', '--', str(scope)],
|
|
55
|
+
scope
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if not success:
|
|
59
|
+
# Try alternative format
|
|
60
|
+
success, output = run_git_command(
|
|
61
|
+
['log', '--format=%H|%an|%ai|%s', '-20', '--', str(scope)],
|
|
62
|
+
scope
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if not success or not output:
|
|
66
|
+
return []
|
|
67
|
+
|
|
68
|
+
commits = []
|
|
69
|
+
for line in output.split('\n'):
|
|
70
|
+
if not line.strip():
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
parts = line.split('|', 3)
|
|
74
|
+
if len(parts) >= 4:
|
|
75
|
+
commits.append({
|
|
76
|
+
'hash': parts[0][:7],
|
|
77
|
+
'author': parts[1],
|
|
78
|
+
'date': parts[2].split()[0],
|
|
79
|
+
'subject': parts[3]
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
return commits
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_changed_files(scope: Path, since: str) -> Dict[str, List[str]]:
|
|
86
|
+
"""Get files changed with their status."""
|
|
87
|
+
success, output = run_git_command(
|
|
88
|
+
['diff', '--name-status', since, '--', str(scope)],
|
|
89
|
+
scope
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if not success:
|
|
93
|
+
success, output = run_git_command(
|
|
94
|
+
['diff', '--name-status', 'HEAD~10', '--', str(scope)],
|
|
95
|
+
scope
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
changes = {'added': [], 'modified': [], 'deleted': []}
|
|
99
|
+
|
|
100
|
+
if not success or not output:
|
|
101
|
+
return changes
|
|
102
|
+
|
|
103
|
+
for line in output.split('\n'):
|
|
104
|
+
if not line.strip():
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
parts = line.split('\t')
|
|
108
|
+
if len(parts) < 2:
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
status, filepath = parts[0], parts[1]
|
|
112
|
+
|
|
113
|
+
if status.startswith('A'):
|
|
114
|
+
changes['added'].append(filepath)
|
|
115
|
+
elif status.startswith('M'):
|
|
116
|
+
changes['modified'].append(filepath)
|
|
117
|
+
elif status.startswith('D'):
|
|
118
|
+
changes['deleted'].append(filepath)
|
|
119
|
+
|
|
120
|
+
return changes
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def categorize_change(filepath: str) -> str:
|
|
124
|
+
"""Categorize a file change for changelog purposes."""
|
|
125
|
+
if 'SKILL.md' in filepath:
|
|
126
|
+
return 'skill'
|
|
127
|
+
if '/scripts/' in filepath:
|
|
128
|
+
return 'script'
|
|
129
|
+
if '/references/' in filepath:
|
|
130
|
+
return 'documentation'
|
|
131
|
+
if filepath.endswith('.py'):
|
|
132
|
+
return 'code'
|
|
133
|
+
if filepath.endswith('.md'):
|
|
134
|
+
return 'documentation'
|
|
135
|
+
return 'other'
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def generate_changelog_entry(changes: Dict, commits: List[Dict],
|
|
139
|
+
scope: str, version: str = 'Unreleased') -> str:
|
|
140
|
+
"""Generate a changelog entry in Keep a Changelog format."""
|
|
141
|
+
today = datetime.now().strftime('%Y-%m-%d')
|
|
142
|
+
|
|
143
|
+
lines = [
|
|
144
|
+
f"## [{version}] - {today}",
|
|
145
|
+
""
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
# Group changes
|
|
149
|
+
added = []
|
|
150
|
+
changed = []
|
|
151
|
+
removed = []
|
|
152
|
+
|
|
153
|
+
for filepath in changes['added']:
|
|
154
|
+
category = categorize_change(filepath)
|
|
155
|
+
filename = Path(filepath).name
|
|
156
|
+
|
|
157
|
+
if category == 'skill':
|
|
158
|
+
added.append(f"New skill definition: `{Path(filepath).parent.name}`")
|
|
159
|
+
elif category == 'script':
|
|
160
|
+
skill = filepath.split('/scripts/')[0].split('/')[-1] if '/scripts/' in filepath else 'unknown'
|
|
161
|
+
added.append(f"New script `{filename}` for `{skill}` skill")
|
|
162
|
+
elif category == 'documentation':
|
|
163
|
+
added.append(f"New documentation: `{filename}`")
|
|
164
|
+
else:
|
|
165
|
+
added.append(f"Added `{filepath}`")
|
|
166
|
+
|
|
167
|
+
for filepath in changes['modified']:
|
|
168
|
+
category = categorize_change(filepath)
|
|
169
|
+
filename = Path(filepath).name
|
|
170
|
+
|
|
171
|
+
if category == 'skill':
|
|
172
|
+
skill = Path(filepath).parent.name
|
|
173
|
+
changed.append(f"Updated `{skill}` skill definition")
|
|
174
|
+
elif category == 'script':
|
|
175
|
+
skill = filepath.split('/scripts/')[0].split('/')[-1] if '/scripts/' in filepath else 'unknown'
|
|
176
|
+
changed.append(f"Modified `{filename}` in `{skill}` skill")
|
|
177
|
+
elif category == 'documentation':
|
|
178
|
+
changed.append(f"Updated documentation: `{filename}`")
|
|
179
|
+
else:
|
|
180
|
+
changed.append(f"Modified `{filepath}`")
|
|
181
|
+
|
|
182
|
+
for filepath in changes['deleted']:
|
|
183
|
+
category = categorize_change(filepath)
|
|
184
|
+
filename = Path(filepath).name
|
|
185
|
+
|
|
186
|
+
if category == 'skill':
|
|
187
|
+
removed.append(f"Removed skill: `{Path(filepath).parent.name}`")
|
|
188
|
+
else:
|
|
189
|
+
removed.append(f"Removed `{filepath}`")
|
|
190
|
+
|
|
191
|
+
# Deduplicate
|
|
192
|
+
added = list(dict.fromkeys(added))
|
|
193
|
+
changed = list(dict.fromkeys(changed))
|
|
194
|
+
removed = list(dict.fromkeys(removed))
|
|
195
|
+
|
|
196
|
+
# Write sections
|
|
197
|
+
if added:
|
|
198
|
+
lines.append("### Added")
|
|
199
|
+
for item in added:
|
|
200
|
+
lines.append(f"- {item}")
|
|
201
|
+
lines.append("")
|
|
202
|
+
|
|
203
|
+
if changed:
|
|
204
|
+
lines.append("### Changed")
|
|
205
|
+
for item in changed:
|
|
206
|
+
lines.append(f"- {item}")
|
|
207
|
+
lines.append("")
|
|
208
|
+
|
|
209
|
+
if removed:
|
|
210
|
+
lines.append("### Removed")
|
|
211
|
+
for item in removed:
|
|
212
|
+
lines.append(f"- {item}")
|
|
213
|
+
lines.append("")
|
|
214
|
+
|
|
215
|
+
# Add commit references if available
|
|
216
|
+
if commits:
|
|
217
|
+
lines.append("### Commits")
|
|
218
|
+
for commit in commits[:10]: # Limit to 10 commits
|
|
219
|
+
lines.append(f"- `{commit['hash']}` {commit['subject']}")
|
|
220
|
+
lines.append("")
|
|
221
|
+
|
|
222
|
+
return '\n'.join(lines)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def prepend_to_changelog(new_entry: str, changelog_path: Path) -> bool:
|
|
226
|
+
"""Prepend a new entry to an existing changelog."""
|
|
227
|
+
if not changelog_path.exists():
|
|
228
|
+
# Create new changelog
|
|
229
|
+
header = """# Changelog
|
|
230
|
+
|
|
231
|
+
All notable changes to this project will be documented in this file.
|
|
232
|
+
|
|
233
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
234
|
+
|
|
235
|
+
"""
|
|
236
|
+
changelog_path.write_text(header + new_entry)
|
|
237
|
+
return True
|
|
238
|
+
|
|
239
|
+
content = changelog_path.read_text()
|
|
240
|
+
|
|
241
|
+
# Find where to insert (after header, before first version)
|
|
242
|
+
insert_pattern = r'(# Changelog.*?\n\n)'
|
|
243
|
+
match = re.search(insert_pattern, content, re.DOTALL)
|
|
244
|
+
|
|
245
|
+
if match:
|
|
246
|
+
insert_pos = match.end()
|
|
247
|
+
new_content = content[:insert_pos] + new_entry + '\n' + content[insert_pos:]
|
|
248
|
+
changelog_path.write_text(new_content)
|
|
249
|
+
return True
|
|
250
|
+
else:
|
|
251
|
+
# Just prepend
|
|
252
|
+
changelog_path.write_text(new_entry + '\n' + content)
|
|
253
|
+
return True
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def main():
|
|
257
|
+
parser = argparse.ArgumentParser(
|
|
258
|
+
description='Generate changelog entries from code changes',
|
|
259
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
260
|
+
epilog=__doc__
|
|
261
|
+
)
|
|
262
|
+
parser.add_argument('--scope', required=True, help='Directory to analyze')
|
|
263
|
+
parser.add_argument('--since', required=True, help='Changes since commit/date')
|
|
264
|
+
parser.add_argument('--output', help='Output file (default: stdout)')
|
|
265
|
+
parser.add_argument('--format', default='keep-a-changelog', help='Changelog format')
|
|
266
|
+
parser.add_argument('--prepend', action='store_true', help='Prepend to existing changelog')
|
|
267
|
+
parser.add_argument('--version', default='Unreleased', help='Version label')
|
|
268
|
+
args = parser.parse_args()
|
|
269
|
+
|
|
270
|
+
scope = Path(args.scope).resolve()
|
|
271
|
+
|
|
272
|
+
if not scope.exists():
|
|
273
|
+
print(f"❌ Error: Directory not found: {scope}", file=sys.stderr)
|
|
274
|
+
sys.exit(2)
|
|
275
|
+
|
|
276
|
+
print(f"📋 Generating changelog...", file=sys.stderr)
|
|
277
|
+
print(f" Scope: {scope}", file=sys.stderr)
|
|
278
|
+
print(f" Since: {args.since}", file=sys.stderr)
|
|
279
|
+
|
|
280
|
+
# Get changes and commits
|
|
281
|
+
changes = get_changed_files(scope, args.since)
|
|
282
|
+
commits = get_git_log(scope, args.since)
|
|
283
|
+
|
|
284
|
+
total_changes = sum(len(v) for v in changes.values())
|
|
285
|
+
print(f" Found {total_changes} changed file(s), {len(commits)} commit(s)", file=sys.stderr)
|
|
286
|
+
|
|
287
|
+
if total_changes == 0 and len(commits) == 0:
|
|
288
|
+
print(" No changes to document", file=sys.stderr)
|
|
289
|
+
sys.exit(0)
|
|
290
|
+
|
|
291
|
+
# Generate entry
|
|
292
|
+
entry = generate_changelog_entry(changes, commits, str(scope), args.version)
|
|
293
|
+
|
|
294
|
+
# Output
|
|
295
|
+
if args.output:
|
|
296
|
+
output_path = Path(args.output)
|
|
297
|
+
|
|
298
|
+
if args.prepend:
|
|
299
|
+
prepend_to_changelog(entry, output_path)
|
|
300
|
+
print(f"✅ Prepended to: {args.output}", file=sys.stderr)
|
|
301
|
+
else:
|
|
302
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
303
|
+
output_path.write_text(entry)
|
|
304
|
+
print(f"✅ Saved to: {args.output}", file=sys.stderr)
|
|
305
|
+
else:
|
|
306
|
+
print(entry)
|
|
307
|
+
|
|
308
|
+
sys.exit(0)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
if __name__ == '__main__':
|
|
312
|
+
main()
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Full Documentation Synchronization
|
|
4
|
+
|
|
5
|
+
Orchestrates a complete documentation update across the entire repository.
|
|
6
|
+
Combines change detection, skill updates, and catalog synchronization.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python sync_docs.py --skills-dir <path> [options]
|
|
10
|
+
|
|
11
|
+
Arguments:
|
|
12
|
+
--skills-dir Skills directory (required)
|
|
13
|
+
--update-catalog Update SKILLS_CATALOG.md (default: true)
|
|
14
|
+
--update-readme Update README.md if exists (optional)
|
|
15
|
+
--dry-run Preview changes without writing (optional)
|
|
16
|
+
--report Save sync report to file (optional)
|
|
17
|
+
|
|
18
|
+
Exit Codes:
|
|
19
|
+
0 - Success
|
|
20
|
+
1 - Invalid arguments
|
|
21
|
+
2 - Directory not found
|
|
22
|
+
3 - Sync error
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import argparse
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
from datetime import datetime
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import List, Dict, Tuple
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def find_repo_root(start_path: Path) -> Path:
|
|
34
|
+
"""Find repository root by looking for AGENTS.md or .git."""
|
|
35
|
+
current = start_path.resolve()
|
|
36
|
+
|
|
37
|
+
while current != current.parent:
|
|
38
|
+
if (current / 'AGENTS.md').exists() or (current / '.git').exists():
|
|
39
|
+
return current
|
|
40
|
+
current = current.parent
|
|
41
|
+
|
|
42
|
+
return start_path.resolve()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def scan_all_skills(skills_dir: Path) -> List[Dict]:
|
|
46
|
+
"""Scan all skills and gather their metadata."""
|
|
47
|
+
skills = []
|
|
48
|
+
|
|
49
|
+
for item in sorted(skills_dir.iterdir()):
|
|
50
|
+
if not item.is_dir():
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
skill_md = item / 'SKILL.md'
|
|
54
|
+
if not skill_md.exists():
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
skill_info = {
|
|
58
|
+
'name': item.name,
|
|
59
|
+
'path': item,
|
|
60
|
+
'scripts': list((item / 'scripts').glob('*.py')) if (item / 'scripts').exists() else [],
|
|
61
|
+
'references': list((item / 'references').glob('*.md')) if (item / 'references').exists() else [],
|
|
62
|
+
'has_assets': (item / 'assets').exists() and any((item / 'assets').iterdir()),
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Filter out example files
|
|
66
|
+
skill_info['scripts'] = [s for s in skill_info['scripts']
|
|
67
|
+
if not s.name.startswith('example') and not s.name.startswith('__')]
|
|
68
|
+
skill_info['references'] = [r for r in skill_info['references']
|
|
69
|
+
if not r.name.startswith('example')]
|
|
70
|
+
|
|
71
|
+
skills.append(skill_info)
|
|
72
|
+
|
|
73
|
+
return skills
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def update_skill_timestamps(skills: List[Dict], dry_run: bool = False) -> List[Tuple[str, bool]]:
|
|
77
|
+
"""Update the 'Last Updated' timestamp in all SKILL.md files."""
|
|
78
|
+
results = []
|
|
79
|
+
today = datetime.now().strftime('%Y-%m-%d')
|
|
80
|
+
|
|
81
|
+
for skill in skills:
|
|
82
|
+
skill_md = skill['path'] / 'SKILL.md'
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
content = skill_md.read_text()
|
|
86
|
+
|
|
87
|
+
# Check if timestamp exists and update it
|
|
88
|
+
import re
|
|
89
|
+
pattern = r'>\s*\*\*Last Updated:\*\*\s*\d{4}-\d{2}-\d{2}'
|
|
90
|
+
replacement = f'> **Last Updated:** {today}'
|
|
91
|
+
|
|
92
|
+
if re.search(pattern, content):
|
|
93
|
+
new_content = re.sub(pattern, replacement, content)
|
|
94
|
+
|
|
95
|
+
if new_content != content and not dry_run:
|
|
96
|
+
skill_md.write_text(new_content)
|
|
97
|
+
results.append((skill['name'], True))
|
|
98
|
+
else:
|
|
99
|
+
results.append((skill['name'], False)) # No change needed or dry run
|
|
100
|
+
else:
|
|
101
|
+
results.append((skill['name'], False)) # No timestamp found
|
|
102
|
+
|
|
103
|
+
except Exception as e:
|
|
104
|
+
print(f" ⚠️ Error updating {skill['name']}: {e}", file=sys.stderr)
|
|
105
|
+
results.append((skill['name'], False))
|
|
106
|
+
|
|
107
|
+
return results
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def run_catalog_update(skills_dir: Path, repo_root: Path, dry_run: bool = False) -> bool:
|
|
111
|
+
"""Run the skills catalog updater."""
|
|
112
|
+
update_script = repo_root / 'skill-creator' / 'scripts' / 'update_catalog.py'
|
|
113
|
+
|
|
114
|
+
if not update_script.exists():
|
|
115
|
+
print(f" ⚠️ Catalog update script not found: {update_script}", file=sys.stderr)
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
if dry_run:
|
|
119
|
+
print(" 🔍 [Dry Run] Would update SKILLS_CATALOG.md")
|
|
120
|
+
return True
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
result = subprocess.run(
|
|
124
|
+
[sys.executable, str(update_script), '--skills-dir', str(skills_dir)],
|
|
125
|
+
cwd=repo_root,
|
|
126
|
+
capture_output=True,
|
|
127
|
+
text=True,
|
|
128
|
+
timeout=60
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if result.returncode == 0:
|
|
132
|
+
return True
|
|
133
|
+
else:
|
|
134
|
+
print(f" ⚠️ Catalog update failed: {result.stderr}", file=sys.stderr)
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
except Exception as e:
|
|
138
|
+
print(f" ⚠️ Error running catalog update: {e}", file=sys.stderr)
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def generate_sync_report(skills: List[Dict], catalog_updated: bool,
|
|
143
|
+
timestamp_results: List[Tuple[str, bool]]) -> str:
|
|
144
|
+
"""Generate a synchronization report."""
|
|
145
|
+
now = datetime.now().strftime('%Y-%m-%d %H:%M')
|
|
146
|
+
|
|
147
|
+
lines = [
|
|
148
|
+
"# Documentation Synchronization Report",
|
|
149
|
+
"",
|
|
150
|
+
f"> **Generated:** {now}",
|
|
151
|
+
"",
|
|
152
|
+
"---",
|
|
153
|
+
"",
|
|
154
|
+
"## Summary",
|
|
155
|
+
"",
|
|
156
|
+
f"- **Skills Scanned:** {len(skills)}",
|
|
157
|
+
f"- **Catalog Updated:** {'✅ Yes' if catalog_updated else '❌ No'}",
|
|
158
|
+
f"- **Timestamps Updated:** {sum(1 for _, updated in timestamp_results if updated)}",
|
|
159
|
+
"",
|
|
160
|
+
"---",
|
|
161
|
+
"",
|
|
162
|
+
"## Skills Overview",
|
|
163
|
+
"",
|
|
164
|
+
"| Skill | Scripts | References | Timestamp Updated |",
|
|
165
|
+
"|-------|---------|------------|-------------------|",
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
for skill in skills:
|
|
169
|
+
name = skill['name']
|
|
170
|
+
scripts_count = len(skill['scripts'])
|
|
171
|
+
refs_count = len(skill['references'])
|
|
172
|
+
|
|
173
|
+
# Find timestamp result
|
|
174
|
+
ts_updated = '—'
|
|
175
|
+
for ts_name, updated in timestamp_results:
|
|
176
|
+
if ts_name == name:
|
|
177
|
+
ts_updated = '✅' if updated else '—'
|
|
178
|
+
break
|
|
179
|
+
|
|
180
|
+
lines.append(f"| `{name}` | {scripts_count} | {refs_count} | {ts_updated} |")
|
|
181
|
+
|
|
182
|
+
lines.extend([
|
|
183
|
+
"",
|
|
184
|
+
"---",
|
|
185
|
+
"",
|
|
186
|
+
"## Next Steps",
|
|
187
|
+
"",
|
|
188
|
+
])
|
|
189
|
+
|
|
190
|
+
if not catalog_updated:
|
|
191
|
+
lines.append("- [ ] Manually run catalog update if it failed")
|
|
192
|
+
|
|
193
|
+
lines.extend([
|
|
194
|
+
"- [ ] Review any skills with warnings",
|
|
195
|
+
"- [ ] Commit documentation changes",
|
|
196
|
+
"",
|
|
197
|
+
])
|
|
198
|
+
|
|
199
|
+
return '\n'.join(lines)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def main():
|
|
203
|
+
parser = argparse.ArgumentParser(
|
|
204
|
+
description='Synchronize all documentation',
|
|
205
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
206
|
+
epilog=__doc__
|
|
207
|
+
)
|
|
208
|
+
parser.add_argument('--skills-dir', required=True, help='Skills directory')
|
|
209
|
+
parser.add_argument('--update-catalog', type=bool, default=True, help='Update catalog')
|
|
210
|
+
parser.add_argument('--update-readme', action='store_true', help='Update README.md')
|
|
211
|
+
parser.add_argument('--dry-run', action='store_true', help='Preview without writing')
|
|
212
|
+
parser.add_argument('--report', help='Save report to file')
|
|
213
|
+
args = parser.parse_args()
|
|
214
|
+
|
|
215
|
+
skills_dir = Path(args.skills_dir).resolve()
|
|
216
|
+
|
|
217
|
+
if not skills_dir.exists():
|
|
218
|
+
print(f"❌ Error: Skills directory not found: {skills_dir}", file=sys.stderr)
|
|
219
|
+
sys.exit(2)
|
|
220
|
+
|
|
221
|
+
repo_root = find_repo_root(skills_dir)
|
|
222
|
+
|
|
223
|
+
print(f"🔄 Documentation Synchronization")
|
|
224
|
+
print(f" Skills directory: {skills_dir}")
|
|
225
|
+
print(f" Repository root: {repo_root}")
|
|
226
|
+
if args.dry_run:
|
|
227
|
+
print(f" Mode: DRY RUN (no changes will be written)")
|
|
228
|
+
print()
|
|
229
|
+
|
|
230
|
+
# Scan all skills
|
|
231
|
+
print("📂 Scanning skills...")
|
|
232
|
+
skills = scan_all_skills(skills_dir)
|
|
233
|
+
print(f" Found {len(skills)} skill(s)")
|
|
234
|
+
|
|
235
|
+
# Update timestamps
|
|
236
|
+
print("\n📅 Updating timestamps...")
|
|
237
|
+
timestamp_results = update_skill_timestamps(skills, args.dry_run)
|
|
238
|
+
updated_count = sum(1 for _, updated in timestamp_results if updated)
|
|
239
|
+
print(f" Updated {updated_count} timestamp(s)")
|
|
240
|
+
|
|
241
|
+
# Update catalog
|
|
242
|
+
catalog_updated = False
|
|
243
|
+
if args.update_catalog:
|
|
244
|
+
print("\n📚 Updating skills catalog...")
|
|
245
|
+
catalog_updated = run_catalog_update(skills_dir, repo_root, args.dry_run)
|
|
246
|
+
if catalog_updated:
|
|
247
|
+
print(" ✅ Catalog updated successfully")
|
|
248
|
+
else:
|
|
249
|
+
print(" ⚠️ Catalog update had issues")
|
|
250
|
+
|
|
251
|
+
# Generate report
|
|
252
|
+
report = generate_sync_report(skills, catalog_updated, timestamp_results)
|
|
253
|
+
|
|
254
|
+
if args.report:
|
|
255
|
+
report_path = Path(args.report)
|
|
256
|
+
report_path.parent.mkdir(parents=True, exist_ok=True)
|
|
257
|
+
|
|
258
|
+
if not args.dry_run:
|
|
259
|
+
report_path.write_text(report)
|
|
260
|
+
print(f"\n📋 Report saved to: {args.report}")
|
|
261
|
+
else:
|
|
262
|
+
print(f"\n📋 [Dry Run] Would save report to: {args.report}")
|
|
263
|
+
|
|
264
|
+
print("\n" + "=" * 50)
|
|
265
|
+
print("✅ Documentation synchronization complete!")
|
|
266
|
+
print("=" * 50)
|
|
267
|
+
|
|
268
|
+
sys.exit(0)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
if __name__ == '__main__':
|
|
272
|
+
main()
|