solokit 0.1.1__py3-none-any.whl
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.
- solokit/__init__.py +10 -0
- solokit/__version__.py +3 -0
- solokit/cli.py +374 -0
- solokit/core/__init__.py +1 -0
- solokit/core/cache.py +102 -0
- solokit/core/command_runner.py +278 -0
- solokit/core/config.py +453 -0
- solokit/core/config_validator.py +204 -0
- solokit/core/constants.py +291 -0
- solokit/core/error_formatter.py +279 -0
- solokit/core/error_handlers.py +346 -0
- solokit/core/exceptions.py +1567 -0
- solokit/core/file_ops.py +309 -0
- solokit/core/logging_config.py +166 -0
- solokit/core/output.py +99 -0
- solokit/core/performance.py +57 -0
- solokit/core/protocols.py +141 -0
- solokit/core/types.py +312 -0
- solokit/deployment/__init__.py +1 -0
- solokit/deployment/executor.py +411 -0
- solokit/git/__init__.py +1 -0
- solokit/git/integration.py +619 -0
- solokit/init/__init__.py +41 -0
- solokit/init/claude_commands_installer.py +87 -0
- solokit/init/dependency_installer.py +313 -0
- solokit/init/docs_structure.py +90 -0
- solokit/init/env_generator.py +160 -0
- solokit/init/environment_validator.py +334 -0
- solokit/init/git_hooks_installer.py +71 -0
- solokit/init/git_setup.py +188 -0
- solokit/init/gitignore_updater.py +195 -0
- solokit/init/initial_commit.py +145 -0
- solokit/init/initial_scans.py +109 -0
- solokit/init/orchestrator.py +246 -0
- solokit/init/readme_generator.py +207 -0
- solokit/init/session_structure.py +239 -0
- solokit/init/template_installer.py +424 -0
- solokit/learning/__init__.py +1 -0
- solokit/learning/archiver.py +115 -0
- solokit/learning/categorizer.py +126 -0
- solokit/learning/curator.py +428 -0
- solokit/learning/extractor.py +352 -0
- solokit/learning/reporter.py +351 -0
- solokit/learning/repository.py +254 -0
- solokit/learning/similarity.py +342 -0
- solokit/learning/validator.py +144 -0
- solokit/project/__init__.py +1 -0
- solokit/project/init.py +1162 -0
- solokit/project/stack.py +436 -0
- solokit/project/sync_plugin.py +438 -0
- solokit/project/tree.py +375 -0
- solokit/quality/__init__.py +1 -0
- solokit/quality/api_validator.py +424 -0
- solokit/quality/checkers/__init__.py +25 -0
- solokit/quality/checkers/base.py +114 -0
- solokit/quality/checkers/context7.py +221 -0
- solokit/quality/checkers/custom.py +162 -0
- solokit/quality/checkers/deployment.py +323 -0
- solokit/quality/checkers/documentation.py +179 -0
- solokit/quality/checkers/formatting.py +161 -0
- solokit/quality/checkers/integration.py +394 -0
- solokit/quality/checkers/linting.py +159 -0
- solokit/quality/checkers/security.py +261 -0
- solokit/quality/checkers/spec_completeness.py +127 -0
- solokit/quality/checkers/tests.py +184 -0
- solokit/quality/env_validator.py +306 -0
- solokit/quality/gates.py +655 -0
- solokit/quality/reporters/__init__.py +10 -0
- solokit/quality/reporters/base.py +25 -0
- solokit/quality/reporters/console.py +98 -0
- solokit/quality/reporters/json_reporter.py +34 -0
- solokit/quality/results.py +98 -0
- solokit/session/__init__.py +1 -0
- solokit/session/briefing/__init__.py +245 -0
- solokit/session/briefing/documentation_loader.py +53 -0
- solokit/session/briefing/formatter.py +476 -0
- solokit/session/briefing/git_context.py +282 -0
- solokit/session/briefing/learning_loader.py +212 -0
- solokit/session/briefing/milestone_builder.py +78 -0
- solokit/session/briefing/orchestrator.py +137 -0
- solokit/session/briefing/stack_detector.py +51 -0
- solokit/session/briefing/tree_generator.py +52 -0
- solokit/session/briefing/work_item_loader.py +209 -0
- solokit/session/briefing.py +353 -0
- solokit/session/complete.py +1188 -0
- solokit/session/status.py +246 -0
- solokit/session/validate.py +452 -0
- solokit/templates/.claude/commands/end.md +109 -0
- solokit/templates/.claude/commands/init.md +159 -0
- solokit/templates/.claude/commands/learn-curate.md +88 -0
- solokit/templates/.claude/commands/learn-search.md +62 -0
- solokit/templates/.claude/commands/learn-show.md +69 -0
- solokit/templates/.claude/commands/learn.md +136 -0
- solokit/templates/.claude/commands/start.md +114 -0
- solokit/templates/.claude/commands/status.md +22 -0
- solokit/templates/.claude/commands/validate.md +27 -0
- solokit/templates/.claude/commands/work-delete.md +119 -0
- solokit/templates/.claude/commands/work-graph.md +139 -0
- solokit/templates/.claude/commands/work-list.md +26 -0
- solokit/templates/.claude/commands/work-new.md +114 -0
- solokit/templates/.claude/commands/work-next.md +25 -0
- solokit/templates/.claude/commands/work-show.md +24 -0
- solokit/templates/.claude/commands/work-update.md +141 -0
- solokit/templates/CHANGELOG.md +17 -0
- solokit/templates/WORK_ITEM_TYPES.md +141 -0
- solokit/templates/__init__.py +1 -0
- solokit/templates/bug_spec.md +217 -0
- solokit/templates/config.schema.json +150 -0
- solokit/templates/dashboard_refine/base/.gitignore +36 -0
- solokit/templates/dashboard_refine/base/app/(dashboard)/layout.tsx +22 -0
- solokit/templates/dashboard_refine/base/app/(dashboard)/page.tsx +68 -0
- solokit/templates/dashboard_refine/base/app/(dashboard)/users/page.tsx +77 -0
- solokit/templates/dashboard_refine/base/app/globals.css +60 -0
- solokit/templates/dashboard_refine/base/app/layout.tsx +23 -0
- solokit/templates/dashboard_refine/base/app/page.tsx +9 -0
- solokit/templates/dashboard_refine/base/components/client-refine-wrapper.tsx +21 -0
- solokit/templates/dashboard_refine/base/components/layout/header.tsx +44 -0
- solokit/templates/dashboard_refine/base/components/layout/sidebar.tsx +82 -0
- solokit/templates/dashboard_refine/base/components/ui/button.tsx +53 -0
- solokit/templates/dashboard_refine/base/components/ui/card.tsx +78 -0
- solokit/templates/dashboard_refine/base/components/ui/table.tsx +116 -0
- solokit/templates/dashboard_refine/base/components.json +16 -0
- solokit/templates/dashboard_refine/base/lib/refine.tsx +65 -0
- solokit/templates/dashboard_refine/base/lib/utils.ts +13 -0
- solokit/templates/dashboard_refine/base/next.config.ts +10 -0
- solokit/templates/dashboard_refine/base/package.json.template +40 -0
- solokit/templates/dashboard_refine/base/postcss.config.mjs +8 -0
- solokit/templates/dashboard_refine/base/providers/refine-provider.tsx +26 -0
- solokit/templates/dashboard_refine/base/tailwind.config.ts +57 -0
- solokit/templates/dashboard_refine/base/tsconfig.json +27 -0
- solokit/templates/dashboard_refine/docker/Dockerfile +57 -0
- solokit/templates/dashboard_refine/docker/docker-compose.prod.yml +31 -0
- solokit/templates/dashboard_refine/docker/docker-compose.yml +21 -0
- solokit/templates/dashboard_refine/tier-1-essential/.eslintrc.json +7 -0
- solokit/templates/dashboard_refine/tier-1-essential/jest.config.ts +17 -0
- solokit/templates/dashboard_refine/tier-1-essential/jest.setup.ts +1 -0
- solokit/templates/dashboard_refine/tier-1-essential/package.json.tier1.template +57 -0
- solokit/templates/dashboard_refine/tier-1-essential/tests/setup.ts +26 -0
- solokit/templates/dashboard_refine/tier-1-essential/tests/unit/example.test.tsx +73 -0
- solokit/templates/dashboard_refine/tier-2-standard/package.json.tier2.template +62 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/eslint.config.mjs +22 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/package.json.tier3.template +79 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/playwright.config.ts +66 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/stryker.conf.json +38 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/tests/e2e/dashboard.spec.ts +88 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/tests/e2e/user-management.spec.ts +102 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/tests/integration/dashboard.test.tsx +90 -0
- solokit/templates/dashboard_refine/tier-3-comprehensive/type-coverage.json +16 -0
- solokit/templates/dashboard_refine/tier-4-production/instrumentation.ts +9 -0
- solokit/templates/dashboard_refine/tier-4-production/k6/dashboard-load-test.js +70 -0
- solokit/templates/dashboard_refine/tier-4-production/next.config.ts +46 -0
- solokit/templates/dashboard_refine/tier-4-production/package.json.tier4.template +89 -0
- solokit/templates/dashboard_refine/tier-4-production/sentry.client.config.ts +26 -0
- solokit/templates/dashboard_refine/tier-4-production/sentry.edge.config.ts +11 -0
- solokit/templates/dashboard_refine/tier-4-production/sentry.server.config.ts +11 -0
- solokit/templates/deployment_spec.md +500 -0
- solokit/templates/feature_spec.md +248 -0
- solokit/templates/fullstack_nextjs/base/.gitignore +36 -0
- solokit/templates/fullstack_nextjs/base/app/api/example/route.ts +65 -0
- solokit/templates/fullstack_nextjs/base/app/globals.css +27 -0
- solokit/templates/fullstack_nextjs/base/app/layout.tsx +20 -0
- solokit/templates/fullstack_nextjs/base/app/page.tsx +32 -0
- solokit/templates/fullstack_nextjs/base/components/example-component.tsx +20 -0
- solokit/templates/fullstack_nextjs/base/lib/prisma.ts +17 -0
- solokit/templates/fullstack_nextjs/base/lib/utils.ts +13 -0
- solokit/templates/fullstack_nextjs/base/lib/validations.ts +20 -0
- solokit/templates/fullstack_nextjs/base/next.config.ts +7 -0
- solokit/templates/fullstack_nextjs/base/package.json.template +32 -0
- solokit/templates/fullstack_nextjs/base/postcss.config.mjs +8 -0
- solokit/templates/fullstack_nextjs/base/prisma/schema.prisma +21 -0
- solokit/templates/fullstack_nextjs/base/tailwind.config.ts +19 -0
- solokit/templates/fullstack_nextjs/base/tsconfig.json +27 -0
- solokit/templates/fullstack_nextjs/docker/Dockerfile +60 -0
- solokit/templates/fullstack_nextjs/docker/docker-compose.prod.yml +57 -0
- solokit/templates/fullstack_nextjs/docker/docker-compose.yml +47 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/.eslintrc.json +7 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/jest.config.ts +17 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/jest.setup.ts +1 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/package.json.tier1.template +48 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/tests/api/example.test.ts +88 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/tests/setup.ts +22 -0
- solokit/templates/fullstack_nextjs/tier-1-essential/tests/unit/example.test.tsx +22 -0
- solokit/templates/fullstack_nextjs/tier-2-standard/package.json.tier2.template +52 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/eslint.config.mjs +39 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/package.json.tier3.template +68 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/playwright.config.ts +66 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/stryker.conf.json +33 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/tests/e2e/flow.spec.ts +59 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/tests/integration/api.test.ts +165 -0
- solokit/templates/fullstack_nextjs/tier-3-comprehensive/type-coverage.json +12 -0
- solokit/templates/fullstack_nextjs/tier-4-production/instrumentation.ts +9 -0
- solokit/templates/fullstack_nextjs/tier-4-production/k6/load-test.js +45 -0
- solokit/templates/fullstack_nextjs/tier-4-production/next.config.ts +46 -0
- solokit/templates/fullstack_nextjs/tier-4-production/package.json.tier4.template +77 -0
- solokit/templates/fullstack_nextjs/tier-4-production/sentry.client.config.ts +26 -0
- solokit/templates/fullstack_nextjs/tier-4-production/sentry.edge.config.ts +11 -0
- solokit/templates/fullstack_nextjs/tier-4-production/sentry.server.config.ts +11 -0
- solokit/templates/git-hooks/prepare-commit-msg +24 -0
- solokit/templates/integration_test_spec.md +363 -0
- solokit/templates/learnings.json +15 -0
- solokit/templates/ml_ai_fastapi/base/.gitignore +104 -0
- solokit/templates/ml_ai_fastapi/base/alembic/env.py +96 -0
- solokit/templates/ml_ai_fastapi/base/alembic.ini +114 -0
- solokit/templates/ml_ai_fastapi/base/pyproject.toml.template +91 -0
- solokit/templates/ml_ai_fastapi/base/requirements.txt.template +28 -0
- solokit/templates/ml_ai_fastapi/base/src/__init__.py +5 -0
- solokit/templates/ml_ai_fastapi/base/src/api/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/api/dependencies.py +20 -0
- solokit/templates/ml_ai_fastapi/base/src/api/routes/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/api/routes/example.py +134 -0
- solokit/templates/ml_ai_fastapi/base/src/api/routes/health.py +66 -0
- solokit/templates/ml_ai_fastapi/base/src/core/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/core/config.py +64 -0
- solokit/templates/ml_ai_fastapi/base/src/core/database.py +50 -0
- solokit/templates/ml_ai_fastapi/base/src/main.py +64 -0
- solokit/templates/ml_ai_fastapi/base/src/models/__init__.py +7 -0
- solokit/templates/ml_ai_fastapi/base/src/models/example.py +61 -0
- solokit/templates/ml_ai_fastapi/base/src/services/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/base/src/services/example.py +115 -0
- solokit/templates/ml_ai_fastapi/docker/Dockerfile +59 -0
- solokit/templates/ml_ai_fastapi/docker/docker-compose.prod.yml +112 -0
- solokit/templates/ml_ai_fastapi/docker/docker-compose.yml +77 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/pyproject.toml.tier1.template +112 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/pyrightconfig.json +41 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/pytest.ini +69 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/requirements-dev.txt +17 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/ruff.toml +81 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/conftest.py +72 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/test_main.py +49 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/unit/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-1-essential/tests/unit/test_example.py +113 -0
- solokit/templates/ml_ai_fastapi/tier-2-standard/pyproject.toml.tier2.template +130 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/locustfile.py +99 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/mutmut_config.py +53 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/pyproject.toml.tier3.template +150 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/tests/integration/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/tests/integration/conftest.py +74 -0
- solokit/templates/ml_ai_fastapi/tier-3-comprehensive/tests/integration/test_api.py +131 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/pyproject.toml.tier4.template +162 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/requirements-prod.txt +25 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/api/routes/metrics.py +19 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/core/logging.py +74 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/core/monitoring.py +68 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/core/sentry.py +66 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/middleware/__init__.py +3 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/middleware/logging.py +79 -0
- solokit/templates/ml_ai_fastapi/tier-4-production/src/middleware/tracing.py +60 -0
- solokit/templates/refactor_spec.md +287 -0
- solokit/templates/saas_t3/base/.gitignore +36 -0
- solokit/templates/saas_t3/base/app/api/trpc/[trpc]/route.ts +33 -0
- solokit/templates/saas_t3/base/app/globals.css +27 -0
- solokit/templates/saas_t3/base/app/layout.tsx +23 -0
- solokit/templates/saas_t3/base/app/page.tsx +31 -0
- solokit/templates/saas_t3/base/lib/api.tsx +77 -0
- solokit/templates/saas_t3/base/lib/utils.ts +13 -0
- solokit/templates/saas_t3/base/next.config.ts +7 -0
- solokit/templates/saas_t3/base/package.json.template +38 -0
- solokit/templates/saas_t3/base/postcss.config.mjs +8 -0
- solokit/templates/saas_t3/base/prisma/schema.prisma +20 -0
- solokit/templates/saas_t3/base/server/api/root.ts +19 -0
- solokit/templates/saas_t3/base/server/api/routers/example.ts +28 -0
- solokit/templates/saas_t3/base/server/api/trpc.ts +52 -0
- solokit/templates/saas_t3/base/server/db.ts +17 -0
- solokit/templates/saas_t3/base/tailwind.config.ts +19 -0
- solokit/templates/saas_t3/base/tsconfig.json +27 -0
- solokit/templates/saas_t3/docker/Dockerfile +60 -0
- solokit/templates/saas_t3/docker/docker-compose.prod.yml +59 -0
- solokit/templates/saas_t3/docker/docker-compose.yml +49 -0
- solokit/templates/saas_t3/tier-1-essential/.eslintrc.json +7 -0
- solokit/templates/saas_t3/tier-1-essential/jest.config.ts +17 -0
- solokit/templates/saas_t3/tier-1-essential/jest.setup.ts +1 -0
- solokit/templates/saas_t3/tier-1-essential/package.json.tier1.template +54 -0
- solokit/templates/saas_t3/tier-1-essential/tests/setup.ts +22 -0
- solokit/templates/saas_t3/tier-1-essential/tests/unit/example.test.tsx +24 -0
- solokit/templates/saas_t3/tier-2-standard/package.json.tier2.template +58 -0
- solokit/templates/saas_t3/tier-3-comprehensive/eslint.config.mjs +39 -0
- solokit/templates/saas_t3/tier-3-comprehensive/package.json.tier3.template +74 -0
- solokit/templates/saas_t3/tier-3-comprehensive/playwright.config.ts +66 -0
- solokit/templates/saas_t3/tier-3-comprehensive/stryker.conf.json +34 -0
- solokit/templates/saas_t3/tier-3-comprehensive/tests/e2e/home.spec.ts +41 -0
- solokit/templates/saas_t3/tier-3-comprehensive/tests/integration/api.test.ts +44 -0
- solokit/templates/saas_t3/tier-3-comprehensive/type-coverage.json +12 -0
- solokit/templates/saas_t3/tier-4-production/instrumentation.ts +9 -0
- solokit/templates/saas_t3/tier-4-production/k6/load-test.js +51 -0
- solokit/templates/saas_t3/tier-4-production/next.config.ts +46 -0
- solokit/templates/saas_t3/tier-4-production/package.json.tier4.template +83 -0
- solokit/templates/saas_t3/tier-4-production/sentry.client.config.ts +26 -0
- solokit/templates/saas_t3/tier-4-production/sentry.edge.config.ts +11 -0
- solokit/templates/saas_t3/tier-4-production/sentry.server.config.ts +11 -0
- solokit/templates/saas_t3/tier-4-production/vercel.json +37 -0
- solokit/templates/security_spec.md +287 -0
- solokit/templates/stack-versions.yaml +617 -0
- solokit/templates/status_update.json +6 -0
- solokit/templates/template-registry.json +257 -0
- solokit/templates/work_items.json +11 -0
- solokit/testing/__init__.py +1 -0
- solokit/testing/integration_runner.py +550 -0
- solokit/testing/performance.py +637 -0
- solokit/visualization/__init__.py +1 -0
- solokit/visualization/dependency_graph.py +788 -0
- solokit/work_items/__init__.py +1 -0
- solokit/work_items/creator.py +217 -0
- solokit/work_items/delete.py +264 -0
- solokit/work_items/get_dependencies.py +185 -0
- solokit/work_items/get_dependents.py +113 -0
- solokit/work_items/get_metadata.py +121 -0
- solokit/work_items/get_next_recommendations.py +133 -0
- solokit/work_items/manager.py +235 -0
- solokit/work_items/milestones.py +137 -0
- solokit/work_items/query.py +376 -0
- solokit/work_items/repository.py +267 -0
- solokit/work_items/scheduler.py +184 -0
- solokit/work_items/spec_parser.py +838 -0
- solokit/work_items/spec_validator.py +493 -0
- solokit/work_items/updater.py +157 -0
- solokit/work_items/validator.py +205 -0
- solokit-0.1.1.dist-info/METADATA +640 -0
- solokit-0.1.1.dist-info/RECORD +323 -0
- solokit-0.1.1.dist-info/WHEEL +5 -0
- solokit-0.1.1.dist-info/entry_points.txt +2 -0
- solokit-0.1.1.dist-info/licenses/LICENSE +21 -0
- solokit-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Sync Solokit main repository to claude-plugins marketplace repository.
|
|
4
|
+
|
|
5
|
+
This script automates the process of syncing files from the main Solokit repo
|
|
6
|
+
to the claude-plugins marketplace repo, handling transformations and preserving
|
|
7
|
+
plugin-specific files.
|
|
8
|
+
|
|
9
|
+
Note: This script is automatically executed by GitHub Actions on every push to main.
|
|
10
|
+
See .github/workflows/sync-plugin.yml for the automation workflow.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
python src/solokit/project/sync_plugin.py [--main-repo PATH] [--plugin-repo PATH] [--dry-run]
|
|
14
|
+
|
|
15
|
+
Arguments:
|
|
16
|
+
--main-repo PATH Path to main Solokit repository (default: current directory)
|
|
17
|
+
--plugin-repo PATH Path to claude-plugins repository (required)
|
|
18
|
+
--dry-run Show what would be synced without making changes
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import json
|
|
23
|
+
import shutil
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
from solokit.core.exceptions import (
|
|
28
|
+
ErrorCode,
|
|
29
|
+
FileOperationError,
|
|
30
|
+
ValidationError,
|
|
31
|
+
)
|
|
32
|
+
from solokit.core.exceptions import (
|
|
33
|
+
FileNotFoundError as SolokitFileNotFoundError,
|
|
34
|
+
)
|
|
35
|
+
from solokit.core.logging_config import get_logger
|
|
36
|
+
|
|
37
|
+
logger = get_logger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PluginSyncer:
|
|
41
|
+
"""Handles syncing from main Solokit repo to claude-plugins marketplace."""
|
|
42
|
+
|
|
43
|
+
# Define file mappings: (source_path, dest_path, is_directory)
|
|
44
|
+
FILE_MAPPINGS = [
|
|
45
|
+
("src/solokit", "solokit/src/solokit", True),
|
|
46
|
+
(".claude/commands", "solokit/commands", True),
|
|
47
|
+
("pyproject.toml", "solokit/pyproject.toml", False),
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
# Files to preserve in plugin repo (never overwrite)
|
|
51
|
+
# These files are maintained separately in the plugin marketplace repo
|
|
52
|
+
PRESERVE_FILES = [
|
|
53
|
+
".claude-plugin/plugin.json", # Only update version field
|
|
54
|
+
"README.md", # Plugin has its own marketplace README
|
|
55
|
+
"CONTRIBUTING.md", # Directs contributors to main Solokit repo
|
|
56
|
+
"LICENSE", # Static license file for marketplace
|
|
57
|
+
"SECURITY.md", # Static security policy for marketplace
|
|
58
|
+
".git", # Git metadata
|
|
59
|
+
".github", # Plugin repo's own workflows
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
def __init__(self, main_repo: Path, plugin_repo: Path, dry_run: bool = False):
|
|
63
|
+
"""
|
|
64
|
+
Initialize the syncer.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
main_repo: Path to main Solokit repository
|
|
68
|
+
plugin_repo: Path to claude-plugins repository
|
|
69
|
+
dry_run: If True, show what would be done without making changes
|
|
70
|
+
"""
|
|
71
|
+
self.main_repo = main_repo.resolve()
|
|
72
|
+
self.plugin_repo = plugin_repo.resolve()
|
|
73
|
+
self.dry_run = dry_run
|
|
74
|
+
self.changes: list[str] = []
|
|
75
|
+
|
|
76
|
+
def validate_repos(self) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Validate that both repositories exist and have expected structure.
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
SolokitFileNotFoundError: If repository path doesn't exist
|
|
82
|
+
ValidationError: If repository is missing expected files/directories
|
|
83
|
+
"""
|
|
84
|
+
# Check main repo
|
|
85
|
+
if not self.main_repo.exists():
|
|
86
|
+
raise SolokitFileNotFoundError(
|
|
87
|
+
file_path=str(self.main_repo), file_type="main repository"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
main_markers = ["src/solokit/cli.py", "pyproject.toml", ".claude/commands"]
|
|
91
|
+
for marker in main_markers:
|
|
92
|
+
if not (self.main_repo / marker).exists():
|
|
93
|
+
raise ValidationError(
|
|
94
|
+
message=f"Main repository missing expected file/directory: {marker}",
|
|
95
|
+
code=ErrorCode.CONFIG_VALIDATION_FAILED,
|
|
96
|
+
context={
|
|
97
|
+
"repository": str(self.main_repo),
|
|
98
|
+
"missing_marker": marker,
|
|
99
|
+
"marker_type": "file" if "." in marker else "directory",
|
|
100
|
+
},
|
|
101
|
+
remediation=f"Ensure {self.main_repo} is a valid Solokit repository",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Check plugin repo
|
|
105
|
+
if not self.plugin_repo.exists():
|
|
106
|
+
raise SolokitFileNotFoundError(
|
|
107
|
+
file_path=str(self.plugin_repo), file_type="plugin repository"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
plugin_markers = ["solokit", "solokit/.claude-plugin/plugin.json"]
|
|
111
|
+
for marker in plugin_markers:
|
|
112
|
+
if not (self.plugin_repo / marker).exists():
|
|
113
|
+
raise ValidationError(
|
|
114
|
+
message=f"Plugin repository missing expected file/directory: {marker}",
|
|
115
|
+
code=ErrorCode.CONFIG_VALIDATION_FAILED,
|
|
116
|
+
context={
|
|
117
|
+
"repository": str(self.plugin_repo),
|
|
118
|
+
"missing_marker": marker,
|
|
119
|
+
"marker_type": "file" if "plugin.json" in marker else "directory",
|
|
120
|
+
},
|
|
121
|
+
remediation=f"Ensure {self.plugin_repo} is a valid claude-plugins repository",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
logger.info("Repository validation passed")
|
|
125
|
+
|
|
126
|
+
def get_version_from_main(self) -> str:
|
|
127
|
+
"""
|
|
128
|
+
Extract version from pyproject.toml in main repo.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Version string (e.g., "0.5.7")
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
SolokitFileNotFoundError: If pyproject.toml doesn't exist
|
|
135
|
+
ValidationError: If version field not found in pyproject.toml
|
|
136
|
+
FileOperationError: If file cannot be read
|
|
137
|
+
"""
|
|
138
|
+
pyproject_path = self.main_repo / "pyproject.toml"
|
|
139
|
+
if not pyproject_path.exists():
|
|
140
|
+
raise SolokitFileNotFoundError(
|
|
141
|
+
file_path=str(pyproject_path), file_type="pyproject.toml"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
with open(pyproject_path) as f:
|
|
146
|
+
for line in f:
|
|
147
|
+
if line.strip().startswith("version"):
|
|
148
|
+
# Extract version from line like: version = "0.5.7"
|
|
149
|
+
version = line.split("=")[1].strip().strip('"')
|
|
150
|
+
return version
|
|
151
|
+
except OSError as e:
|
|
152
|
+
raise FileOperationError(
|
|
153
|
+
operation="read", file_path=str(pyproject_path), details=str(e), cause=e
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
raise ValidationError(
|
|
157
|
+
message="Version not found in pyproject.toml",
|
|
158
|
+
code=ErrorCode.MISSING_REQUIRED_FIELD,
|
|
159
|
+
context={"file_path": str(pyproject_path), "expected_field": "version"},
|
|
160
|
+
remediation="Ensure pyproject.toml contains a 'version = \"x.y.z\"' line",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def update_plugin_version(self, version: str) -> None:
|
|
164
|
+
"""
|
|
165
|
+
Update version in plugin.json.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
version: Version string to set (e.g., "0.5.7")
|
|
169
|
+
|
|
170
|
+
Raises:
|
|
171
|
+
FileOperationError: If plugin.json cannot be read, parsed, or written
|
|
172
|
+
"""
|
|
173
|
+
plugin_json_path = self.plugin_repo / "solokit" / ".claude-plugin" / "plugin.json"
|
|
174
|
+
|
|
175
|
+
if self.dry_run:
|
|
176
|
+
logger.info(f"[DRY RUN] Would update plugin.json version to {version}")
|
|
177
|
+
self.changes.append(f"Update plugin.json version to {version}")
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
with open(plugin_json_path) as f:
|
|
182
|
+
plugin_data = json.load(f)
|
|
183
|
+
except json.JSONDecodeError as e:
|
|
184
|
+
raise FileOperationError(
|
|
185
|
+
operation="parse",
|
|
186
|
+
file_path=str(plugin_json_path),
|
|
187
|
+
details=f"Invalid JSON: {e}",
|
|
188
|
+
cause=e,
|
|
189
|
+
)
|
|
190
|
+
except OSError as e:
|
|
191
|
+
raise FileOperationError(
|
|
192
|
+
operation="read", file_path=str(plugin_json_path), details=str(e), cause=e
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
old_version = plugin_data.get("version", "unknown")
|
|
196
|
+
plugin_data["version"] = version
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
with open(plugin_json_path, "w") as f:
|
|
200
|
+
json.dump(plugin_data, f, indent=2)
|
|
201
|
+
f.write("\n") # Add trailing newline
|
|
202
|
+
except OSError as e:
|
|
203
|
+
raise FileOperationError(
|
|
204
|
+
operation="write", file_path=str(plugin_json_path), details=str(e), cause=e
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
change_msg = f"Updated plugin.json version: {old_version} → {version}"
|
|
208
|
+
logger.info(change_msg)
|
|
209
|
+
self.changes.append(change_msg)
|
|
210
|
+
|
|
211
|
+
def sync_file(self, src: Path, dest: Path) -> None:
|
|
212
|
+
"""
|
|
213
|
+
Sync a single file from source to destination.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
src: Source file path
|
|
217
|
+
dest: Destination file path
|
|
218
|
+
|
|
219
|
+
Raises:
|
|
220
|
+
FileOperationError: If file copy operation fails
|
|
221
|
+
"""
|
|
222
|
+
if self.dry_run:
|
|
223
|
+
logger.info(
|
|
224
|
+
f"[DRY RUN] Would copy: {src.relative_to(self.main_repo)} → {dest.relative_to(self.plugin_repo)}"
|
|
225
|
+
)
|
|
226
|
+
self.changes.append(
|
|
227
|
+
f"Copy {src.relative_to(self.main_repo)} → {dest.relative_to(self.plugin_repo)}"
|
|
228
|
+
)
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
# Create parent directory if needed
|
|
233
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
234
|
+
|
|
235
|
+
# Copy file
|
|
236
|
+
shutil.copy2(src, dest)
|
|
237
|
+
except (OSError, shutil.Error) as e:
|
|
238
|
+
raise FileOperationError(
|
|
239
|
+
operation="copy",
|
|
240
|
+
file_path=str(src),
|
|
241
|
+
details=f"Failed to copy to {dest}: {e}",
|
|
242
|
+
cause=e,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
logger.info(
|
|
246
|
+
f"Copied: {src.relative_to(self.main_repo)} → {dest.relative_to(self.plugin_repo)}"
|
|
247
|
+
)
|
|
248
|
+
self.changes.append(f"Copied {src.relative_to(self.main_repo)}")
|
|
249
|
+
|
|
250
|
+
def sync_directory(self, src: Path, dest: Path) -> None:
|
|
251
|
+
"""
|
|
252
|
+
Sync a directory from source to destination.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
src: Source directory path
|
|
256
|
+
dest: Destination directory path
|
|
257
|
+
|
|
258
|
+
Raises:
|
|
259
|
+
FileOperationError: If directory sync operation fails
|
|
260
|
+
"""
|
|
261
|
+
if self.dry_run:
|
|
262
|
+
logger.info(
|
|
263
|
+
f"[DRY RUN] Would sync directory: {src.relative_to(self.main_repo)} → {dest.relative_to(self.plugin_repo)}"
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Count files to sync
|
|
267
|
+
file_count = sum(1 for _ in src.rglob("*") if _.is_file())
|
|
268
|
+
self.changes.append(
|
|
269
|
+
f"Sync directory {src.relative_to(self.main_repo)} ({file_count} files)"
|
|
270
|
+
)
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
# Remove existing destination if it exists
|
|
275
|
+
if dest.exists():
|
|
276
|
+
shutil.rmtree(dest)
|
|
277
|
+
|
|
278
|
+
# Copy entire directory tree
|
|
279
|
+
shutil.copytree(src, dest)
|
|
280
|
+
except (OSError, shutil.Error) as e:
|
|
281
|
+
raise FileOperationError(
|
|
282
|
+
operation="sync_directory",
|
|
283
|
+
file_path=str(src),
|
|
284
|
+
details=f"Failed to sync directory to {dest}: {e}",
|
|
285
|
+
cause=e,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
# Count files synced
|
|
289
|
+
file_count = sum(1 for _ in dest.rglob("*") if _.is_file())
|
|
290
|
+
logger.info(f"Synced directory: {src.relative_to(self.main_repo)} ({file_count} files)")
|
|
291
|
+
self.changes.append(
|
|
292
|
+
f"Synced directory {src.relative_to(self.main_repo)} ({file_count} files)"
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def sync_all_files(self) -> None:
|
|
296
|
+
"""
|
|
297
|
+
Sync all files according to FILE_MAPPINGS.
|
|
298
|
+
|
|
299
|
+
Raises:
|
|
300
|
+
FileOperationError: If any file/directory sync operation fails
|
|
301
|
+
"""
|
|
302
|
+
logger.info("Syncing files...")
|
|
303
|
+
|
|
304
|
+
for src_rel, dest_rel, is_directory in self.FILE_MAPPINGS:
|
|
305
|
+
src = self.main_repo / src_rel
|
|
306
|
+
dest = self.plugin_repo / dest_rel
|
|
307
|
+
|
|
308
|
+
if not src.exists():
|
|
309
|
+
logger.warning(f"Source not found (skipping): {src_rel}")
|
|
310
|
+
continue
|
|
311
|
+
|
|
312
|
+
if is_directory:
|
|
313
|
+
self.sync_directory(src, dest)
|
|
314
|
+
else:
|
|
315
|
+
self.sync_file(src, dest)
|
|
316
|
+
|
|
317
|
+
def generate_summary(self) -> str:
|
|
318
|
+
"""Generate a summary of all changes made."""
|
|
319
|
+
summary_lines = [
|
|
320
|
+
"# Plugin Sync Summary",
|
|
321
|
+
"",
|
|
322
|
+
f"Main repo: {self.main_repo}",
|
|
323
|
+
f"Plugin repo: {self.plugin_repo}",
|
|
324
|
+
f"Dry run: {self.dry_run}",
|
|
325
|
+
"",
|
|
326
|
+
"## Changes:",
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
if self.changes:
|
|
330
|
+
for change in self.changes:
|
|
331
|
+
summary_lines.append(f"- {change}")
|
|
332
|
+
else:
|
|
333
|
+
summary_lines.append("- No changes made")
|
|
334
|
+
|
|
335
|
+
return "\n".join(summary_lines)
|
|
336
|
+
|
|
337
|
+
def sync(self) -> None:
|
|
338
|
+
"""
|
|
339
|
+
Execute the sync process.
|
|
340
|
+
|
|
341
|
+
Raises:
|
|
342
|
+
SolokitFileNotFoundError: If repository paths don't exist
|
|
343
|
+
ValidationError: If repositories are missing expected files
|
|
344
|
+
FileOperationError: If file operations fail
|
|
345
|
+
"""
|
|
346
|
+
logger.info("Starting plugin sync...")
|
|
347
|
+
|
|
348
|
+
# Validate repositories
|
|
349
|
+
self.validate_repos()
|
|
350
|
+
|
|
351
|
+
# Get version from main repo
|
|
352
|
+
version = self.get_version_from_main()
|
|
353
|
+
logger.info(f"Main repo version: {version}")
|
|
354
|
+
|
|
355
|
+
# Update plugin version
|
|
356
|
+
self.update_plugin_version(version)
|
|
357
|
+
|
|
358
|
+
# Sync all files
|
|
359
|
+
self.sync_all_files()
|
|
360
|
+
|
|
361
|
+
# Print summary
|
|
362
|
+
logger.info("=" * 60)
|
|
363
|
+
logger.info(self.generate_summary())
|
|
364
|
+
logger.info("=" * 60)
|
|
365
|
+
|
|
366
|
+
if self.dry_run:
|
|
367
|
+
logger.warning("This was a DRY RUN - no changes were made")
|
|
368
|
+
else:
|
|
369
|
+
logger.info("Sync completed successfully!")
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def main() -> None:
|
|
373
|
+
"""
|
|
374
|
+
Main entry point.
|
|
375
|
+
|
|
376
|
+
Exit codes:
|
|
377
|
+
0: Success
|
|
378
|
+
1: General error
|
|
379
|
+
2: Validation error
|
|
380
|
+
3: File not found
|
|
381
|
+
4: Configuration error
|
|
382
|
+
5: System/file operation error
|
|
383
|
+
"""
|
|
384
|
+
parser = argparse.ArgumentParser(
|
|
385
|
+
description="Sync Solokit main repository to claude-plugins marketplace repository"
|
|
386
|
+
)
|
|
387
|
+
parser.add_argument(
|
|
388
|
+
"--main-repo",
|
|
389
|
+
type=Path,
|
|
390
|
+
default=Path.cwd(),
|
|
391
|
+
help="Path to main Solokit repository (default: current directory)",
|
|
392
|
+
)
|
|
393
|
+
parser.add_argument(
|
|
394
|
+
"--plugin-repo",
|
|
395
|
+
type=Path,
|
|
396
|
+
required=True,
|
|
397
|
+
help="Path to claude-plugins repository",
|
|
398
|
+
)
|
|
399
|
+
parser.add_argument(
|
|
400
|
+
"--dry-run",
|
|
401
|
+
action="store_true",
|
|
402
|
+
help="Show what would be synced without making changes",
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
args = parser.parse_args()
|
|
406
|
+
|
|
407
|
+
# Create syncer and run
|
|
408
|
+
syncer = PluginSyncer(
|
|
409
|
+
main_repo=args.main_repo,
|
|
410
|
+
plugin_repo=args.plugin_repo,
|
|
411
|
+
dry_run=args.dry_run,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
try:
|
|
415
|
+
syncer.sync()
|
|
416
|
+
sys.exit(0)
|
|
417
|
+
except SolokitFileNotFoundError as e:
|
|
418
|
+
logger.error(f"File not found: {e.message}")
|
|
419
|
+
if e.remediation:
|
|
420
|
+
logger.error(f"Remediation: {e.remediation}")
|
|
421
|
+
sys.exit(e.exit_code)
|
|
422
|
+
except ValidationError as e:
|
|
423
|
+
logger.error(f"Validation error: {e.message}")
|
|
424
|
+
if e.remediation:
|
|
425
|
+
logger.error(f"Remediation: {e.remediation}")
|
|
426
|
+
sys.exit(e.exit_code)
|
|
427
|
+
except FileOperationError as e:
|
|
428
|
+
logger.error(f"File operation error: {e.message}")
|
|
429
|
+
if e.remediation:
|
|
430
|
+
logger.error(f"Remediation: {e.remediation}")
|
|
431
|
+
sys.exit(e.exit_code)
|
|
432
|
+
except Exception as e:
|
|
433
|
+
logger.error(f"Unexpected error: {e}")
|
|
434
|
+
sys.exit(1)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
if __name__ == "__main__":
|
|
438
|
+
main()
|