diff --git a/docs/LOCAL-DEVELOPMENT.md b/docs/LOCAL-DEVELOPMENT.md index b1430d5..16c5b39 100644 --- a/docs/LOCAL-DEVELOPMENT.md +++ b/docs/LOCAL-DEVELOPMENT.md @@ -1,13 +1,13 @@ # Local development without Docker Compose -This Windows-only helper runs the API, analytics worker, and frontend directly from the three sibling repositories. PostgreSQL and Redis must already be reachable locally. It does not create databases, run migrations/imports, generate credentials, edit service-owned environment files, or start Docker. +This Windows-only helper runs the API, analytics worker, growth-coach scheduler, and frontend directly from the three sibling repositories. PostgreSQL and Redis must already be reachable locally. It does not create databases, run migrations/imports, generate credentials, edit service-owned environment files, or start Docker. ## Prerequisites - Go and npm are on `PATH`. - PostgreSQL is reachable at `127.0.0.1:5432`. - Redis is reachable at `127.0.0.1:6379`. -- The backend has `cmd/api` and `cmd/worker`, plus a protected backend-owned `.env`. +- The backend has `cmd/api`, `cmd/worker`, and `cmd/scheduler`, plus a protected backend-owned `.env`. - The frontend has a `package.json` `dev` script and its frontend-owned local environment file. - Dependencies, migrations, the one-time importer, and demo merchant creation have already completed through their owning repositories. @@ -41,6 +41,6 @@ After `start`, open the frontend URL declared by the frontend development server The AI growth analyst is controlled by service-owned environment files; no AvalAI credential belongs in this deployment repository or in the frontend. -For local deterministic verification, set `AI_GROWTH_ENABLED=true` in the ignored backend `.env` and `PUBLIC_AI_GROWTH_ENABLED=true` in the ignored frontend `.env`. With `AVALAI_API_KEY` empty, the worker must not make a provider request: it persists and serves a deterministic `PARTIAL` report instead. +For local verification, set `AI_COACH_ENABLED=true`, `AI_PAGE_INSIGHTS_ENABLED=true`, and `AI_GROWTH_ENABLED=true` in the ignored backend `.env`, and `PUBLIC_AI_GROWTH_ENABLED=true` in the ignored frontend `.env`. With `AVALAI_API_KEY` empty, no generation is misrepresented as a fresh AI response. The backend `.env.example` is authoritative for the complete contract. The production operator must explicitly provide `AVALAI_API_KEY`, `AVALAI_MODEL`, `AI_DAILY_REQUEST_BUDGET`, and `AI_DAILY_TOKEN_BUDGET` before enabling the feature. `AVALAI_BASE_URL`, `AI_MAX_CONCURRENT`, `AI_CREATE_PER_MINUTE`, and `AI_UNCACHED_SCOPES_PER_DAY` remain configurable safety limits. Never place `AVALAI_API_KEY` in a public frontend variable. diff --git a/scripts/LocalDev.psm1 b/scripts/LocalDev.psm1 index beafa3a..4867b8a 100644 --- a/scripts/LocalDev.psm1 +++ b/scripts/LocalDev.psm1 @@ -39,7 +39,7 @@ function Resolve-LocalDevCommands { [Parameter(Mandatory)][string]$FrontendPath ) - foreach ($entrypoint in 'api', 'worker') { + foreach ($entrypoint in 'api', 'worker', 'scheduler') { $path = Join-Path $BackendPath "cmd\$entrypoint" if (-not (Test-Path -LiteralPath $path -PathType Container)) { throw "Backend entrypoint cmd/$entrypoint is missing under $BackendPath" @@ -58,6 +58,7 @@ function Resolve-LocalDevCommands { return @{ Api = @{ FilePath = 'go'; Arguments = @('run', './cmd/api'); WorkingDirectory = $BackendPath } Worker = @{ FilePath = 'go'; Arguments = @('run', './cmd/worker'); WorkingDirectory = $BackendPath } + Scheduler = @{ FilePath = 'go'; Arguments = @('run', './cmd/scheduler'); WorkingDirectory = $BackendPath } Frontend = @{ FilePath = 'npm.cmd'; Arguments = @('run', 'dev', '--', '--host', '127.0.0.1'); WorkingDirectory = $FrontendPath } } } @@ -188,6 +189,7 @@ function Start-LocalDev { try { $started += Start-LocalProcess -Name 'api' -Command $commands.Api -Environment $backendEnvironment -LogDirectory $logDirectory $started += Start-LocalProcess -Name 'worker' -Command $commands.Worker -Environment $backendEnvironment -LogDirectory $logDirectory + $started += Start-LocalProcess -Name 'scheduler' -Command $commands.Scheduler -Environment $backendEnvironment -LogDirectory $logDirectory $started += Start-LocalProcess -Name 'frontend' -Command $commands.Frontend -Environment @{} -LogDirectory $logDirectory $started | ConvertTo-Json | Set-Content -LiteralPath (Get-StatePath -DeploymentPath $DeploymentPath) -Encoding utf8 return $started diff --git a/tests/local-dev.tests.ps1 b/tests/local-dev.tests.ps1 index 5329385..78a9ce5 100644 --- a/tests/local-dev.tests.ps1 +++ b/tests/local-dev.tests.ps1 @@ -55,12 +55,14 @@ try { $frontend = Join-Path $fixture 'frontend' New-Item -ItemType Directory -Path (Join-Path $backend 'cmd\api') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $backend 'cmd\worker') -Force | Out-Null + New-Item -ItemType Directory -Path (Join-Path $backend 'cmd\scheduler') -Force | Out-Null New-Item -ItemType Directory -Path $frontend -Force | Out-Null '{"scripts":{"dev":"vite"}}' | Set-Content -LiteralPath (Join-Path $frontend 'package.json') -Encoding utf8 $commands = Resolve-LocalDevCommands -BackendPath $backend -FrontendPath $frontend Assert-Equal './cmd/api' $commands.Api.Arguments[1] 'API command should follow the backend contract' Assert-Equal './cmd/worker' $commands.Worker.Arguments[1] 'Worker command should follow the backend contract' + Assert-Equal './cmd/scheduler' $commands.Scheduler.Arguments[1] 'Scheduler command should follow the backend contract' Assert-Equal 'run' $commands.Frontend.Arguments[0] 'Frontend should use its checked-in dev script' $stateDirectory = Join-Path $fixture '.local'