Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Changelog

## 2026-03-08 — Fix failed investigation counter reporting

### Bug Fixes

- **investigate: correctly count and report failed investigations** — In both sequential (`--parallel=1`) and batched (`--parallel=N`) modes, the `failed` counter was only incremented when the API call to start an investigation failed, not when the Temporal workflow itself completed with a `Failed` status. This caused the summary to report `0 failed` even when workflows had failed.
- **investigate: show warning icon on failure summary** — The summary line now displays `⚠` instead of `✓` when any investigations failed.

---

## 2026-03-08 — Doctor and DynamoDB credential fixes, auto-sync repos.json

### Bug Fixes

- **doctor: fix Anthropic API key reported as NOT SET for Docker installs** — `checkProviderCredentials()` was fetching environment variables exclusively from the API endpoint (`/workers/worker-1/env?reveal=true`), which returns empty results for Docker-based installations. The fix applies the same Docker-aware pattern already used by `checkWorkerEnv()`: detecting the install type and reading from the local `worker.env` file via `bootstrap.ReadWorkerEnvFile()` for Docker installs, falling back to the API endpoint for source installs.

- **fix DynamoDB not connecting for Docker installs** — The `TemporalComposeLocal()` docker-compose template was missing `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` in both the API and worker service environments. The local DynamoDB container requires dummy AWS credentials to accept connections. Added `AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-local}` and `AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-local}` to both services.

### Features

- **auto-sync repos from repos.json** — When `investigate --all` finds zero registered repos, it now automatically syncs from `repos.json` (checked in order: `~/.reposwarm/repos.json` local override → Docker container `/app/prompts/repos.json` → source install `<installDir>/worker/prompts/repos.json`). Zero-touch setup for fresh Docker installs.

---

## 2026-03-08 — Bulk terminate and workflow prune fix

### Changes

- `internal/commands/workflows.go` — Added `--all` flag to `workflows terminate`. Running `reposwarm workflows terminate --all --yes` now terminates all running workflows without interactive prompts. Previously each workflow had to be terminated individually.
- `internal/commands/workflows_prune.go` — Fixed `workflows prune` to actually delete workflows from Temporal history using `DELETE /workflows/<id>` instead of just re-terminating them. Previously prune reported success but workflows remained in the list.

### Usage

```
reposwarm workflows terminate --all --yes # Terminate all running workflows
reposwarm workflows prune --yes # Delete old completed/failed/terminated workflows
```

---

## 2026-03-08 — Sequential investigation mode (`--parallel` flag)

### Problem

Running `reposwarm investigate --all` on resource-constrained machines (16GB RAM) caused Temporal deadlock errors (`TMPRL1101`). The CLI fired `POST /investigate/single` for all repos simultaneously, starting 7+ concurrent Temporal workflows. The worker had no concurrency limits, so all repos cloned and analyzed in parallel, saturating I/O/CPU and blocking the Temporal event loop for >2 seconds.

### Solution

Single control point: the `--parallel` flag on `investigate --all` now controls both CLI dispatch behaviour and worker concurrency. No separate env vars to remember.

```
reposwarm investigate --all # Unchanged (fire-and-forget, parallel)
reposwarm investigate --all --parallel=1 # Sequential: one repo at a time
reposwarm investigate --all --parallel=2 # Batched: two repos at a time
```

When `--parallel` is set, the CLI:
1. Writes `REPOSWARM_PARALLEL=N` to `worker.env` (skips if already set)
2. Restarts the worker to apply the new concurrency limit (only if value changed)
3. Checks for running workflows before restarting to avoid killing in-flight work
4. Dispatches repos sequentially (or in batches of N), polling for completion between each

### Changes

**lac-reposwarm-cli (Go CLI):**

- `internal/commands/investigate.go` — Rewrote `--all` loop with three modes: sequential (`--parallel=0` or `1`), batched (`--parallel=N`), and fire-and-forget (no flag, unchanged). Updated help text with examples.
- `internal/commands/investigate_helpers.go` — Added `waitForWorkflow()` (polls `GET /workflows/<id>` until terminal state) and `ensureWorkerParallel()` (writes env var, restarts worker if changed, aborts if workflows are running).
- `internal/api/types.go` — Added `InvestigateResponse` struct to capture `workflowId` from `POST /investigate/single`.

**lac-repo-swarm (Python worker):**

- `src/investigate_worker.py` — Reads `REPOSWARM_PARALLEL` env var and passes `max_concurrent_activities` / `max_concurrent_workflow_task_polls` to the Temporal `Worker()` constructor. Default (unset/0) = unlimited, preserving cloud behaviour.
- `.env.example` — Documented `REPOSWARM_PARALLEL` with note that it is managed by the CLI.
6 changes: 6 additions & 0 deletions internal/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ type InvestigateRequest struct {
ChunkSize int `json:"chunk_size"`
}

// InvestigateResponse from POST /investigate/single.
type InvestigateResponse struct {
WorkflowID string `json:"workflowId"`
Message string `json:"message"`
}

// InvestigateDailyRequest for POST /investigate/daily.
type InvestigateDailyRequest struct {
Model string `json:"model,omitempty"`
Expand Down
4 changes: 4 additions & 0 deletions internal/bootstrap/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,8 @@ services:
- TEMPORAL_HTTP_URL=http://temporal-ui:8080
- TEMPORAL_NAMESPACE=default
- AWS_REGION=${AWS_REGION:-us-east-1}
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-local}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-local}
- DYNAMODB_ENDPOINT=http://dynamodb-local:8000
- DYNAMODB_TABLE=${DYNAMODB_TABLE:-reposwarm-cache}
- REPOSWARM_INSTALL_DIR=/data
Expand Down Expand Up @@ -704,6 +706,8 @@ services:
- TEMPORAL_NAMESPACE=default
- TEMPORAL_TASK_QUEUE=investigate-task-queue
- AWS_REGION=${AWS_REGION:-us-east-1}
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-local}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-local}
- DYNAMODB_ENDPOINT=http://localhost:8000
- DYNAMODB_TABLE=${DYNAMODB_TABLE:-reposwarm-cache}
- LOCAL_TESTING=true
Expand Down
40 changes: 28 additions & 12 deletions internal/commands/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -905,20 +905,36 @@ func checkProviderCredentials(priorChecks []checkResult) []checkResult {
provider := cfg.EffectiveProvider()
pc := cfg.ProviderConfig

// Fetch worker env for validation
var envResp struct {
Entries []struct {
Key string `json:"key"`
Value string `json:"value"`
Set bool `json:"set"`
} `json:"entries"`
}
// Fetch worker env for validation — use worker.env file for Docker installs, API for source installs
isDockerEnv := cfg.IsDockerInstall() || bootstrap.IsDockerInstall(cfg.EffectiveInstallDir())

currentEnv := make(map[string]string)
if err := client.Get(ctx(), "/workers/worker-1/env?reveal=true", &envResp); err == nil {
for _, e := range envResp.Entries {
if e.Set {
currentEnv[e.Key] = e.Value
if isDockerEnv {
workerEnv, _ := bootstrap.ReadWorkerEnvFile(cfg.EffectiveInstallDir())
containerEnv, _ := bootstrap.DockerServiceEnv(cfg.EffectiveInstallDir(), "worker")
for k, v := range workerEnv {
if v != "" {
currentEnv[k] = v
}
}
for k, v := range containerEnv {
if v != "" {
currentEnv[k] = v
}
}
} else {
var envResp struct {
Entries []struct {
Key string `json:"key"`
Value string `json:"value"`
Set bool `json:"set"`
} `json:"entries"`
}
if err := client.Get(ctx(), "/workers/worker-1/env?reveal=true", &envResp); err == nil {
for _, e := range envResp.Entries {
if e.Set {
currentEnv[e.Key] = e.Value
}
}
}
}
Expand Down
Loading