feat: implement project launch script run.sh and update README

This commit is contained in:
2025-12-20 22:05:18 +03:00
parent e4dc3159cd
commit 58831c536a
23 changed files with 964 additions and 28 deletions

2
.kilocode/rules/specify-rules.md Executable file → Normal file
View File

@@ -3,6 +3,7 @@
Auto-generated from all feature plans. Last updated: 2025-12-19 Auto-generated from all feature plans. Last updated: 2025-12-19
## Active Technologies ## Active Technologies
- Python 3.9+, Node.js 18+ + `uvicorn`, `npm`, `bash` (003-project-launch-script)
- Python 3.9+ (Backend), Node.js 18+ (Frontend Build) (001-plugin-arch-svelte-ui) - Python 3.9+ (Backend), Node.js 18+ (Frontend Build) (001-plugin-arch-svelte-ui)
@@ -23,6 +24,7 @@ cd src; pytest; ruff check .
Python 3.9+ (Backend), Node.js 18+ (Frontend Build): Follow standard conventions Python 3.9+ (Backend), Node.js 18+ (Frontend Build): Follow standard conventions
## Recent Changes ## Recent Changes
- 003-project-launch-script: Added Python 3.9+, Node.js 18+ + `uvicorn`, `npm`, `bash`
- 001-plugin-arch-svelte-ui: Added Python 3.9+ (Backend), Node.js 18+ (Frontend Build) - 001-plugin-arch-svelte-ui: Added Python 3.9+ (Backend), Node.js 18+ (Frontend Build)

View File

@@ -50,6 +50,19 @@
## Использование ## Использование
### Запуск проекта (Web UI)
Для запуска backend и frontend серверов одной командой:
```bash
./run.sh
```
Опции:
- `--skip-install`: Пропустить проверку и установку зависимостей.
- `--help`: Показать справку.
Переменные окружения:
- `BACKEND_PORT`: Порт для backend (по умолчанию 8000).
- `FRONTEND_PORT`: Порт для frontend (по умолчанию 5173).
### Скрипт резервного копирования (`backup_script.py`) ### Скрипт резервного копирования (`backup_script.py`)
Для создания резервных копий дашбордов из настроенных окружений Superset: Для создания резервных копий дашбордов из настроенных окружений Superset:
```bash ```bash

189
backend/backend.log Normal file
View File

@@ -0,0 +1,189 @@
INFO: Will watch for changes in these directories: ['/home/user/ss-tools/backend']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [7952] using StatReload
INFO: Started server process [7968]
INFO: Waiting for application startup.
INFO: Application startup complete.
Error loading plugin module backup: No module named 'yaml'
Error loading plugin module migration: No module named 'yaml'
INFO: 127.0.0.1:36934 - "HEAD /docs HTTP/1.1" 200 OK
INFO: 127.0.0.1:55006 - "GET /settings HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:55006 - "GET /settings/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:55010 - "GET /plugins HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:55010 - "GET /plugins/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:55010 - "GET /settings HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:55010 - "GET /settings/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:55010 - "GET /plugins HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:55010 - "GET /plugins/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:55010 - "GET /settings HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:55010 - "GET /settings/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:35508 - "GET /plugins HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:35508 - "GET /plugins/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:49820 - "GET /plugins HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:49820 - "GET /plugins/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:49822 - "GET /settings HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:49822 - "GET /settings/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:49822 - "GET /plugins HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:49822 - "GET /plugins/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:49908 - "GET /settings HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:49908 - "GET /settings/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:49922 - "OPTIONS /settings/environments HTTP/1.1" 200 OK
[2025-12-20 19:14:15,576][INFO][superset_tools_app] [ConfigManager.save_config][Coherence:OK] Configuration saved context={'path': '/home/user/ss-tools/config.json'}
INFO: 127.0.0.1:49922 - "POST /settings/environments HTTP/1.1" 200 OK
INFO: 127.0.0.1:49922 - "GET /settings HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:49922 - "GET /settings/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:49922 - "OPTIONS /settings/environments/7071dab6-881f-49a2-b850-c004b3fc11c0/test HTTP/1.1" 200 OK
INFO: 127.0.0.1:36930 - "POST /settings/environments/7071dab6-881f-49a2-b850-c004b3fc11c0/test HTTP/1.1" 500 Internal Server Error
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/h11_impl.py", line 403, in run_asgi
result = await app( # type: ignore[func-returns-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__
return await self.app(scope, receive, send)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/fastapi/applications.py", line 1135, in __call__
await super().__call__(scope, receive, send)
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/applications.py", line 107, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/middleware/cors.py", line 93, in __call__
await self.simple_response(scope, receive, send, request_headers=headers)
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/middleware/cors.py", line 144, in simple_response
await self.app(scope, receive, send)
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 63, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
raise exc
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
await self.app(scope, receive, send)
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/routing.py", line 716, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/routing.py", line 736, in app
await route.handle(scope, receive, send)
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/routing.py", line 290, in handle
await self.app(scope, receive, send)
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/fastapi/routing.py", line 118, in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
raise exc
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/fastapi/routing.py", line 104, in app
response = await f(request)
^^^^^^^^^^^^^^^^
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/fastapi/routing.py", line 428, in app
raw_response = await run_endpoint_function(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/fastapi/routing.py", line 314, in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/ss-tools/backend/src/api/routes/settings.py", line 103, in test_connection
import httpx
ModuleNotFoundError: No module named 'httpx'
INFO: 127.0.0.1:45776 - "POST /settings/environments/7071dab6-881f-49a2-b850-c004b3fc11c0/test HTTP/1.1" 200 OK
INFO: 127.0.0.1:45784 - "GET /plugins HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:45784 - "GET /plugins/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:41628 - "GET /settings HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:41628 - "GET /settings/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:41628 - "GET /plugins HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:41628 - "GET /plugins/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:60184 - "GET /settings HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:60184 - "GET /settings/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:60184 - "GET /plugins HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:60184 - "GET /plugins/ HTTP/1.1" 200 OK
INFO: 127.0.0.1:60184 - "GET /settings HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:60184 - "GET /settings/ HTTP/1.1" 200 OK
WARNING: StatReload detected changes in 'src/core/plugin_loader.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [7968]
INFO: Started server process [12178]
INFO: Waiting for application startup.
INFO: Application startup complete.
WARNING: StatReload detected changes in 'src/dependencies.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [12178]
INFO: Started server process [12451]
INFO: Waiting for application startup.
INFO: Application startup complete.
Plugin 'Superset Dashboard Backup' (ID: superset-backup) loaded successfully.
Plugin 'Superset Dashboard Migration' (ID: superset-migration) loaded successfully.
INFO: 127.0.0.1:37334 - "GET / HTTP/1.1" 200 OK
INFO: 127.0.0.1:37334 - "GET /favicon.ico HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:39932 - "GET / HTTP/1.1" 200 OK
INFO: 127.0.0.1:39932 - "GET /favicon.ico HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:39932 - "GET / HTTP/1.1" 200 OK
INFO: 127.0.0.1:39932 - "GET / HTTP/1.1" 200 OK
INFO: 127.0.0.1:54900 - "GET /plugins HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:49280 - "GET /plugins HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:49280 - "GET /plugins/ HTTP/1.1" 200 OK
WARNING: StatReload detected changes in 'src/api/routes/plugins.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [12451]
INFO: Started server process [15016]
INFO: Waiting for application startup.
INFO: Application startup complete.
Plugin 'Superset Dashboard Backup' (ID: superset-backup) loaded successfully.
Plugin 'Superset Dashboard Migration' (ID: superset-migration) loaded successfully.
INFO: 127.0.0.1:59340 - "GET /plugins HTTP/1.1" 307 Temporary Redirect
DEBUG: list_plugins called. Found 0 plugins.
INFO: 127.0.0.1:59340 - "GET /plugins/ HTTP/1.1" 200 OK
WARNING: StatReload detected changes in 'src/dependencies.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [15016]
INFO: Started server process [15257]
INFO: Waiting for application startup.
INFO: Application startup complete.
Plugin 'Superset Dashboard Backup' (ID: superset-backup) loaded successfully.
Plugin 'Superset Dashboard Migration' (ID: superset-migration) loaded successfully.
DEBUG: dependencies.py initialized. PluginLoader ID: 139922613090976
DEBUG: dependencies.py initialized. PluginLoader ID: 139922627375088
INFO: 127.0.0.1:57464 - "GET /plugins HTTP/1.1" 307 Temporary Redirect
DEBUG: get_plugin_loader called. Returning PluginLoader ID: 139922627375088
DEBUG: list_plugins called. Found 0 plugins.
INFO: 127.0.0.1:57464 - "GET /plugins/ HTTP/1.1" 200 OK
WARNING: StatReload detected changes in 'src/core/plugin_loader.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [15257]
INFO: Started server process [15533]
INFO: Waiting for application startup.
INFO: Application startup complete.
DEBUG: Loading plugin backup as src.plugins.backup
Plugin 'Superset Dashboard Backup' (ID: superset-backup) loaded successfully.
DEBUG: Loading plugin migration as src.plugins.migration
Plugin 'Superset Dashboard Migration' (ID: superset-migration) loaded successfully.
DEBUG: dependencies.py initialized. PluginLoader ID: 140371031142384
INFO: 127.0.0.1:46470 - "GET /plugins HTTP/1.1" 307 Temporary Redirect
DEBUG: get_plugin_loader called. Returning PluginLoader ID: 140371031142384
DEBUG: list_plugins called. Found 2 plugins.
DEBUG: Plugin: superset-backup
DEBUG: Plugin: superset-migration
INFO: 127.0.0.1:46470 - "GET /plugins/ HTTP/1.1" 200 OK
WARNING: StatReload detected changes in 'src/api/routes/settings.py'. Reloading...
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [15533]
INFO: Started server process [15827]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [15827]
INFO: Stopping reloader process [7952]

View File

@@ -0,0 +1,35 @@
2025-12-20 19:55:11,325 - INFO - [BackupPlugin][Entry] Starting backup for superset.
2025-12-20 19:55:11,325 - INFO - [setup_clients][Enter] Starting Superset clients initialization.
2025-12-20 19:55:11,327 - CRITICAL - [setup_clients][Failure] Critical error during client initialization: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
Traceback (most recent call last):
File "/home/user/ss-tools/superset_tool/utils/init_clients.py", line 43, in setup_clients
config = SupersetConfig(
^^^^^^^^^^^^^^^
File "/home/user/ss-tools/backend/venv/lib/python3.12/site-packages/pydantic/main.py", line 250, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
2025-12-20 21:01:49,905 - INFO - [BackupPlugin][Entry] Starting backup for superset.
2025-12-20 21:01:49,906 - INFO - [setup_clients][Enter] Starting Superset clients initialization.
2025-12-20 21:01:49,988 - INFO - [setup_clients][Action] Loading environments from ConfigManager
2025-12-20 21:01:49,990 - CRITICAL - [setup_clients][Failure] Critical error during client initialization: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
Traceback (most recent call last):
File "/home/user/ss-tools/superset_tool/utils/init_clients.py", line 66, in setup_clients
config = SupersetConfig(
^^^^^^^^^^^^^^^
File "/home/user/ss-tools/venv/lib/python3.12/site-packages/pydantic/main.py", line 250, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error

View File

@@ -70,6 +70,25 @@ async def add_environment(
config_manager: ConfigManager = Depends(get_config_manager) config_manager: ConfigManager = Depends(get_config_manager)
): ):
logger.info(f"[add_environment][Entry] Adding environment {env.id}") logger.info(f"[add_environment][Entry] Adding environment {env.id}")
# Validate connection before adding
try:
superset_config = SupersetConfig(
env=env.name,
base_url=env.url,
auth={
"provider": "db",
"username": env.username,
"password": env.password,
"refresh": "true"
}
)
client = SupersetClient(config=superset_config)
client.get_dashboards(query={"page_size": 1})
except Exception as e:
logger.error(f"[add_environment][Coherence:Failed] Connection validation failed: {e}")
raise HTTPException(status_code=400, detail=f"Connection validation failed: {e}")
config_manager.add_environment(env) config_manager.add_environment(env)
return env return env
# [/DEF:add_environment] # [/DEF:add_environment]
@@ -86,6 +105,32 @@ async def update_environment(
config_manager: ConfigManager = Depends(get_config_manager) config_manager: ConfigManager = Depends(get_config_manager)
): ):
logger.info(f"[update_environment][Entry] Updating environment {id}") logger.info(f"[update_environment][Entry] Updating environment {id}")
# If password is masked, we need the real one for validation
env_to_validate = env.copy(deep=True)
if env_to_validate.password == "********":
old_env = next((e for e in config_manager.get_environments() if e.id == id), None)
if old_env:
env_to_validate.password = old_env.password
# Validate connection before updating
try:
superset_config = SupersetConfig(
env=env_to_validate.name,
base_url=env_to_validate.url,
auth={
"provider": "db",
"username": env_to_validate.username,
"password": env_to_validate.password,
"refresh": "true"
}
)
client = SupersetClient(config=superset_config)
client.get_dashboards(query={"page_size": 1})
except Exception as e:
logger.error(f"[update_environment][Coherence:Failed] Connection validation failed: {e}")
raise HTTPException(status_code=400, detail=f"Connection validation failed: {e}")
if config_manager.update_environment(id, env): if config_manager.update_environment(id, env):
return env return env
raise HTTPException(status_code=404, detail=f"Environment {id} not found") raise HTTPException(status_code=404, detail=f"Environment {id} not found")
@@ -152,34 +197,22 @@ async def test_environment_connection(
# @PARAM: path (str) - The path to validate. # @PARAM: path (str) - The path to validate.
# @RETURN: dict - Validation result. # @RETURN: dict - Validation result.
@router.post("/validate-path") @router.post("/validate-path")
async def validate_backup_path(path_data: dict): async def validate_backup_path(
path_data: dict,
config_manager: ConfigManager = Depends(get_config_manager)
):
path = path_data.get("path") path = path_data.get("path")
if not path: if not path:
raise HTTPException(status_code=400, detail="Path is required") raise HTTPException(status_code=400, detail="Path is required")
logger.info(f"[validate_backup_path][Entry] Validating path: {path}") logger.info(f"[validate_backup_path][Entry] Validating path: {path}")
p = os.path.abspath(path) valid, message = config_manager.validate_path(path)
exists = os.path.exists(p)
writable = os.access(p, os.W_OK) if exists else os.access(os.path.dirname(p), os.W_OK)
if not exists: if not valid:
# Try to create it return {"status": "error", "message": message}
try:
os.makedirs(p, exist_ok=True)
exists = True
writable = os.access(p, os.W_OK)
logger.info(f"[validate_backup_path][Action] Created directory: {p}")
except Exception as e:
logger.error(f"[validate_backup_path][Coherence:Failed] Failed to create directory: {e}")
return {"status": "error", "message": f"Path does not exist and could not be created: {e}"}
if not writable: return {"status": "success", "message": message}
logger.warning(f"[validate_backup_path][Coherence:Failed] Path not writable: {p}")
return {"status": "error", "message": "Path is not writable"}
logger.info(f"[validate_backup_path][Coherence:OK] Path valid: {p}")
return {"status": "success", "message": "Path is valid and writable"}
# [/DEF:validate_backup_path] # [/DEF:validate_backup_path]
# [/DEF:SettingsRouter] # [/DEF:SettingsRouter]

View File

@@ -124,6 +124,24 @@ class ConfigManager:
logger.info(f"[update_global_settings][Exit] Settings updated") logger.info(f"[update_global_settings][Exit] Settings updated")
# [/DEF:update_global_settings] # [/DEF:update_global_settings]
# [DEF:validate_path:Function]
# @PURPOSE: Validates if a path exists and is writable.
# @PARAM: path (str) - The path to validate.
# @RETURN: tuple (bool, str) - (is_valid, message)
def validate_path(self, path: str) -> tuple[bool, str]:
p = os.path.abspath(path)
if not os.path.exists(p):
try:
os.makedirs(p, exist_ok=True)
except Exception as e:
return False, f"Path does not exist and could not be created: {e}"
if not os.access(p, os.W_OK):
return False, "Path is not writable"
return True, "Path is valid and writable"
# [/DEF:validate_path]
# [DEF:get_environments:Function] # [DEF:get_environments:Function]
# @PURPOSE: Returns the list of configured environments. # @PURPOSE: Returns the list of configured environments.
# @RETURN: List[Environment] - List of environments. # @RETURN: List[Environment] - List of environments.
@@ -131,6 +149,13 @@ class ConfigManager:
return self.config.environments return self.config.environments
# [/DEF:get_environments] # [/DEF:get_environments]
# [DEF:has_environments:Function]
# @PURPOSE: Checks if at least one environment is configured.
# @RETURN: bool - True if at least one environment exists.
def has_environments(self) -> bool:
return len(self.config.environments) > 0
# [/DEF:has_environments]
# [DEF:add_environment:Function] # [DEF:add_environment:Function]
# @PURPOSE: Adds a new environment to the configuration. # @PURPOSE: Adds a new environment to the configuration.
# @PRE: isinstance(env, Environment) # @PRE: isinstance(env, Environment)

View File

@@ -58,7 +58,7 @@ class BackupPlugin(PluginBase):
"type": "string", "type": "string",
"title": "Environment", "title": "Environment",
"description": "The Superset environment to back up.", "description": "The Superset environment to back up.",
"enum": envs if envs else ["dev", "prod"], "enum": envs if envs else [],
}, },
"backup_path": { "backup_path": {
"type": "string", "type": "string",
@@ -79,6 +79,9 @@ class BackupPlugin(PluginBase):
try: try:
config_manager = get_config_manager() config_manager = get_config_manager()
if not config_manager.has_environments():
raise ValueError("No Superset environments configured. Please add an environment in Settings.")
clients = setup_clients(logger, custom_envs=config_manager.get_environments()) clients = setup_clients(logger, custom_envs=config_manager.get_environments())
client = clients.get(env) client = clients.get(env)

View File

@@ -20,7 +20,8 @@
if ($selectedTask) { if ($selectedTask) {
console.log(`[TaskRunner][Entry] Connecting to logs for task: ${$selectedTask.id}`); console.log(`[TaskRunner][Entry] Connecting to logs for task: ${$selectedTask.id}`);
taskLogs.set([]); // Clear previous logs taskLogs.set([]); // Clear previous logs
const wsUrl = `ws://localhost:8000/ws/logs/${$selectedTask.id}`; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/logs/${$selectedTask.id}`;
ws = new WebSocket(wsUrl); ws = new WebSocket(wsUrl);
ws.onopen = () => { ws.onopen = () => {

View File

@@ -5,7 +5,7 @@
import { addToast } from './toasts.js'; import { addToast } from './toasts.js';
const API_BASE_URL = 'http://localhost:8000'; const API_BASE_URL = '';
// [DEF:fetchApi:Function] // [DEF:fetchApi:Function]
// @PURPOSE: Generic GET request wrapper. // @PURPOSE: Generic GET request wrapper.

View File

@@ -134,6 +134,13 @@ None
<section class="mb-8 bg-white p-6 rounded shadow"> <section class="mb-8 bg-white p-6 rounded shadow">
<h2 class="text-xl font-semibold mb-4">Superset Environments</h2> <h2 class="text-xl font-semibold mb-4">Superset Environments</h2>
{#if settings.environments.length === 0}
<div class="mb-4 p-4 bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700">
<p class="font-bold">Warning</p>
<p>No Superset environments configured. You must add at least one environment to perform backups or migrations.</p>
</div>
{/if}
<div class="mb-6 overflow-x-auto"> <div class="mb-6 overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200"> <table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50"> <thead class="bg-gray-50">

View File

@@ -4,4 +4,29 @@ import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [svelte()], plugins: [svelte()],
server: {
proxy: {
'/plugins': {
target: 'http://localhost:8000',
changeOrigin: true,
},
'/tasks': {
target: 'http://localhost:8000',
changeOrigin: true,
},
'/settings': {
target: 'http://localhost:8000',
changeOrigin: true,
},
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
'/ws': {
target: 'ws://localhost:8000',
ws: true,
},
},
},
}) })

155
run.sh Executable file
View File

@@ -0,0 +1,155 @@
#!/bin/bash
# Project Launch Script
# Automates setup and concurrent execution of backend and frontend servers.
set -e
# Default configuration
BACKEND_PORT=${BACKEND_PORT:-8000}
FRONTEND_PORT=${FRONTEND_PORT:-5173}
SKIP_INSTALL=false
# Help message
show_help() {
echo "Usage: ./run.sh [options]"
echo ""
echo "Options:"
echo " --help Show this help message"
echo " --skip-install Skip dependency checks and installation"
echo ""
echo "Environment Variables:"
echo " BACKEND_PORT Port for the backend server (default: 8000)"
echo " FRONTEND_PORT Port for the frontend server (default: 5173)"
}
# Parse arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
--help) show_help; exit 0 ;;
--skip-install) SKIP_INSTALL=true ;;
*) echo "Unknown parameter passed: $1"; show_help; exit 1 ;;
esac
shift
done
echo "Starting Project Launch Script..."
# Environment validation
validate_env() {
echo "Validating environment..."
if ! command -v python3 &> /dev/null; then
echo "Error: python3 is not installed."
exit 1
fi
if ! python3 -c 'import sys; exit(0) if sys.version_info >= (3, 9) else exit(1)'; then
PYTHON_VERSION=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
echo "Error: python3 version 3.9 or higher is required. Found $PYTHON_VERSION"
exit 1
fi
if ! command -v npm &> /dev/null; then
echo "Error: npm is not installed."
exit 1
fi
PYTHON_VERSION=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
echo "Environment validation passed (Python $PYTHON_VERSION, npm $(npm -v))"
}
validate_env
# Backend dependency management
setup_backend() {
if [ "$SKIP_INSTALL" = true ]; then
echo "Skipping backend installation..."
return
fi
echo "Setting up backend..."
cd backend
if [ ! -d ".venv" ]; then
echo "Creating virtual environment..."
python3 -m venv .venv
fi
source .venv/bin/activate
if [ -f "requirements.txt" ]; then
echo "Installing backend dependencies..."
pip install -r requirements.txt
else
echo "Warning: backend/requirements.txt not found."
fi
cd ..
}
# Frontend dependency management
setup_frontend() {
if [ "$SKIP_INSTALL" = true ]; then
echo "Skipping frontend installation..."
return
fi
echo "Setting up frontend..."
cd frontend
if [ ! -d "node_modules" ]; then
echo "Installing frontend dependencies..."
npm install
else
echo "frontend/node_modules already exists. Skipping npm install."
fi
cd ..
}
setup_backend
setup_frontend
# Cleanup function for graceful shutdown
cleanup() {
echo ""
echo "Stopping services..."
if [ -n "$BACKEND_PID" ]; then
kill $BACKEND_PID 2>/dev/null || true
fi
if [ -n "$FRONTEND_PID" ]; then
kill $FRONTEND_PID 2>/dev/null || true
fi
echo "Services stopped."
exit 0
}
# Trap SIGINT (Ctrl+C)
trap cleanup SIGINT
# Start Backend
start_backend() {
echo -e "\033[0;34m[Backend]\033[0m Starting on port $BACKEND_PORT..."
cd backend
if [ -f ".venv/bin/activate" ]; then
source .venv/bin/activate
else
echo -e "\033[0;31m[Backend]\033[0m Warning: .venv/bin/activate not found. Attempting to run without venv."
fi
# Use a subshell to prefix output
python3 -m uvicorn src.app:app --reload --port "$BACKEND_PORT" 2>&1 | sed "s/^/$(echo -e '\033[0;34m[Backend]\033[0m ') /" &
BACKEND_PID=$!
cd ..
}
# Start Frontend
start_frontend() {
echo -e "\033[0;32m[Frontend]\033[0m Starting on port $FRONTEND_PORT..."
cd frontend
# Use a subshell to prefix output
npm run dev -- --port "$FRONTEND_PORT" 2>&1 | sed "s/^/$(echo -e '\033[0;32m[Frontend]\033[0m ') /" &
FRONTEND_PID=$!
cd ..
}
start_backend
start_frontend
echo "Services are running. Press Ctrl+C to stop."
wait

View File

@@ -36,6 +36,7 @@ A new class `ConfigManager` in `backend/src/core/config_manager.py` will handle:
- CRUD operations for environments. - CRUD operations for environments.
- Updating global settings. - Updating global settings.
- Validating backup paths and Superset URLs. - Validating backup paths and Superset URLs.
- Enforcing system invariants (e.g., at least one environment configured).
### 1.3 API Endpoints ### 1.3 API Endpoints

View File

@@ -40,9 +40,9 @@ As an administrator, I want to configure the file path or storage location for b
### Edge Cases ### Edge Cases
- **Duplicate Environments**: What happens when a user tries to add an environment with a name that already exists? (System should prevent duplicates). - **Duplicate Environments**: System MUST prevent adding an environment with a name that already exists.
- **Invalid Credentials**: How does the system handle saving environments with incorrect credentials? (System should ideally validate connection on save). - **Invalid Credentials**: System MUST validate connection on save and prevent saving if credentials are invalid.
- **Path Permissions**: How does the system handle a backup path that is valid but the application lacks write permissions for? (System should check write permissions). - **Path Permissions**: System MUST verify write permissions for the backup path and display an error if inaccessible.
## Requirements *(mandatory)* ## Requirements *(mandatory)*
@@ -56,6 +56,7 @@ As an administrator, I want to configure the file path or storage location for b
- **FR-006**: System MUST validate the Superset URL format before saving. - **FR-006**: System MUST validate the Superset URL format before saving.
- **FR-007**: System MUST verify that the Backup Storage Path is writable by the application. - **FR-007**: System MUST verify that the Backup Storage Path is writable by the application.
- **FR-008**: System MUST allow selecting a "Default" environment for operations. - **FR-008**: System MUST allow selecting a "Default" environment for operations.
- **FR-009**: System MUST enforce that at least one environment is configured before allowing Superset-related tasks (e.g., backup, migration).
### System Invariants (Constitution Check) ### System Invariants (Constitution Check)
@@ -65,7 +66,7 @@ As an administrator, I want to configure the file path or storage location for b
### Key Entities *(include if feature involves data)* ### Key Entities *(include if feature involves data)*
- **Environment**: Represents a Superset instance. Attributes: Unique ID, Name, URL, Credentials, IsDefault flag. - **Environment**: Represents a Superset instance. Attributes: Unique ID, Name, URL, Credentials, IsDefault flag.
- **AppConfiguration**: Singleton entity representing global settings. Attributes: BackupPath, DefaultEnvironmentID. - **AppConfig**: Singleton entity representing global settings. Attributes: BackupPath, DefaultEnvironmentID.
## Success Criteria *(mandatory)* ## Success Criteria *(mandatory)*

View File

@@ -85,6 +85,7 @@ description: "Task list for implementing the web application settings mechanism"
- [x] T019 [P] Add password masking in `backend/src/api/routes/settings.py` and UI - [x] T019 [P] Add password masking in `backend/src/api/routes/settings.py` and UI
- [x] T020 [P] Add "Settings" link to navigation in `frontend/src/App.svelte` - [x] T020 [P] Add "Settings" link to navigation in `frontend/src/App.svelte`
- [x] T021 [P] Documentation updates for settings mechanism in `docs/` - [x] T021 [P] Documentation updates for settings mechanism in `docs/`
- [x] T022 [US1] Enforce INV-002 (at least one environment) in `backend/src/core/config_manager.py` and UI
--- ---

View File

@@ -0,0 +1,34 @@
# Specification Quality Checklist: Project Launch Script
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2025-12-20
**Feature**: [Link to spec.md](../spec.md)
## Content Quality
- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed
## Requirement Completeness
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification
## Notes
- Validated against the updated spec.

View File

@@ -0,0 +1,28 @@
# CLI Contract: Project Launch Script
## Command
`./run.sh [options]`
## Arguments
| Argument | Description | Default |
|----------|-------------|---------|
| `--help` | Show help message | N/A |
| `--skip-install` | Skip dependency checks and installation | false |
## Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `BACKEND_PORT` | Port for the backend server | 8000 |
| `FRONTEND_PORT` | Port for the frontend server | 5173 |
## Exit Codes
| Code | Meaning |
|------|---------|
| 0 | Success (on graceful shutdown) |
| 1 | Missing dependencies (python/npm) |
| 2 | Installation failure |
| 130 | Terminated by SIGINT (Ctrl+C) |

View File

@@ -0,0 +1,26 @@
# Data Model: Project Launch Script
## Entities
N/A - This feature is a utility script and does not involve persistent data storage or complex data structures.
## Process State
The script manages the lifecycle of two primary processes:
1. **Backend Process**:
- Command: `python3 -m uvicorn src.app:app`
- Port: 8000 (default)
- Environment: Python Virtual Environment (`.venv`)
2. **Frontend Process**:
- Command: `npm run dev`
- Port: 5173 (default)
- Environment: Node.js / `node_modules`
## Validation Rules
- `python3` must be version 3.9 or higher.
- `npm` must be available.
- `backend/requirements.txt` must exist.
- `frontend/package.json` must exist.

View File

@@ -0,0 +1,72 @@
# Implementation Plan: Project Launch Script
**Branch**: `003-project-launch-script` | **Date**: 2025-12-20 | **Spec**: [specs/003-project-launch-script/spec.md](specs/003-project-launch-script/spec.md)
**Input**: Feature specification from `/specs/003-project-launch-script/spec.md`
## Summary
Create a root-level bash script (`run.sh`) to automate the setup and concurrent execution of the backend (FastAPI) and frontend (Svelte/Vite) development servers. The script will handle dependency checks, installation, and graceful termination of both processes.
## Technical Context
**Language/Version**: Python 3.9+, Node.js 18+
**Primary Dependencies**: `uvicorn`, `npm`, `bash`
**Storage**: N/A
**Testing**: Manual verification of service availability; `pytest` for backend logic if any.
**Target Platform**: Linux
**Project Type**: Web application (frontend + backend)
**Performance Goals**: Services accessible within 30 seconds.
**Constraints**: Must handle `SIGINT` (Ctrl+C) to kill all child processes.
**Scale/Scope**: Developer utility script.
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
| Gate | Status | Rationale |
|------|--------|-----------|
| Python 3.9+ | PASS | Backend uses Python 3.9+ |
| Node.js 18+ | PASS | Frontend uses Node.js 18+ |
| Project Structure | PASS | Follows `backend/`, `frontend/` structure |
| Executable from root | PASS | Planned as `./run.sh` |
**Post-Design Re-evaluation**: Design artifacts (research, data-model, contracts) confirm compliance with all project principles. No violations found.
## Project Structure
### Documentation (this feature)
```text
specs/003-project-launch-script/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
├── contracts/ # Phase 1 output
└── tasks.md # Phase 2 output
```
### Source Code (repository root)
```text
run.sh # New launch script
backend/
├── src/
│ └── app.py
└── requirements.txt
frontend/
├── src/
└── package.json
```
**Structure Decision**: Option 2: Web application. The script will reside in the root to orchestrate both directories.
## Complexity Tracking
> **Fill ONLY if Constitution Check has violations that must be justified**
| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| None | - | - |

View File

@@ -0,0 +1,39 @@
# Quickstart: Project Launch Script
## Prerequisites
- Linux/macOS environment
- Python 3.9+
- Node.js 18+
## Installation
No installation required. The script is part of the repository.
## Usage
1. Navigate to the project root:
```bash
cd ss-tools
```
2. Make the script executable (if not already):
```bash
chmod +x run.sh
```
3. Run the script:
```bash
./run.sh
```
## What it does
1. Checks for `python3` and `npm`.
2. Sets up a Python virtual environment in `backend/.venv` if it doesn't exist.
3. Installs backend dependencies from `backend/requirements.txt`.
4. Installs frontend dependencies if `frontend/node_modules` is missing.
5. Starts the backend server on port 8000.
6. Starts the frontend server on port 5173.
7. Streams logs from both services to the terminal.
8. Gracefully stops both services when you press `Ctrl+C`.

View File

@@ -0,0 +1,57 @@
# Research: Project Launch Script
## Decision: Bash Script with `trap` and Background Processes
### Rationale
A bash script is the most portable and lightweight way to meet the requirement of a single command (`./run.sh`) without introducing additional process management dependencies like `pm2` or `concurrently` (unless we want to use `npm` to run everything, but the user asked for a script).
### Alternatives Considered
1. **`concurrently` (NPM package)**:
- *Pros*: Easy to use, handles output well.
- *Cons*: Requires `npm install` before it can even run. The goal is a script that *handles* the install.
2. **`docker-compose`**:
- *Pros*: Perfect for multi-service orchestration.
- *Cons*: Overkill for a simple local dev environment; requires Docker to be installed and configured.
3. **Python script**:
- *Pros*: Better cross-platform support (Windows/Linux).
- *Cons*: Slightly more verbose for process management than bash on Linux.
## Technical Findings
### 1. Concurrent Execution & Graceful Shutdown
To run processes concurrently and handle Ctrl+C:
```bash
#!/bin/bash
# Cleanup function
cleanup() {
echo "Stopping services..."
kill $BACKEND_PID $FRONTEND_PID
exit
}
# Trap SIGINT (Ctrl+C)
trap cleanup SIGINT
# Start Backend
cd backend && python3 -m uvicorn src.app:app --reload --port 8000 &
BACKEND_PID=$!
# Start Frontend
cd frontend && npm run dev -- --port 5173 &
FRONTEND_PID=$!
# Wait for processes
wait
```
### 2. Dependency Checking
- **Backend**: Check for `venv`. If missing, create it and install requirements.
- **Frontend**: Check for `frontend/node_modules`. If missing, run `npm install`.
### 3. Environment Validation
- Check `command -v python3` and `command -v npm`.
## Best Practices
- Use colors for logs to distinguish between Backend and Frontend output.
- Use `set -e` to exit on error during setup, but disable it or handle it carefully when starting background processes.

View File

@@ -0,0 +1,54 @@
# Feature Specification: Project Launch Script
**Feature Branch**: `003-project-launch-script`
**Created**: 2025-12-20
**Status**: Draft
**Input**: User description: "давай создадим скрипт для запуска проекта"
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Launch Project (Priority: P1)
As a developer, I want to launch the entire project (backend and frontend) with a single command so that I can start working quickly without manually running multiple commands in different terminals.
**Why this priority**: This is the core functionality requested. It simplifies the development workflow.
**Independent Test**: Can be fully tested by running the script and verifying that both backend and frontend services are accessible.
**Acceptance Scenarios**:
1. **Given** the project is cloned and I am in the root directory, **When** I run the launch script, **Then** the script checks for dependencies, installs them if missing, and starts both backend and frontend servers.
2. **Given** the servers are running, **When** I press Ctrl+C, **Then** both backend and frontend processes terminate gracefully.
3. **Given** dependencies are missing, **When** I run the script, **Then** it installs them before starting the servers.
---
### Edge Cases
- What happens when a port is already in use? The underlying tools (uvicorn/vite) will likely fail or complain. The script should ideally show this output.
- What happens if `python3` or `npm` is missing? The script should fail with a clear error message.
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: The script MUST be executable from the project root (e.g., `./run.sh`).
- **FR-002**: The script MUST check if `python3` and `npm` are available in the environment.
- **FR-003**: The script MUST check for and install backend dependencies from `backend/requirements.txt` if they are missing or outdated.
- **FR-004**: The script MUST check for and install frontend dependencies from `frontend/package.json` if `node_modules` is missing.
- **FR-005**: The script MUST start the backend application server in development mode.
- **FR-006**: The script MUST start the frontend application server in development mode.
- **FR-007**: The script MUST run both backend and frontend processes concurrently.
- **FR-008**: The script MUST handle `SIGINT` (Ctrl+C) to terminate both processes gracefully.
### Key Entities *(include if feature involves data)*
N/A
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: Developers can start the full stack with 1 command.
- **SC-002**: Both backend and frontend services are accessible via their configured network ports within 30 seconds of running the script (assuming dependencies are installed).
- **SC-003**: 100% of child processes are terminated when the script is stopped.

View File

@@ -0,0 +1,135 @@
---
description: "Task list for Project Launch Script implementation"
---
# Tasks: Project Launch Script
**Input**: Design documents from `/specs/003-project-launch-script/`
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
**Tests**: Manual verification as per spec.md. No automated test suite requested for this utility script.
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
- Include exact file paths in descriptions
## Path Conventions
- **Web app**: `backend/src/`, `frontend/src/`, `run.sh` at root
## Phase 1: Setup (Shared Infrastructure)
**Purpose**: Project initialization and basic structure
- [x] T001 Create `run.sh` with basic structure and `--help` message in `run.sh`
- [x] T002 [P] Implement environment validation for `python3` (3.9+) and `npm` in `run.sh`
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
- [x] T003 Implement backend dependency check and installation logic (venv + requirements.txt) in `run.sh`
- [x] T004 Implement frontend dependency check and installation logic (node_modules) in `run.sh`
- [x] T005 Implement `SIGINT` trap and `cleanup` function for graceful shutdown in `run.sh`
**Checkpoint**: Foundation ready - user story implementation can now begin
---
## Phase 3: User Story 1 - Launch Project (Priority: P1) 🎯 MVP
**Goal**: Launch backend and frontend concurrently with dependency management and graceful shutdown.
**Independent Test**: Run `./run.sh` from root. Verify both services start, are accessible on their ports, and both stop when Ctrl+C is pressed.
### Implementation for User Story 1
- [x] T006 [US1] Implement backend server startup logic with `BACKEND_PORT` support in `run.sh`
- [x] T007 [US1] Implement frontend server startup logic with `FRONTEND_PORT` support in `run.sh`
- [x] T008 [US1] Implement concurrent execution using background processes and `wait` in `run.sh`
- [x] T009 [US1] Implement `--skip-install` flag logic to bypass dependency checks in `run.sh`
**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently.
---
## Phase 4: Polish & Cross-Cutting Concerns
**Purpose**: Improvements that affect multiple user stories
- [x] T010 [P] Add color-coded logging to distinguish between backend and frontend output in `run.sh`
- [x] T011 [P] Update project `README.md` with `run.sh` usage instructions
- [x] T012 Run `quickstart.md` validation for `run.sh`
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies - can start immediately
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
- **User Stories (Phase 3+)**: All depend on Foundational phase completion
- **Polish (Final Phase)**: Depends on all desired user stories being complete
### User Story Dependencies
- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories
### Within Each User Story
- Core implementation before integration
- Story complete before moving to next priority
### Parallel Opportunities
- T002 can run in parallel with T001 (though both edit `run.sh`, they are logically independent)
- T010, T011 can run in parallel
---
## Parallel Example: User Story 1
```bash
# Implementation tasks for User Story 1 are mostly sequential in run.sh
# but can be developed in separate blocks:
Task: "Implement backend server startup logic with BACKEND_PORT support in run.sh"
Task: "Implement frontend server startup logic with FRONTEND_PORT support in run.sh"
```
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. Complete Phase 1: Setup
2. Complete Phase 2: Foundational (CRITICAL - blocks all stories)
3. Complete Phase 3: User Story 1
4. **STOP and VALIDATE**: Test User Story 1 independently
5. Deploy/demo if ready
### Incremental Delivery
1. Complete Setup + Foundational → Foundation ready
2. Add User Story 1 → Test independently → Deploy/Demo (MVP!)
3. Each story adds value without breaking previous stories
---
## Notes
- [P] tasks = different files or independent logic blocks
- [Story] label maps task to specific user story for traceability
- Each user story should be independently completable and testable
- Commit after each task or logical group
- Stop at any checkpoint to validate story independently