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
solokit/project/init.py
ADDED
|
@@ -0,0 +1,1162 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Deterministic Solokit initialization - transforms any project into working Solokit project.
|
|
4
|
+
Philosophy: Don't check and warn - CREATE and FIX.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import shutil
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from solokit.core.command_runner import CommandRunner
|
|
16
|
+
from solokit.core.constants import GIT_QUICK_TIMEOUT, GIT_STANDARD_TIMEOUT
|
|
17
|
+
from solokit.core.exceptions import (
|
|
18
|
+
DirectoryNotEmptyError,
|
|
19
|
+
ErrorCode,
|
|
20
|
+
FileOperationError,
|
|
21
|
+
GitError,
|
|
22
|
+
NotAGitRepoError,
|
|
23
|
+
TemplateNotFoundError,
|
|
24
|
+
ValidationError,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ============================================================================
|
|
31
|
+
# LEGACY INIT FUNCTIONS
|
|
32
|
+
# These functions are kept for backward compatibility with init_project()
|
|
33
|
+
# New template-based init uses modules in src/solokit/init/ instead
|
|
34
|
+
# ============================================================================
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def check_or_init_git(project_root: Path | None = None) -> bool:
|
|
38
|
+
"""
|
|
39
|
+
Check if git is initialized, if not initialize it.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
project_root: Root directory of the project. Defaults to current working directory.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
True if git repository exists or was successfully initialized.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
GitError: If git initialization or branch configuration fails.
|
|
49
|
+
|
|
50
|
+
Note:
|
|
51
|
+
This function prints success messages but does not print errors - it raises exceptions instead.
|
|
52
|
+
"""
|
|
53
|
+
if project_root is None:
|
|
54
|
+
project_root = Path.cwd()
|
|
55
|
+
|
|
56
|
+
git_dir = project_root / ".git"
|
|
57
|
+
|
|
58
|
+
if git_dir.exists():
|
|
59
|
+
logger.info("Git repository already initialized")
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
runner = CommandRunner(default_timeout=GIT_QUICK_TIMEOUT, working_dir=project_root)
|
|
63
|
+
|
|
64
|
+
# Initialize git
|
|
65
|
+
result = runner.run(["git", "init"], check=True)
|
|
66
|
+
if not result.success:
|
|
67
|
+
raise GitError(
|
|
68
|
+
message="Failed to initialize git repository",
|
|
69
|
+
code=ErrorCode.GIT_COMMAND_FAILED,
|
|
70
|
+
context={"stderr": result.stderr, "command": "git init"},
|
|
71
|
+
remediation="Ensure git is installed and you have write permissions in the directory",
|
|
72
|
+
)
|
|
73
|
+
logger.info("Initialized git repository")
|
|
74
|
+
|
|
75
|
+
# Set default branch to main (modern convention)
|
|
76
|
+
result = runner.run(["git", "branch", "-m", "main"], check=True)
|
|
77
|
+
if not result.success:
|
|
78
|
+
raise GitError(
|
|
79
|
+
message="Failed to set default branch to 'main'",
|
|
80
|
+
code=ErrorCode.GIT_COMMAND_FAILED,
|
|
81
|
+
context={"stderr": result.stderr, "command": "git branch -m main"},
|
|
82
|
+
remediation="Manually run 'git branch -m main' in the repository",
|
|
83
|
+
)
|
|
84
|
+
logger.info("Set default branch to 'main'")
|
|
85
|
+
|
|
86
|
+
return True
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def install_git_hooks(project_root: Path | None = None) -> bool:
|
|
90
|
+
"""
|
|
91
|
+
Install git hooks from templates.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
project_root: Root directory of the project. Defaults to current working directory.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
True if git hooks were successfully installed.
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
NotAGitRepoError: If .git/hooks directory doesn't exist (git not initialized).
|
|
101
|
+
TemplateNotFoundError: If hook template file is not found.
|
|
102
|
+
FileOperationError: If hook installation or permission setting fails.
|
|
103
|
+
"""
|
|
104
|
+
if project_root is None:
|
|
105
|
+
project_root = Path.cwd()
|
|
106
|
+
|
|
107
|
+
git_hooks_dir = project_root / ".git" / "hooks"
|
|
108
|
+
|
|
109
|
+
# Check if .git/hooks exists
|
|
110
|
+
if not git_hooks_dir.exists():
|
|
111
|
+
raise NotAGitRepoError(str(project_root))
|
|
112
|
+
|
|
113
|
+
# Get template directory
|
|
114
|
+
template_dir = Path(__file__).parent.parent / "templates" / "git-hooks"
|
|
115
|
+
|
|
116
|
+
# Install prepare-commit-msg hook
|
|
117
|
+
hook_template = template_dir / "prepare-commit-msg"
|
|
118
|
+
hook_dest = git_hooks_dir / "prepare-commit-msg"
|
|
119
|
+
|
|
120
|
+
if not hook_template.exists():
|
|
121
|
+
raise TemplateNotFoundError(
|
|
122
|
+
template_name="prepare-commit-msg", template_path=str(template_dir)
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
shutil.copy(hook_template, hook_dest)
|
|
127
|
+
# Make executable (chmod +x)
|
|
128
|
+
import stat
|
|
129
|
+
|
|
130
|
+
hook_dest.chmod(hook_dest.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
131
|
+
logger.info("Installed git prepare-commit-msg hook")
|
|
132
|
+
return True
|
|
133
|
+
except Exception as e:
|
|
134
|
+
raise FileOperationError(
|
|
135
|
+
operation="install",
|
|
136
|
+
file_path=str(hook_dest),
|
|
137
|
+
details=f"Failed to copy or set permissions: {str(e)}",
|
|
138
|
+
cause=e,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def detect_project_type() -> str:
|
|
143
|
+
"""Detect project type from existing files."""
|
|
144
|
+
if Path("package.json").exists():
|
|
145
|
+
if Path("tsconfig.json").exists():
|
|
146
|
+
return "typescript"
|
|
147
|
+
return "javascript"
|
|
148
|
+
elif Path("pyproject.toml").exists() or Path("setup.py").exists():
|
|
149
|
+
return "python"
|
|
150
|
+
else:
|
|
151
|
+
# No project files found - ask user
|
|
152
|
+
logger.info("\nNo project files detected. What type of project is this?")
|
|
153
|
+
logger.info("1. TypeScript")
|
|
154
|
+
logger.info("2. JavaScript")
|
|
155
|
+
logger.info("3. Python")
|
|
156
|
+
|
|
157
|
+
if not sys.stdin.isatty():
|
|
158
|
+
# Non-interactive mode - default to TypeScript
|
|
159
|
+
logger.info("Non-interactive mode: defaulting to TypeScript")
|
|
160
|
+
return "typescript"
|
|
161
|
+
|
|
162
|
+
choice = input("Enter choice (1-3): ").strip()
|
|
163
|
+
return {"1": "typescript", "2": "javascript", "3": "python"}.get(choice, "typescript")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def ensure_package_manager_file(project_type: str) -> None:
|
|
167
|
+
"""
|
|
168
|
+
Create or update package manager file with required dependencies.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
project_type: Type of project (typescript, javascript, or python).
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
ValidationError: If project_type is invalid.
|
|
175
|
+
TemplateNotFoundError: If required template file is not found.
|
|
176
|
+
FileOperationError: If file read/write operations fail.
|
|
177
|
+
|
|
178
|
+
Note:
|
|
179
|
+
Prints informational messages about created/updated files but raises exceptions on errors.
|
|
180
|
+
"""
|
|
181
|
+
if project_type not in ["typescript", "javascript", "python"]:
|
|
182
|
+
raise ValidationError(
|
|
183
|
+
message=f"Invalid project type: {project_type}",
|
|
184
|
+
code=ErrorCode.INVALID_WORK_ITEM_TYPE,
|
|
185
|
+
context={"project_type": project_type},
|
|
186
|
+
remediation="Use 'typescript', 'javascript', or 'python'",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
template_dir = Path(__file__).parent.parent / "templates"
|
|
190
|
+
|
|
191
|
+
if project_type in ["typescript", "javascript"]:
|
|
192
|
+
package_json = Path("package.json")
|
|
193
|
+
|
|
194
|
+
if not package_json.exists():
|
|
195
|
+
logger.info("Creating package.json...")
|
|
196
|
+
# Get project name from directory
|
|
197
|
+
project_name = Path.cwd().name
|
|
198
|
+
project_desc = f"A {project_type} project with Session-Driven Development"
|
|
199
|
+
|
|
200
|
+
# Load template and replace placeholders
|
|
201
|
+
template_path = template_dir / "package.json.template"
|
|
202
|
+
if not template_path.exists():
|
|
203
|
+
raise TemplateNotFoundError(
|
|
204
|
+
template_name="package.json.template", template_path=str(template_dir)
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
template_content = template_path.read_text()
|
|
209
|
+
content = template_content.replace("{project_name}", project_name)
|
|
210
|
+
content = content.replace("{project_description}", project_desc)
|
|
211
|
+
package_json.write_text(content)
|
|
212
|
+
logger.info(f"Created package.json for {project_name}")
|
|
213
|
+
except Exception as e:
|
|
214
|
+
raise FileOperationError(
|
|
215
|
+
operation="create",
|
|
216
|
+
file_path=str(package_json),
|
|
217
|
+
details=f"Failed to create package.json from template: {str(e)}",
|
|
218
|
+
cause=e,
|
|
219
|
+
)
|
|
220
|
+
else:
|
|
221
|
+
logger.info("Found package.json")
|
|
222
|
+
# Ensure required scripts and devDependencies exist
|
|
223
|
+
try:
|
|
224
|
+
with open(package_json) as f:
|
|
225
|
+
data = json.load(f)
|
|
226
|
+
except json.JSONDecodeError as e:
|
|
227
|
+
raise FileOperationError(
|
|
228
|
+
operation="parse",
|
|
229
|
+
file_path=str(package_json),
|
|
230
|
+
details=f"Invalid JSON in package.json: {str(e)}",
|
|
231
|
+
cause=e,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Ensure scripts
|
|
235
|
+
required_scripts = {
|
|
236
|
+
"test": "jest",
|
|
237
|
+
"lint": "eslint src tests --ext .ts,.tsx,.js,.jsx"
|
|
238
|
+
if project_type == "typescript"
|
|
239
|
+
else "eslint src tests --ext .js,.jsx",
|
|
240
|
+
"format": 'prettier --write "src/**/*" "tests/**/*"',
|
|
241
|
+
}
|
|
242
|
+
if project_type == "typescript":
|
|
243
|
+
required_scripts["build"] = "tsc"
|
|
244
|
+
|
|
245
|
+
if "scripts" not in data:
|
|
246
|
+
data["scripts"] = {}
|
|
247
|
+
|
|
248
|
+
scripts_modified = False
|
|
249
|
+
for script, cmd in required_scripts.items():
|
|
250
|
+
if script not in data["scripts"]:
|
|
251
|
+
data["scripts"][script] = cmd
|
|
252
|
+
logger.info(f"Added script: {script}")
|
|
253
|
+
scripts_modified = True
|
|
254
|
+
|
|
255
|
+
# Ensure devDependencies
|
|
256
|
+
if "devDependencies" not in data:
|
|
257
|
+
data["devDependencies"] = {}
|
|
258
|
+
|
|
259
|
+
# Common dependencies for all JS/TS projects
|
|
260
|
+
required_deps = {
|
|
261
|
+
"jest": "^29.5.0",
|
|
262
|
+
"prettier": "^3.0.0",
|
|
263
|
+
"eslint": "^8.40.0",
|
|
264
|
+
"@types/jest": "^29.5.0",
|
|
265
|
+
"@types/node": "^20.0.0",
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
# TypeScript-specific dependencies
|
|
269
|
+
if project_type == "typescript":
|
|
270
|
+
required_deps.update(
|
|
271
|
+
{
|
|
272
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
273
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
274
|
+
"ts-jest": "^29.1.0",
|
|
275
|
+
"typescript": "^5.0.0",
|
|
276
|
+
}
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
deps_modified = False
|
|
280
|
+
for pkg, version in required_deps.items():
|
|
281
|
+
if pkg not in data["devDependencies"]:
|
|
282
|
+
data["devDependencies"][pkg] = version
|
|
283
|
+
logger.info(f"Added devDependency: {pkg}")
|
|
284
|
+
deps_modified = True
|
|
285
|
+
|
|
286
|
+
# Save back only if modified
|
|
287
|
+
if scripts_modified or deps_modified:
|
|
288
|
+
try:
|
|
289
|
+
with open(package_json, "w") as f:
|
|
290
|
+
json.dump(data, f, indent=2)
|
|
291
|
+
if deps_modified:
|
|
292
|
+
logger.info("Run 'npm install' to install new dependencies")
|
|
293
|
+
except Exception as e:
|
|
294
|
+
raise FileOperationError(
|
|
295
|
+
operation="write",
|
|
296
|
+
file_path=str(package_json),
|
|
297
|
+
details=f"Failed to save package.json: {str(e)}",
|
|
298
|
+
cause=e,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
elif project_type == "python":
|
|
302
|
+
pyproject = Path("pyproject.toml")
|
|
303
|
+
|
|
304
|
+
if not pyproject.exists():
|
|
305
|
+
logger.info("Creating pyproject.toml...")
|
|
306
|
+
project_name = Path.cwd().name.replace("-", "_")
|
|
307
|
+
project_desc = "A Python project with Session-Driven Development"
|
|
308
|
+
|
|
309
|
+
template_path = template_dir / "pyproject.toml.template"
|
|
310
|
+
if not template_path.exists():
|
|
311
|
+
raise TemplateNotFoundError(
|
|
312
|
+
template_name="pyproject.toml.template", template_path=str(template_dir)
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
template_content = template_path.read_text()
|
|
317
|
+
content = template_content.replace("{project_name}", project_name)
|
|
318
|
+
content = template_content.replace("{project_description}", project_desc)
|
|
319
|
+
pyproject.write_text(content)
|
|
320
|
+
logger.info(f"Created pyproject.toml for {project_name}")
|
|
321
|
+
except Exception as e:
|
|
322
|
+
raise FileOperationError(
|
|
323
|
+
operation="create",
|
|
324
|
+
file_path=str(pyproject),
|
|
325
|
+
details=f"Failed to create pyproject.toml from template: {str(e)}",
|
|
326
|
+
cause=e,
|
|
327
|
+
)
|
|
328
|
+
else:
|
|
329
|
+
logger.info("Found pyproject.toml")
|
|
330
|
+
# Check if it has dev dependencies section
|
|
331
|
+
try:
|
|
332
|
+
content = pyproject.read_text()
|
|
333
|
+
if "[project.optional-dependencies]" not in content and "dev" not in content:
|
|
334
|
+
logger.info(
|
|
335
|
+
"Note: Add [project.optional-dependencies] section with pytest, pytest-cov, ruff"
|
|
336
|
+
)
|
|
337
|
+
logger.info("Or install manually: pip install pytest pytest-cov ruff")
|
|
338
|
+
except Exception as e:
|
|
339
|
+
raise FileOperationError(
|
|
340
|
+
operation="read",
|
|
341
|
+
file_path=str(pyproject),
|
|
342
|
+
details=f"Failed to read pyproject.toml: {str(e)}",
|
|
343
|
+
cause=e,
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def ensure_config_files(project_type: str) -> None:
|
|
348
|
+
"""
|
|
349
|
+
Create all required config files from templates.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
project_type: Type of project (typescript, javascript, or python).
|
|
353
|
+
|
|
354
|
+
Raises:
|
|
355
|
+
FileOperationError: If file copy operation fails.
|
|
356
|
+
|
|
357
|
+
Note:
|
|
358
|
+
Prints informational messages about created/found files.
|
|
359
|
+
Missing templates are silently skipped (no error raised).
|
|
360
|
+
"""
|
|
361
|
+
template_dir = Path(__file__).parent.parent / "templates"
|
|
362
|
+
|
|
363
|
+
# Common configs
|
|
364
|
+
configs_to_create = [
|
|
365
|
+
("CHANGELOG.md", "CHANGELOG.md"),
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
if project_type in ["typescript", "javascript"]:
|
|
369
|
+
configs_to_create.extend(
|
|
370
|
+
[
|
|
371
|
+
(".eslintrc.json", ".eslintrc.json"),
|
|
372
|
+
(".prettierrc.json", ".prettierrc.json"),
|
|
373
|
+
(".prettierignore", ".prettierignore"),
|
|
374
|
+
]
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# Use correct jest config based on project type
|
|
378
|
+
if project_type == "typescript":
|
|
379
|
+
configs_to_create.append(("jest.config.js", "jest.config.js"))
|
|
380
|
+
configs_to_create.append(("tsconfig.json", "tsconfig.json"))
|
|
381
|
+
else: # javascript
|
|
382
|
+
configs_to_create.append(("jest.config.js.javascript", "jest.config.js"))
|
|
383
|
+
|
|
384
|
+
for template_name, dest_name in configs_to_create:
|
|
385
|
+
dest_path = Path(dest_name)
|
|
386
|
+
if not dest_path.exists():
|
|
387
|
+
template_path = template_dir / template_name
|
|
388
|
+
if template_path.exists():
|
|
389
|
+
try:
|
|
390
|
+
shutil.copy(template_path, dest_path)
|
|
391
|
+
logger.info(f"Created {dest_name}")
|
|
392
|
+
except Exception as e:
|
|
393
|
+
raise FileOperationError(
|
|
394
|
+
operation="copy",
|
|
395
|
+
file_path=str(dest_path),
|
|
396
|
+
details=f"Failed to copy config file: {str(e)}",
|
|
397
|
+
cause=e,
|
|
398
|
+
)
|
|
399
|
+
else:
|
|
400
|
+
logger.info(f"Found {dest_name}")
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def install_dependencies(project_type: str) -> None:
|
|
404
|
+
"""
|
|
405
|
+
Install project dependencies.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
project_type: Type of project (typescript, javascript, or python).
|
|
409
|
+
|
|
410
|
+
Raises:
|
|
411
|
+
CommandExecutionError: If dependency installation command fails critically.
|
|
412
|
+
|
|
413
|
+
Note:
|
|
414
|
+
Prints informational messages. Some failures are logged as warnings rather than exceptions
|
|
415
|
+
to allow the initialization to continue (user can manually install dependencies later).
|
|
416
|
+
"""
|
|
417
|
+
if project_type in ["typescript", "javascript"]:
|
|
418
|
+
# Always run npm install to ensure new devDependencies are installed
|
|
419
|
+
logger.info("\nInstalling npm dependencies...")
|
|
420
|
+
try:
|
|
421
|
+
runner = CommandRunner(default_timeout=300)
|
|
422
|
+
result = runner.run(["npm", "install"], check=True)
|
|
423
|
+
if result.success:
|
|
424
|
+
logger.info("Dependencies installed")
|
|
425
|
+
else:
|
|
426
|
+
logger.warning("npm install failed - you may need to run it manually")
|
|
427
|
+
except Exception as e:
|
|
428
|
+
logger.warning(f"npm install failed: {e} - you may need to run it manually")
|
|
429
|
+
|
|
430
|
+
elif project_type == "python":
|
|
431
|
+
# Check if we're in a venv, if not create one
|
|
432
|
+
if not (Path("venv").exists() or Path(".venv").exists()):
|
|
433
|
+
logger.info("\nCreating Python virtual environment...")
|
|
434
|
+
try:
|
|
435
|
+
runner = CommandRunner(default_timeout=60)
|
|
436
|
+
result = runner.run([sys.executable, "-m", "venv", "venv"], check=True)
|
|
437
|
+
if result.success:
|
|
438
|
+
logger.info("Created venv/")
|
|
439
|
+
logger.info(
|
|
440
|
+
"Activate with: source venv/bin/activate (Unix) or venv\\Scripts\\activate (Windows)"
|
|
441
|
+
)
|
|
442
|
+
else:
|
|
443
|
+
logger.warning("venv creation failed")
|
|
444
|
+
return
|
|
445
|
+
except Exception as e:
|
|
446
|
+
logger.warning(f"venv creation failed: {e}")
|
|
447
|
+
return
|
|
448
|
+
|
|
449
|
+
# Try to install dev dependencies
|
|
450
|
+
logger.info("\nInstalling Python dependencies...")
|
|
451
|
+
pip_cmd = "venv/bin/pip" if Path("venv").exists() else ".venv/bin/pip"
|
|
452
|
+
if Path(pip_cmd).exists():
|
|
453
|
+
try:
|
|
454
|
+
runner = CommandRunner(default_timeout=300)
|
|
455
|
+
result = runner.run([pip_cmd, "install", "-e", ".[dev]"], check=True)
|
|
456
|
+
if result.success:
|
|
457
|
+
logger.info("Dependencies installed")
|
|
458
|
+
else:
|
|
459
|
+
logger.warning(
|
|
460
|
+
"pip install failed - you may need to activate venv and install manually"
|
|
461
|
+
)
|
|
462
|
+
except Exception as e:
|
|
463
|
+
logger.warning(
|
|
464
|
+
f"pip install failed: {e} - you may need to activate venv and install manually"
|
|
465
|
+
)
|
|
466
|
+
else:
|
|
467
|
+
logger.warning("Please activate virtual environment and run: pip install -e .[dev]")
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def create_smoke_tests(project_type: str) -> None:
|
|
471
|
+
"""
|
|
472
|
+
Create initial smoke tests that validate Solokit setup.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
project_type: Type of project (typescript, javascript, or python).
|
|
476
|
+
|
|
477
|
+
Raises:
|
|
478
|
+
FileOperationError: If test directory creation or file copy fails.
|
|
479
|
+
|
|
480
|
+
Note:
|
|
481
|
+
Prints informational messages about created/found test files.
|
|
482
|
+
Missing templates are silently skipped (no error raised).
|
|
483
|
+
"""
|
|
484
|
+
template_dir = Path(__file__).parent.parent / "templates" / "tests"
|
|
485
|
+
test_dir = Path("tests")
|
|
486
|
+
|
|
487
|
+
try:
|
|
488
|
+
test_dir.mkdir(exist_ok=True)
|
|
489
|
+
except Exception as e:
|
|
490
|
+
raise FileOperationError(
|
|
491
|
+
operation="create",
|
|
492
|
+
file_path=str(test_dir),
|
|
493
|
+
details=f"Failed to create tests directory: {str(e)}",
|
|
494
|
+
cause=e,
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
if project_type == "typescript":
|
|
498
|
+
test_file = test_dir / "solokit-setup.test.ts"
|
|
499
|
+
template_name = "solokit-setup.test.ts"
|
|
500
|
+
if not test_file.exists():
|
|
501
|
+
template_file = template_dir / template_name
|
|
502
|
+
if template_file.exists():
|
|
503
|
+
try:
|
|
504
|
+
shutil.copy(template_file, test_file)
|
|
505
|
+
logger.info(f"Created smoke tests: {test_file}")
|
|
506
|
+
except Exception as e:
|
|
507
|
+
raise FileOperationError(
|
|
508
|
+
operation="copy",
|
|
509
|
+
file_path=str(test_file),
|
|
510
|
+
details=f"Failed to copy smoke test template: {str(e)}",
|
|
511
|
+
cause=e,
|
|
512
|
+
)
|
|
513
|
+
else:
|
|
514
|
+
logger.info(f"Found {test_file}")
|
|
515
|
+
|
|
516
|
+
elif project_type == "javascript":
|
|
517
|
+
test_file = test_dir / "solokit-setup.test.js"
|
|
518
|
+
template_name = "solokit-setup.test.js"
|
|
519
|
+
if not test_file.exists():
|
|
520
|
+
template_file = template_dir / template_name
|
|
521
|
+
if template_file.exists():
|
|
522
|
+
try:
|
|
523
|
+
shutil.copy(template_file, test_file)
|
|
524
|
+
logger.info(f"Created smoke tests: {test_file}")
|
|
525
|
+
except Exception as e:
|
|
526
|
+
raise FileOperationError(
|
|
527
|
+
operation="copy",
|
|
528
|
+
file_path=str(test_file),
|
|
529
|
+
details=f"Failed to copy smoke test template: {str(e)}",
|
|
530
|
+
cause=e,
|
|
531
|
+
)
|
|
532
|
+
else:
|
|
533
|
+
logger.info(f"Found {test_file}")
|
|
534
|
+
|
|
535
|
+
elif project_type == "python":
|
|
536
|
+
test_file = test_dir / "test_sdd_setup.py"
|
|
537
|
+
if not test_file.exists():
|
|
538
|
+
template_file = template_dir / "test_sdd_setup.py"
|
|
539
|
+
if template_file.exists():
|
|
540
|
+
try:
|
|
541
|
+
shutil.copy(template_file, test_file)
|
|
542
|
+
logger.info(f"Created smoke tests: {test_file}")
|
|
543
|
+
except Exception as e:
|
|
544
|
+
raise FileOperationError(
|
|
545
|
+
operation="copy",
|
|
546
|
+
file_path=str(test_file),
|
|
547
|
+
details=f"Failed to copy smoke test template: {str(e)}",
|
|
548
|
+
cause=e,
|
|
549
|
+
)
|
|
550
|
+
else:
|
|
551
|
+
logger.info(f"Found {test_file}")
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def create_session_structure() -> None:
|
|
555
|
+
"""
|
|
556
|
+
Create .session directory structure.
|
|
557
|
+
|
|
558
|
+
Raises:
|
|
559
|
+
FileOperationError: If directory creation fails.
|
|
560
|
+
|
|
561
|
+
Note:
|
|
562
|
+
Prints informational messages about created directories.
|
|
563
|
+
"""
|
|
564
|
+
session_dir = Path(".session")
|
|
565
|
+
|
|
566
|
+
logger.info("\nCreating .session/ structure...")
|
|
567
|
+
|
|
568
|
+
# Create directories
|
|
569
|
+
try:
|
|
570
|
+
(session_dir / "tracking").mkdir(parents=True)
|
|
571
|
+
(session_dir / "briefings").mkdir(parents=True)
|
|
572
|
+
(session_dir / "history").mkdir(parents=True)
|
|
573
|
+
(session_dir / "specs").mkdir(parents=True)
|
|
574
|
+
except Exception as e:
|
|
575
|
+
raise FileOperationError(
|
|
576
|
+
operation="create",
|
|
577
|
+
file_path=str(session_dir),
|
|
578
|
+
details=f"Failed to create .session directory structure: {str(e)}",
|
|
579
|
+
cause=e,
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
logger.info("Created .session/tracking/")
|
|
583
|
+
logger.info("Created .session/briefings/")
|
|
584
|
+
logger.info("Created .session/history/")
|
|
585
|
+
logger.info("Created .session/specs/")
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def initialize_tracking_files() -> None:
|
|
589
|
+
"""
|
|
590
|
+
Initialize tracking files from templates.
|
|
591
|
+
|
|
592
|
+
Raises:
|
|
593
|
+
FileOperationError: If file operations fail.
|
|
594
|
+
TemplateNotFoundError: If required template files are missing.
|
|
595
|
+
|
|
596
|
+
Note:
|
|
597
|
+
Creates tracking files, config.json, and schema files in .session directory.
|
|
598
|
+
"""
|
|
599
|
+
session_dir = Path(".session")
|
|
600
|
+
template_dir = Path(__file__).parent.parent / "templates"
|
|
601
|
+
|
|
602
|
+
logger.info("\nInitializing tracking files...")
|
|
603
|
+
|
|
604
|
+
# Copy templates
|
|
605
|
+
tracking_files = [
|
|
606
|
+
("work_items.json", "tracking/work_items.json"),
|
|
607
|
+
("learnings.json", "tracking/learnings.json"),
|
|
608
|
+
("status_update.json", "tracking/status_update.json"),
|
|
609
|
+
]
|
|
610
|
+
|
|
611
|
+
for src, dst in tracking_files:
|
|
612
|
+
src_path = template_dir / src
|
|
613
|
+
dst_path = session_dir / dst
|
|
614
|
+
if src_path.exists():
|
|
615
|
+
try:
|
|
616
|
+
shutil.copy(src_path, dst_path)
|
|
617
|
+
logger.info(f"Created {dst}")
|
|
618
|
+
except Exception as e:
|
|
619
|
+
raise FileOperationError(
|
|
620
|
+
operation="copy",
|
|
621
|
+
file_path=str(dst_path),
|
|
622
|
+
details=f"Failed to copy tracking file template: {str(e)}",
|
|
623
|
+
cause=e,
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
# Create empty files for stack and tree tracking
|
|
627
|
+
try:
|
|
628
|
+
(session_dir / "tracking" / "stack_updates.json").write_text(
|
|
629
|
+
json.dumps({"updates": []}, indent=2)
|
|
630
|
+
)
|
|
631
|
+
logger.info("Created stack_updates.json")
|
|
632
|
+
|
|
633
|
+
(session_dir / "tracking" / "tree_updates.json").write_text(
|
|
634
|
+
json.dumps({"updates": []}, indent=2)
|
|
635
|
+
)
|
|
636
|
+
logger.info("Created tree_updates.json")
|
|
637
|
+
except Exception as e:
|
|
638
|
+
raise FileOperationError(
|
|
639
|
+
operation="create",
|
|
640
|
+
file_path=str(session_dir / "tracking"),
|
|
641
|
+
details=f"Failed to create tracking files: {str(e)}",
|
|
642
|
+
cause=e,
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
# Create config.json with default settings
|
|
646
|
+
config_data = {
|
|
647
|
+
"curation": {
|
|
648
|
+
"auto_curate": True,
|
|
649
|
+
"frequency": 5,
|
|
650
|
+
"dry_run": False,
|
|
651
|
+
"similarity_threshold": 0.7,
|
|
652
|
+
"categories": [
|
|
653
|
+
"architecture_patterns",
|
|
654
|
+
"gotchas",
|
|
655
|
+
"best_practices",
|
|
656
|
+
"technical_debt",
|
|
657
|
+
"performance_insights",
|
|
658
|
+
"security",
|
|
659
|
+
],
|
|
660
|
+
},
|
|
661
|
+
"quality_gates": {
|
|
662
|
+
"test_execution": {
|
|
663
|
+
"enabled": True,
|
|
664
|
+
"required": True,
|
|
665
|
+
"coverage_threshold": 80,
|
|
666
|
+
"commands": {
|
|
667
|
+
"python": "pytest --cov --cov-report=json",
|
|
668
|
+
"javascript": "npm test -- --coverage",
|
|
669
|
+
"typescript": "npm test -- --coverage",
|
|
670
|
+
},
|
|
671
|
+
},
|
|
672
|
+
"linting": {
|
|
673
|
+
"enabled": True,
|
|
674
|
+
"required": True,
|
|
675
|
+
"auto_fix": True,
|
|
676
|
+
"commands": {
|
|
677
|
+
"python": "ruff check .",
|
|
678
|
+
"javascript": "npx eslint . --ext .js,.jsx",
|
|
679
|
+
"typescript": "npx eslint . --ext .ts,.tsx",
|
|
680
|
+
},
|
|
681
|
+
},
|
|
682
|
+
"formatting": {
|
|
683
|
+
"enabled": True,
|
|
684
|
+
"required": True,
|
|
685
|
+
"auto_fix": True,
|
|
686
|
+
"commands": {
|
|
687
|
+
"python": "ruff format .",
|
|
688
|
+
"javascript": "npx prettier .",
|
|
689
|
+
"typescript": "npx prettier .",
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
"security": {"enabled": True, "required": True, "fail_on": "high"},
|
|
693
|
+
"documentation": {
|
|
694
|
+
"enabled": True,
|
|
695
|
+
"required": True,
|
|
696
|
+
"check_changelog": True,
|
|
697
|
+
"check_docstrings": True,
|
|
698
|
+
"check_readme": False,
|
|
699
|
+
},
|
|
700
|
+
"context7": {
|
|
701
|
+
"enabled": False,
|
|
702
|
+
"required": True,
|
|
703
|
+
"important_libraries": [],
|
|
704
|
+
},
|
|
705
|
+
"custom_validations": {"rules": []},
|
|
706
|
+
},
|
|
707
|
+
"integration_tests": {
|
|
708
|
+
"enabled": True,
|
|
709
|
+
"docker_compose_file": "docker-compose.integration.yml",
|
|
710
|
+
"environment_validation": True,
|
|
711
|
+
"health_check_timeout": 300,
|
|
712
|
+
"test_data_fixtures": True,
|
|
713
|
+
"parallel_execution": True,
|
|
714
|
+
"performance_benchmarks": {
|
|
715
|
+
"enabled": True,
|
|
716
|
+
"required": True,
|
|
717
|
+
"regression_threshold": 0.10,
|
|
718
|
+
"baseline_storage": ".session/tracking/performance_baselines.json",
|
|
719
|
+
"load_test_tool": "wrk",
|
|
720
|
+
"metrics": ["response_time", "throughput", "resource_usage"],
|
|
721
|
+
},
|
|
722
|
+
"api_contracts": {
|
|
723
|
+
"enabled": True,
|
|
724
|
+
"required": True,
|
|
725
|
+
"contract_format": "openapi",
|
|
726
|
+
"breaking_change_detection": True,
|
|
727
|
+
"version_storage": ".session/tracking/api_contracts/",
|
|
728
|
+
"fail_on_breaking_changes": True,
|
|
729
|
+
},
|
|
730
|
+
"documentation": {
|
|
731
|
+
"architecture_diagrams": True,
|
|
732
|
+
"sequence_diagrams": True,
|
|
733
|
+
"contract_documentation": True,
|
|
734
|
+
"performance_baseline_docs": True,
|
|
735
|
+
},
|
|
736
|
+
},
|
|
737
|
+
"git_workflow": {
|
|
738
|
+
"mode": "pr",
|
|
739
|
+
"auto_push": True,
|
|
740
|
+
"auto_create_pr": True,
|
|
741
|
+
"delete_branch_after_merge": True,
|
|
742
|
+
"pr_title_template": "{type}: {title}",
|
|
743
|
+
"pr_body_template": "## Summary\n\n{description}\n\n## Work Item\n- ID: {work_item_id}\n- Type: {type}\n- Session: {session_num}\n\n## Changes\n{commit_messages}\n\nš¤ Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude <noreply@anthropic.com>",
|
|
744
|
+
},
|
|
745
|
+
}
|
|
746
|
+
try:
|
|
747
|
+
(session_dir / "config.json").write_text(json.dumps(config_data, indent=2))
|
|
748
|
+
logger.info("Created config.json")
|
|
749
|
+
except Exception as e:
|
|
750
|
+
raise FileOperationError(
|
|
751
|
+
operation="create",
|
|
752
|
+
file_path=str(session_dir / "config.json"),
|
|
753
|
+
details=f"Failed to create config.json: {str(e)}",
|
|
754
|
+
cause=e,
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
# Copy config schema file
|
|
758
|
+
schema_source = Path(__file__).parent.parent / "templates" / "config.schema.json"
|
|
759
|
+
schema_dest = session_dir / "config.schema.json"
|
|
760
|
+
|
|
761
|
+
if schema_source.exists() and not schema_dest.exists():
|
|
762
|
+
try:
|
|
763
|
+
shutil.copy(schema_source, schema_dest)
|
|
764
|
+
logger.info("Created config.schema.json")
|
|
765
|
+
except Exception as e:
|
|
766
|
+
raise FileOperationError(
|
|
767
|
+
operation="copy",
|
|
768
|
+
file_path=str(schema_dest),
|
|
769
|
+
details=f"Failed to copy config schema: {str(e)}",
|
|
770
|
+
cause=e,
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def run_initial_scans() -> None:
|
|
775
|
+
"""
|
|
776
|
+
Run initial stack and tree scans with FIXED path resolution (Bug #12).
|
|
777
|
+
|
|
778
|
+
Raises:
|
|
779
|
+
None - failures are logged as warnings to allow initialization to continue.
|
|
780
|
+
|
|
781
|
+
Note:
|
|
782
|
+
Uses absolute paths to stack.py and tree.py scripts.
|
|
783
|
+
Failures don't block initialization - user can generate these later.
|
|
784
|
+
"""
|
|
785
|
+
logger.info("\nGenerating project context...")
|
|
786
|
+
|
|
787
|
+
# Get Solokit installation directory
|
|
788
|
+
script_dir = Path(__file__).parent
|
|
789
|
+
runner = CommandRunner(default_timeout=GIT_STANDARD_TIMEOUT)
|
|
790
|
+
|
|
791
|
+
# Run stack.py with absolute path
|
|
792
|
+
try:
|
|
793
|
+
result = runner.run(["python", str(script_dir / "stack.py")], check=True)
|
|
794
|
+
if result.success:
|
|
795
|
+
logger.info("Generated stack.txt")
|
|
796
|
+
else:
|
|
797
|
+
logger.warning("Could not generate stack.txt")
|
|
798
|
+
if result.stderr:
|
|
799
|
+
logger.warning(f"Error: {result.stderr.strip()}")
|
|
800
|
+
except Exception as e:
|
|
801
|
+
logger.warning(f"Stack generation failed: {e}")
|
|
802
|
+
|
|
803
|
+
# Run tree.py with absolute path
|
|
804
|
+
try:
|
|
805
|
+
result = runner.run(["python", str(script_dir / "tree.py")], check=True)
|
|
806
|
+
if result.success:
|
|
807
|
+
logger.info("Generated tree.txt")
|
|
808
|
+
else:
|
|
809
|
+
logger.warning("Could not generate tree.txt")
|
|
810
|
+
if result.stderr:
|
|
811
|
+
logger.warning(f"Error: {result.stderr.strip()}")
|
|
812
|
+
except Exception as e:
|
|
813
|
+
logger.warning(f"Tree generation failed: {e}")
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def ensure_gitignore_entries() -> None:
|
|
817
|
+
"""
|
|
818
|
+
Add .session patterns and OS-specific files to .gitignore.
|
|
819
|
+
|
|
820
|
+
Raises:
|
|
821
|
+
FileOperationError: If .gitignore read/write operations fail.
|
|
822
|
+
|
|
823
|
+
Note:
|
|
824
|
+
Prints informational messages about updated .gitignore.
|
|
825
|
+
"""
|
|
826
|
+
gitignore = Path(".gitignore")
|
|
827
|
+
|
|
828
|
+
required_entries = [
|
|
829
|
+
".session/briefings/",
|
|
830
|
+
".session/history/",
|
|
831
|
+
"coverage/",
|
|
832
|
+
"coverage.json",
|
|
833
|
+
"node_modules/",
|
|
834
|
+
"dist/",
|
|
835
|
+
"venv/",
|
|
836
|
+
".venv/",
|
|
837
|
+
"*.pyc",
|
|
838
|
+
"__pycache__/",
|
|
839
|
+
]
|
|
840
|
+
|
|
841
|
+
# OS-specific files
|
|
842
|
+
os_specific_entries = [
|
|
843
|
+
".DS_Store # macOS",
|
|
844
|
+
".DS_Store? # macOS",
|
|
845
|
+
"._* # macOS resource forks",
|
|
846
|
+
".Spotlight-V100 # macOS",
|
|
847
|
+
".Trashes # macOS",
|
|
848
|
+
"Thumbs.db # Windows",
|
|
849
|
+
"ehthumbs.db # Windows",
|
|
850
|
+
"Desktop.ini # Windows",
|
|
851
|
+
"$RECYCLE.BIN/ # Windows",
|
|
852
|
+
"*~ # Linux backup files",
|
|
853
|
+
]
|
|
854
|
+
|
|
855
|
+
try:
|
|
856
|
+
existing_content = gitignore.read_text() if gitignore.exists() else ""
|
|
857
|
+
except Exception as e:
|
|
858
|
+
raise FileOperationError(
|
|
859
|
+
operation="read",
|
|
860
|
+
file_path=str(gitignore),
|
|
861
|
+
details=f"Failed to read .gitignore: {str(e)}",
|
|
862
|
+
cause=e,
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
entries_to_add = []
|
|
866
|
+
for entry in required_entries:
|
|
867
|
+
if entry not in existing_content:
|
|
868
|
+
entries_to_add.append(entry)
|
|
869
|
+
|
|
870
|
+
# First pass: check which patterns need to be added
|
|
871
|
+
os_patterns_needed = []
|
|
872
|
+
for entry in os_specific_entries:
|
|
873
|
+
# Skip comment-only lines in first pass
|
|
874
|
+
pattern = entry.split("#")[0].strip()
|
|
875
|
+
if pattern and pattern not in existing_content:
|
|
876
|
+
os_patterns_needed.append(entry)
|
|
877
|
+
|
|
878
|
+
# If we need to add any OS patterns, include the header
|
|
879
|
+
os_entries_to_add = []
|
|
880
|
+
if os_patterns_needed:
|
|
881
|
+
# Add the section header first
|
|
882
|
+
os_entries_to_add.append("# OS-specific files")
|
|
883
|
+
# Then add all the patterns
|
|
884
|
+
os_entries_to_add.extend(os_patterns_needed)
|
|
885
|
+
|
|
886
|
+
if entries_to_add or os_entries_to_add:
|
|
887
|
+
logger.info("\nUpdating .gitignore...")
|
|
888
|
+
try:
|
|
889
|
+
with open(gitignore, "a") as f:
|
|
890
|
+
if existing_content and not existing_content.endswith("\n"):
|
|
891
|
+
f.write("\n")
|
|
892
|
+
|
|
893
|
+
if entries_to_add:
|
|
894
|
+
f.write("\n# Solokit-related patterns\n")
|
|
895
|
+
for entry in entries_to_add:
|
|
896
|
+
f.write(f"{entry}\n")
|
|
897
|
+
|
|
898
|
+
if os_entries_to_add:
|
|
899
|
+
f.write("\n")
|
|
900
|
+
for entry in os_entries_to_add:
|
|
901
|
+
f.write(f"{entry}\n")
|
|
902
|
+
|
|
903
|
+
total_added = len(entries_to_add) + len(
|
|
904
|
+
[e for e in os_entries_to_add if not e.startswith("#")]
|
|
905
|
+
)
|
|
906
|
+
logger.info(f"Added {total_added} entries to .gitignore")
|
|
907
|
+
except Exception as e:
|
|
908
|
+
raise FileOperationError(
|
|
909
|
+
operation="write",
|
|
910
|
+
file_path=str(gitignore),
|
|
911
|
+
details=f"Failed to update .gitignore: {str(e)}",
|
|
912
|
+
cause=e,
|
|
913
|
+
)
|
|
914
|
+
else:
|
|
915
|
+
logger.info(".gitignore already up to date")
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
def create_initial_commit(project_root: Path | None = None) -> bool:
|
|
919
|
+
"""
|
|
920
|
+
Create initial commit after project initialization.
|
|
921
|
+
|
|
922
|
+
Args:
|
|
923
|
+
project_root: Root directory of the project. Defaults to current working directory.
|
|
924
|
+
|
|
925
|
+
Returns:
|
|
926
|
+
True if initial commit was created or already exists.
|
|
927
|
+
|
|
928
|
+
Raises:
|
|
929
|
+
GitError: If git add or commit operations fail critically.
|
|
930
|
+
|
|
931
|
+
Note:
|
|
932
|
+
Prints informational messages. Some failures are logged as warnings to allow
|
|
933
|
+
the initialization to complete (user can commit manually later).
|
|
934
|
+
"""
|
|
935
|
+
if project_root is None:
|
|
936
|
+
project_root = Path.cwd()
|
|
937
|
+
|
|
938
|
+
runner = CommandRunner(default_timeout=GIT_STANDARD_TIMEOUT, working_dir=project_root)
|
|
939
|
+
|
|
940
|
+
try:
|
|
941
|
+
# Check if repository has any commits by trying to count them
|
|
942
|
+
# This will fail gracefully if no commits exist yet
|
|
943
|
+
result = runner.run(["git", "rev-list", "--count", "--all"], check=False)
|
|
944
|
+
|
|
945
|
+
if result.success and result.stdout.strip() and int(result.stdout.strip()) > 0:
|
|
946
|
+
logger.info("Git repository already has commits, skipping initial commit")
|
|
947
|
+
return True
|
|
948
|
+
|
|
949
|
+
except Exception:
|
|
950
|
+
# If command fails (e.g., no commits yet), continue to create initial commit
|
|
951
|
+
pass
|
|
952
|
+
|
|
953
|
+
try:
|
|
954
|
+
# Stage all initialized files
|
|
955
|
+
result = runner.run(["git", "add", "-A"], check=True)
|
|
956
|
+
if not result.success:
|
|
957
|
+
logger.warning(f"Git add failed: {result.stderr}")
|
|
958
|
+
logger.warning("You may need to commit manually before starting sessions")
|
|
959
|
+
return False
|
|
960
|
+
|
|
961
|
+
# Create initial commit
|
|
962
|
+
commit_message = """chore: Initialize project with Session-Driven Development
|
|
963
|
+
|
|
964
|
+
Project initialized with Solokit framework including:
|
|
965
|
+
- Project structure and configuration files
|
|
966
|
+
- Quality gates and testing setup
|
|
967
|
+
- Session tracking infrastructure
|
|
968
|
+
- Documentation templates
|
|
969
|
+
|
|
970
|
+
š¤ Generated with [Claude Code](https://claude.com/claude-code)
|
|
971
|
+
|
|
972
|
+
Co-Authored-By: Claude <noreply@anthropic.com>"""
|
|
973
|
+
|
|
974
|
+
result = runner.run(["git", "commit", "-m", commit_message], check=True)
|
|
975
|
+
if not result.success:
|
|
976
|
+
logger.warning(f"Git commit failed: {result.stderr}")
|
|
977
|
+
logger.warning("You may need to commit manually before starting sessions")
|
|
978
|
+
return False
|
|
979
|
+
|
|
980
|
+
logger.info("Created initial commit on main branch")
|
|
981
|
+
return True
|
|
982
|
+
|
|
983
|
+
except Exception as e:
|
|
984
|
+
logger.warning(f"Failed to create initial commit: {e}")
|
|
985
|
+
logger.warning("You may need to commit manually before starting sessions")
|
|
986
|
+
return False
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
def init_project() -> int:
|
|
990
|
+
"""
|
|
991
|
+
LEGACY: Basic initialization function without templates.
|
|
992
|
+
|
|
993
|
+
This function is deprecated in favor of template-based initialization.
|
|
994
|
+
Use `sk init --template=<template> --tier=<tier> --coverage=<coverage>` instead.
|
|
995
|
+
|
|
996
|
+
Returns:
|
|
997
|
+
0 on success, 1 if already initialized, or raises exception on critical errors.
|
|
998
|
+
|
|
999
|
+
Raises:
|
|
1000
|
+
DirectoryNotEmptyError: If .session directory already exists.
|
|
1001
|
+
GitError: If git operations fail critically.
|
|
1002
|
+
FileOperationError: If file operations fail critically.
|
|
1003
|
+
ValidationError: If configuration or validation fails.
|
|
1004
|
+
|
|
1005
|
+
Note:
|
|
1006
|
+
Some non-critical failures (like dependency installation) are logged as warnings
|
|
1007
|
+
and don't stop the initialization process. The user can fix these manually.
|
|
1008
|
+
All output is now through logger instead of print().
|
|
1009
|
+
"""
|
|
1010
|
+
logger.warning("ā ļø Using legacy initialization mode")
|
|
1011
|
+
logger.warning("ā ļø Consider using template-based init for better experience")
|
|
1012
|
+
logger.warning("")
|
|
1013
|
+
logger.info("š Initializing Session-Driven Development...\n")
|
|
1014
|
+
|
|
1015
|
+
# 1. Check if already initialized
|
|
1016
|
+
if Path(".session").exists():
|
|
1017
|
+
raise DirectoryNotEmptyError(".session")
|
|
1018
|
+
|
|
1019
|
+
# 2. Check or initialize git repository
|
|
1020
|
+
check_or_init_git()
|
|
1021
|
+
|
|
1022
|
+
# 3. Install git hooks
|
|
1023
|
+
install_git_hooks()
|
|
1024
|
+
logger.info("")
|
|
1025
|
+
|
|
1026
|
+
# 4. Detect project type
|
|
1027
|
+
project_type = detect_project_type()
|
|
1028
|
+
logger.info(f"\nš¦ Project type: {project_type}\n")
|
|
1029
|
+
|
|
1030
|
+
# 5. Ensure package manager file (create/update)
|
|
1031
|
+
ensure_package_manager_file(project_type)
|
|
1032
|
+
|
|
1033
|
+
# 6. Ensure all config files (create from templates)
|
|
1034
|
+
logger.info("")
|
|
1035
|
+
ensure_config_files(project_type)
|
|
1036
|
+
|
|
1037
|
+
# 7. Install dependencies
|
|
1038
|
+
logger.info("")
|
|
1039
|
+
install_dependencies(project_type)
|
|
1040
|
+
|
|
1041
|
+
# 8. Create smoke tests
|
|
1042
|
+
logger.info("")
|
|
1043
|
+
create_smoke_tests(project_type)
|
|
1044
|
+
|
|
1045
|
+
# 9. Create .session structure
|
|
1046
|
+
create_session_structure()
|
|
1047
|
+
|
|
1048
|
+
# 10. Initialize tracking files
|
|
1049
|
+
initialize_tracking_files()
|
|
1050
|
+
|
|
1051
|
+
# 11. Generate project context (stack/tree)
|
|
1052
|
+
run_initial_scans()
|
|
1053
|
+
|
|
1054
|
+
# 12. Update .gitignore
|
|
1055
|
+
logger.info("")
|
|
1056
|
+
ensure_gitignore_entries()
|
|
1057
|
+
|
|
1058
|
+
# 13. Create initial commit
|
|
1059
|
+
logger.info("")
|
|
1060
|
+
create_initial_commit()
|
|
1061
|
+
|
|
1062
|
+
# Success summary
|
|
1063
|
+
logger.info("\n" + "=" * 60)
|
|
1064
|
+
logger.info("ā
Solokit Initialized Successfully!")
|
|
1065
|
+
logger.info("=" * 60)
|
|
1066
|
+
|
|
1067
|
+
logger.info("\nš¦ What was created/updated:")
|
|
1068
|
+
logger.info(" ā Git repository initialized with initial commit")
|
|
1069
|
+
logger.info(" ā Git hooks (prepare-commit-msg with CHANGELOG/LEARNING reminders)")
|
|
1070
|
+
logger.info(" ā Config files (.eslintrc, .prettierrc, jest.config, etc.)")
|
|
1071
|
+
logger.info(" ā Dependencies installed")
|
|
1072
|
+
logger.info(" ā Smoke tests created")
|
|
1073
|
+
logger.info(" ā .session/ structure with tracking files")
|
|
1074
|
+
logger.info(" ā Project context (stack.txt, tree.txt)")
|
|
1075
|
+
logger.info(" ā .gitignore updated")
|
|
1076
|
+
|
|
1077
|
+
logger.info("\nš Next Step:")
|
|
1078
|
+
logger.info(" /sk:work-new")
|
|
1079
|
+
logger.info("")
|
|
1080
|
+
|
|
1081
|
+
return 0
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
# ============================================================================
|
|
1085
|
+
# NEW TEMPLATE-BASED INIT (PRIMARY ENTRY POINT)
|
|
1086
|
+
# ============================================================================
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
def main() -> int:
|
|
1090
|
+
"""
|
|
1091
|
+
Main entry point for init command with template-based initialization.
|
|
1092
|
+
|
|
1093
|
+
Handles CLI argument parsing and routes to appropriate init function:
|
|
1094
|
+
- With arguments (--template, --tier, etc.): Template-based init
|
|
1095
|
+
- Without arguments (legacy): Basic init (deprecated)
|
|
1096
|
+
|
|
1097
|
+
Returns:
|
|
1098
|
+
0 on success, non-zero on failure
|
|
1099
|
+
"""
|
|
1100
|
+
import argparse
|
|
1101
|
+
|
|
1102
|
+
parser = argparse.ArgumentParser(description="Initialize Session-Driven Development project")
|
|
1103
|
+
parser.add_argument(
|
|
1104
|
+
"--template",
|
|
1105
|
+
choices=["saas_t3", "ml_ai_fastapi", "dashboard_refine", "fullstack_nextjs"],
|
|
1106
|
+
help="Template to use for initialization",
|
|
1107
|
+
)
|
|
1108
|
+
parser.add_argument(
|
|
1109
|
+
"--tier",
|
|
1110
|
+
choices=[
|
|
1111
|
+
"tier-1-essential",
|
|
1112
|
+
"tier-2-standard",
|
|
1113
|
+
"tier-3-comprehensive",
|
|
1114
|
+
"tier-4-production",
|
|
1115
|
+
],
|
|
1116
|
+
help="Quality gates tier",
|
|
1117
|
+
)
|
|
1118
|
+
parser.add_argument(
|
|
1119
|
+
"--coverage",
|
|
1120
|
+
type=int,
|
|
1121
|
+
help="Test coverage target percentage (e.g., 60, 80, 90)",
|
|
1122
|
+
)
|
|
1123
|
+
parser.add_argument(
|
|
1124
|
+
"--options",
|
|
1125
|
+
help="Comma-separated list of additional options (ci_cd,docker,pre_commit,env_templates)",
|
|
1126
|
+
)
|
|
1127
|
+
|
|
1128
|
+
args = parser.parse_args()
|
|
1129
|
+
|
|
1130
|
+
# Check if template-based init is requested
|
|
1131
|
+
if args.template:
|
|
1132
|
+
# Template-based initialization (new flow)
|
|
1133
|
+
from solokit.init.orchestrator import run_template_based_init
|
|
1134
|
+
|
|
1135
|
+
# Validate required arguments
|
|
1136
|
+
if not args.tier:
|
|
1137
|
+
logger.error("--tier is required when using --template")
|
|
1138
|
+
return 1
|
|
1139
|
+
if not args.coverage:
|
|
1140
|
+
logger.error("--coverage is required when using --template")
|
|
1141
|
+
return 1
|
|
1142
|
+
|
|
1143
|
+
# Parse additional options
|
|
1144
|
+
additional_options = []
|
|
1145
|
+
if args.options:
|
|
1146
|
+
additional_options = [opt.strip() for opt in args.options.split(",")]
|
|
1147
|
+
|
|
1148
|
+
# Run template-based init
|
|
1149
|
+
return run_template_based_init(
|
|
1150
|
+
template_id=args.template,
|
|
1151
|
+
tier=args.tier,
|
|
1152
|
+
coverage_target=args.coverage,
|
|
1153
|
+
additional_options=additional_options,
|
|
1154
|
+
)
|
|
1155
|
+
else:
|
|
1156
|
+
# Legacy init (basic initialization without templates)
|
|
1157
|
+
# The deprecation warning is shown in init_project() itself
|
|
1158
|
+
return init_project()
|
|
1159
|
+
|
|
1160
|
+
|
|
1161
|
+
if __name__ == "__main__":
|
|
1162
|
+
exit(main())
|