@osfactory/har 0.5.0 → 0.7.0

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.
Files changed (36) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +1817 -651
  3. package/dist/prompts/system-authoring.md +27 -2
  4. package/dist/templates/adaptation-prompt-init.md +60 -5
  5. package/dist/templates/adaptation-prompt-maintain.md +7 -4
  6. package/dist/templates/har-boilerplate/CLAUDE.agent.md +5 -3
  7. package/dist/templates/har-boilerplate/README.md +51 -9
  8. package/dist/templates/har-boilerplate/agent-cli.sh +72 -26
  9. package/dist/templates/har-boilerplate/agent-slot.sh +351 -0
  10. package/dist/templates/har-boilerplate/attach.sh +7 -1
  11. package/dist/templates/har-boilerplate/ecosystem.agent.template.cjs +3 -1
  12. package/dist/templates/har-boilerplate/env.template +4 -1
  13. package/dist/templates/har-boilerplate/harness.env +32 -0
  14. package/dist/templates/har-boilerplate/launch.sh +167 -120
  15. package/dist/templates/har-boilerplate/preflight.sh +41 -0
  16. package/dist/templates/har-boilerplate/provision-toolchain.sh +285 -0
  17. package/dist/templates/har-boilerplate/setup-infra.sh +87 -11
  18. package/dist/templates/har-boilerplate/teardown.sh +2 -1
  19. package/dist/templates/har-boilerplate/verify.sh +91 -11
  20. package/dist/templates/har-boilerplate-cli/CLAUDE.agent.md +5 -2
  21. package/dist/templates/har-boilerplate-cli/README.md +40 -6
  22. package/dist/templates/har-boilerplate-cli/agent-cli.sh +1 -1
  23. package/dist/templates/har-boilerplate-cli/agent-slot.sh +331 -0
  24. package/dist/templates/har-boilerplate-cli/harness.env +13 -0
  25. package/dist/templates/har-boilerplate-cli/launch.sh +140 -95
  26. package/dist/templates/har-boilerplate-cli/preflight.sh +37 -0
  27. package/dist/templates/har-boilerplate-cli/provision-toolchain.sh +285 -0
  28. package/dist/templates/har-boilerplate-cli/setup-infra.sh +87 -11
  29. package/dist/templates/har-boilerplate-cli/verify.sh +93 -12
  30. package/dist/templates/har-boilerplate-ios/README.md +29 -2
  31. package/dist/templates/har-boilerplate-ios/harness.env +6 -1
  32. package/dist/templates/har-boilerplate-ios/launch.sh +8 -13
  33. package/dist/templates/har-boilerplate-ios/provision-toolchain.sh +285 -0
  34. package/dist/templates/har-boilerplate-ios/verify.sh +20 -20
  35. package/package.json +1 -1
  36. package/dist/templates/har-boilerplate-ios/ADAPT-PROMPT.md +0 -57
@@ -104,12 +104,36 @@ If the repository contains more than one project or `.har` harness (check for `.
104
104
  - Keep a small `AGENT.md` / `CLAUDE.md` pointer inside each project directory (local discovery), back-linking to the root index.
105
105
  - One Cursor rule at repo root listing all harnesses — never one rule per project.
106
106
 
107
- ## Port allocation
107
+ ## Port allocation & shared services
108
108
 
109
- Agents run in parallel on configurable slot ids. Ports: `BASE + (AGENT_ID × 10)`.
109
+ Document ports in `.har/README.md` and configure them in `.har/harness.env`. Resolved values land in `.env.agent.<id>` and `.har/slots/agent-<id>.json` at launch never hardcode defaults in app code or tests.
110
+
111
+ ### Per-slot app ports (default / PM2 profile)
112
+
113
+ | Layer | Scope | Rule | On conflict |
114
+ |-------|-------|------|-------------|
115
+ | Frontend | Per slot | `HARNESS_FE_BASE_PORT + (AGENT_ID × HARNESS_PORT_STEP)` | Scan `STEP` increments within the slot lane |
116
+ | API | Per slot | `HARNESS_API_BASE_PORT + (AGENT_ID × STEP)` | Same scan policy |
117
+ | Node debug | Per slot | `9200 + (AGENT_ID × STEP)` | Same scan policy |
118
+
119
+ ### Shared infra ports (when listed in `HARNESS_INFRA_SERVICES`)
120
+
121
+ | Service | Default var | On conflict |
122
+ |---------|-------------|-------------|
123
+ | Postgres | `HARNESS_DB_PORT_DEFAULT` | Scan `HARNESS_DB_PORT_SCAN_START..END` |
124
+ | MinIO | `HARNESS_MINIO_PORT_DEFAULT` (+ console) | Scan configured ranges in `harness.env` |
125
+ | Mailpit | `HARNESS_MAILPIT_*_PORT_DEFAULT` | Scan configured ranges |
126
+ | Headless browser | `HARNESS_BROWSER_PORT_DEFAULT` | Scan configured ranges |
127
+
128
+ CLI and iOS profiles typically have no per-slot app ports; optional backends still use the shared-infra model.
110
129
 
111
130
  Set slot limits in `.har/stages.json` (`agentSlots`) and `.har/harness.env` (`HARNESS_AGENT_SLOT_MIN` / `HARNESS_AGENT_SLOT_MAX`) based on machine capacity.
112
131
 
132
+ ### Do not
133
+
134
+ - Hardcode `3000`, `15432`, `3847`, or other defaults in application code, tests, or agent docs
135
+ - Run raw `docker compose` for harness infrastructure — use `setup-infra.sh` / `launch.sh`
136
+
113
137
  ## Rules
114
138
 
115
139
  1. **Edit .har/ files directly** — no YAML runtime config
@@ -120,6 +144,7 @@ Set slot limits in `.har/stages.json` (`agentSlots`) and `.har/harness.env` (`HA
120
144
  6. **Do not edit manifest.json** — managed by har CLI
121
145
  7. **Run the cleanup checklist before finishing** — keep strictly what this repository needs:
122
146
  - compose file has only used services; `HARNESS_INFRA_SERVICES` matches exactly
147
+ - `harness.env` has port documentation vars for every enabled infra service; `.har/README.md` explains allocation and shared vs per-slot
123
148
  - `env.template` has no blocks for removed services; no dead branches left in scripts
124
149
  - unused harness files deleted (`deleteHarnessFile`), e.g. `attach.sh` when unused
125
150
  - `.har/README.md` file table matches the files that actually exist
@@ -6,6 +6,24 @@ Explore this repository, then edit files in `.har/` directly to make the harness
6
6
 
7
7
  **Do NOT** create a YAML config or JSON mapping file for runtime behavior. Put behavior directly in the harness scripts and templates.
8
8
 
9
+ ## HAR profiles (pick the right one at init)
10
+
11
+ `har env init` scaffolds from one of three boilerplate profiles — **choose the profile that matches this repository**:
12
+
13
+ | Profile | Best for | What you get |
14
+ |---------|----------|--------------|
15
+ | `default` | **SaaS / web apps** (Next.js, Rails, full-stack, etc.) | Docker Compose shared infra, PM2 for primary app, per-slot ports and preview URLs |
16
+ | `cli` | **CLI tools, libraries, test-suite repos** (typical SWE-bench) | Git worktree by default, no PM2; optional Docker for databases; run project commands in isolation |
17
+ | `ios` | **Mobile iOS / Swift** | xcodebuild + iOS Simulator; scheme/project/simulator in `harness.env` |
18
+
19
+ ```bash
20
+ har env init # default (SaaS/web)
21
+ har env init --profile cli # libraries / CLI / polyglot test repos
22
+ har env init --profile ios # iOS apps
23
+ ```
24
+
25
+ Do **not** disable worktrees, rewrite launch into repo-root-only mode, or pick the wrong profile unless the project truly requires it.
26
+
9
27
  ## Profile: {{PROFILE}}
10
28
 
11
29
  {{PROFILE_HINT}}
@@ -38,13 +56,18 @@ Clear index of the harness: what each file does, quick start, architecture, how
38
56
  ### `.har/harness.env`
39
57
  Primary app, ports, `HARNESS_INFRA_SERVICES`, migrate/seed commands, health check path.
40
58
 
59
+ **Toolchain provisioning:** set `HARNESS_ECOSYSTEM` (`auto` detects from manifests) and optional `HARNESS_INSTALL_CMD`. Launch runs `provision-toolchain.sh`, writes resolved paths (`PYTHON_BIN`, `NODE_BIN`, `NPM_BIN`, `XCODEBUILD_BIN`, …) into `.env.agent.<id>`. Verify steps **must** use those paths — never hardcode venv or interpreter locations.
60
+
41
61
  ### `.har/ecosystem.agent.template.cjs` (default profile only)
42
62
  PM2 processes for the primary application only, matching how it runs in dev. Skip entirely for the CLI profile.
43
63
 
44
64
  ### `.har/verify.sh`
45
65
  Adapt verification for this repository's toolchain. Step lists in the template are
46
66
  **examples, not exhaustive** — add, remove, or reorder `run_step` calls to match
47
- how this project is built and tested.
67
+ how this project is built and tested. Use toolchain variables from `.env.agent.<id>`
68
+ (e.g. `${NPM_BIN:-npm}`, `${PYTHON_BIN:-python3}`, `${XCODEBUILD_BIN:-xcodebuild}`).
69
+ The stock verify section is keyed by `HARNESS_ECOSYSTEM`; it is a starting point,
70
+ not the repo's final contract. Replace conventions that do not match this project.
48
71
 
49
72
  **Tier contract:**
50
73
 
@@ -56,6 +79,7 @@ how this project is built and tested.
56
79
  - **Quick** must stay fast and minimal (syntax, compile, import smoke) — not the full test suite.
57
80
  - **Full** holds unit tests, lint, and heavier checks; optional Playwright runs on `--full` when installed.
58
81
  - Reuse real commands from `package.json`, `Makefile`, CI, `pyproject.toml`, etc.
82
+ - Remove stock npm/pytest/go/cargo/maven/gradle examples that do not apply.
59
83
  - Replace all TODO placeholders in both tiers.
60
84
 
61
85
  ### Readiness vs liveness (required)
@@ -88,8 +112,9 @@ scripts or document why no substitute is needed. In particular:
88
112
  choose and document an agent-friendly asset mode.
89
113
  - Put Layer 3 checks in `verify --full`, a project-owned readiness script, or
90
114
  documented smoke URLs. Health alone is not sufficient for UI/auth apps.
91
- - Ensure `launch.sh` writes the slot registry before slow or fragile steps, and
92
- `verify.sh` resolves env/work dir through `agent-slot.sh`.
115
+ - Ensure `launch.sh` writes the slot registry before slow or fragile steps, runs
116
+ `provision-toolchain.sh` to install deps and record toolchain paths in
117
+ `.env.agent.<id>`, and `verify.sh` resolves env/work dir through `agent-slot.sh`.
93
118
 
94
119
  ### Optional Playwright stage
95
120
  If the user ran `har env add-stage playwright` (or `@playwright/test` is in package.json):
@@ -107,10 +132,38 @@ Detailed agent instructions: commands, credentials, architecture, definition of
107
132
  ### `.har/env.template`, `setup-infra.sh`, `docker-compose.agent.yml`
108
133
  Adapt as needed for the project's infra.
109
134
 
110
- ### Port allocation
111
- Agents run in parallel on configurable slot ids. Ports: `BASE + (AGENT_ID × 10)`.
135
+ ### Port allocation & shared services
136
+
137
+ Document and configure ports in `.har/harness.env` and `.har/README.md`. Use the bundled template's port-allocation block as the contract — do not hardcode ports in app code or tests.
138
+
139
+ **Per-slot app ports** (default / PM2 profile only):
140
+
141
+ | Layer | Scope | Rule | On conflict |
142
+ |-------|-------|------|-------------|
143
+ | Frontend | Per slot | `HARNESS_FE_BASE_PORT + (AGENT_ID × HARNESS_PORT_STEP)` | Scan `STEP` increments within the slot lane |
144
+ | API | Per slot | `HARNESS_API_BASE_PORT + (AGENT_ID × STEP)` | Same scan policy |
145
+ | Node debug | Per slot | `9200 + (AGENT_ID × STEP)` | Same scan policy |
146
+
147
+ **Shared infra ports** (one per machine, all profiles when the service is in `HARNESS_INFRA_SERVICES`):
148
+
149
+ | Service | Default var | On conflict |
150
+ |---------|-------------|-------------|
151
+ | Postgres | `HARNESS_DB_PORT_DEFAULT` | Scan `HARNESS_DB_PORT_SCAN_START..END` |
152
+ | MinIO | `HARNESS_MINIO_PORT_DEFAULT` (+ console) | Scan configured ranges |
153
+ | Mailpit | `HARNESS_MAILPIT_*_PORT_DEFAULT` | Scan configured ranges |
154
+ | Headless browser | `HARNESS_BROWSER_PORT_DEFAULT` | Scan configured ranges |
155
+
112
156
  Set slot limits in `.har/stages.json` (`agentSlots`) and `.har/harness.env` (`HARNESS_AGENT_SLOT_MIN` / `HARNESS_AGENT_SLOT_MAX`) based on machine capacity.
113
157
 
158
+ **Port / infra checklist:**
159
+
160
+ - [ ] `.har/harness.env` has `HARNESS_FE_BASE_PORT`, `HARNESS_API_BASE_PORT`, `HARNESS_PORT_STEP` (default profile) or explains why they are absent (CLI/iOS)
161
+ - [ ] For each service in `HARNESS_INFRA_SERVICES`, matching `HARNESS_*_PORT_DEFAULT` and `SCAN_*` vars exist in `harness.env`
162
+ - [ ] `.har/README.md` has a **Port & shared services** section (allocation table, shared vs per-slot, do-not rules)
163
+ - [ ] App code and tests read ports from `.env.agent.<id>` / `agent-cli.sh` / slot registry — no hardcoded `3000`, `15432`, `3847`, etc.
164
+ - [ ] `env.template` and `CLAUDE.agent.md` show resolved ports via variables, not literals
165
+ - [ ] Monorepos with `har control up`: document slot-1 conflict if the app port overlaps (e.g. Mission Control on 3847)
166
+
114
167
  ### Git worktree
115
168
  `launch.sh` creates an isolated session worktree at `~/worktrees/<base>-<sha4>-har-agent-<id>-<rand4>` by default (`HARNESS_USE_WORKTREE=true`) and records it in `.har/slots/agent-<id>.json`. Agents should commit from that worktree, not the main checkout.
116
169
 
@@ -162,6 +215,8 @@ The boilerplate ships more than any single repository needs. Strip it down to st
162
215
  - [ ] If full seed/setup is skipped, `.har/` provides a minimal bootstrap or clearly documents why none is needed
163
216
  - [ ] All per-slot data stores are provisioned, not just the primary database
164
217
  - [ ] `.har/CLAUDE.agent.md` defines what "agent usable" means for this repo: login/API smoke, credentials, required default data, and known skipped dev-setup steps
218
+ - [ ] `.har/README.md` documents port allocation and shared-service model; `harness.env` has the port knobs for every enabled infra service
219
+ - [ ] No hardcoded default ports (`3000`, `15432`, `3847`, …) in app code, tests, or harness docs — use agent env / slot registry
165
220
 
166
221
  ## Rules
167
222
 
@@ -6,6 +6,8 @@ The harness already exists. Inspect what changed in the repo since the harness w
6
6
 
7
7
  **Do NOT** create a YAML config or JSON mapping file for runtime behavior. Put behavior directly in the harness scripts and templates.
8
8
 
9
+ {{MAINTAIN_BUNDLE_SECTION}}
10
+
9
11
  ## Step 1 — Inspect the repository
10
12
 
11
13
  Compare the current repo against the existing harness:
@@ -13,7 +15,7 @@ Compare the current repo against the existing harness:
13
15
  - Root manifests, CI, Docker, README
14
16
  - New or changed test, lint, build, migrate, or seed commands
15
17
  - New services, ports, or environment variables
16
- - Run `har env maintain` drift report (generator version, template checksum mismatches)
18
+ - Review `.har/maintain/drift-report.json` (generator version, template drift, **missing port documentation vars**)
17
19
 
18
20
  ## Step 2 — Update `.har/` files
19
21
 
@@ -22,8 +24,8 @@ Prefer targeted edits over full rewrites. Key files to review:
22
24
  ### `.har/README.md` (required)
23
25
  Keep this accurate — it is the harness index. Update whenever scripts, stages, or workflow change.
24
26
 
25
- ### `.har/harness.env`, `verify.sh`, `ecosystem.agent.template.cjs`, `CLAUDE.agent.md`
26
- Align commands and instructions with the current stack.
27
+ ### `.har/harness.env`, `verify.sh`, `provision-toolchain.sh`, `ecosystem.agent.template.cjs`, `CLAUDE.agent.md`
28
+ Align commands and instructions with the current stack. Verify steps must use toolchain paths from `.env.agent.<id>` (`PYTHON_BIN`, `NPM_BIN`, `XCODEBUILD_BIN`, …) — never hardcoded venv or interpreter paths. Replace stock ecosystem conventions that do not match the repository; do not leave npm/pytest/go/cargo/maven/gradle examples in place by accident.
27
29
 
28
30
  ### `.har/env.template`, `setup-infra.sh`, `docker-compose.agent.yml`
29
31
  Update only if infra changed.
@@ -64,9 +66,10 @@ When upgrading `@osfactory/har` or adopting new harness standards:
64
66
 
65
67
  **Generator 0.4.0 — primary app & shared infra services:**
66
68
 
67
- - Migrate `harness.env` from boolean `HARNESS_INFRA_*` flags to the `HARNESS_INFRA_SERVICES` list (space-separated compose service names, e.g. `"db mailpit"`) and add the `har_infra_enabled()` helper — copy both from the bundled template. Update every script that still tests `HARNESS_INFRA_POSTGRES`-style flags (`setup-infra.sh`, `launch.sh`, `teardown.sh`, `agent-cli.sh`) to use `har_infra_enabled <service>`.
69
+ - Migrate `harness.env` from boolean `HARNESS_INFRA_*` flags to the `HARNESS_INFRA_SERVICES` list (space-separated compose service names, e.g. `"db mailpit"`) and add the `har_infra_enabled()` helper — copy both from `.har/maintain/templates/harness.env`. Update every script that still tests `HARNESS_INFRA_POSTGRES`-style flags (`setup-infra.sh`, `launch.sh`, `teardown.sh`, `agent-cli.sh`) to use `har_infra_enabled <service>`.
68
70
  - Set `HARNESS_PRIMARY_APP` in `harness.env` to the ONE app agents modify. Ensure `ecosystem.agent.template.cjs` starts only that app's processes. Move any other in-repo services agents depend on but don't change to shared infra: compose services in `docker-compose.agent.yml` or an optional `.har/ecosystem.shared.config.cjs` (processes `har-shared-<name>`; `setup-infra.sh` starts it when present — resync `setup-infra.sh` from the template to get this hook).
69
71
  - Prune `docker-compose.agent.yml` to only the services this project uses; delete unused menu services and volumes.
72
+ - **Port & shared services:** ensure `.har/README.md` documents the allocation table and shared vs per-slot model; `harness.env` has every `HARNESS_*_PORT_*` var required for services in `HARNESS_INFRA_SERVICES` (copy missing vars from `.har/maintain/templates/harness.env` when drift reports them). Remove hardcoded ports from app code, tests, and `CLAUDE.agent.md`.
70
73
  - Run the **cleanup checklist**: no TODO placeholders, no env blocks for removed services in `env.template`, no dead script branches, `.har/README.md` file table matches the actual files, `CLAUDE.agent.md` shows only real URLs/ports and commands, unused files deleted.
71
74
 
72
75
  **Earlier standards:**
@@ -50,10 +50,12 @@ Quick loop during development: MCP `har_run_verification`, `har env verify ${AGE
50
50
  ## Project commands
51
51
 
52
52
  ```bash
53
- # TODO: adapt for this repository (see package.json, Makefile, etc.)
53
+ # TODO: adapt for this repository (see package.json, Makefile, pyproject.toml, CI, etc.)
54
54
  # npm run typecheck
55
- # npm test
56
- # npm run lint
55
+ # pytest -q
56
+ # go test ./...
57
+ # cargo test
58
+ # make test
57
59
  ```
58
60
 
59
61
  ## Do not
@@ -12,14 +12,15 @@ Generated and maintained by [`har`](https://github.com/antoineFrau/har). Run `ha
12
12
  |------|---------|
13
13
  | `README.md` | This file — index of the harness |
14
14
  | `manifest.json` | Generator metadata (version, checksums) — do not edit |
15
- | `harness.env` | Shared config: primary app, ports, agent slot limits, `HARNESS_INFRA_SERVICES`, migrate/seed commands |
15
+ | `harness.env` | Shared config: primary app, ports, agent slot limits, `HARNESS_INFRA_SERVICES`, toolchain provisioning, migrate/seed commands |
16
16
  | `stages.json` | Machine-readable registry of runnable harness stages |
17
17
  | `stages/` | Optional custom stage scripts registered from `stages.json` |
18
18
  | `runs/` | Run history from `har env` / MCP only — `.har/runs/YYYY-MM-DD/HH-mm-ss_<stageId>_agent-<id>.json` (gitignore) |
19
19
  | `artifacts/` | Stage outputs: reports, traces, screenshots, logs |
20
20
  | `agent-slot.sh` | Shared agent-id validation (reads limits from `harness.env`) |
21
21
  | `setup-infra.sh` | Start shared Docker infra + create template database |
22
- | `launch.sh` | Launch one agent slot (ports, DB clone, PM2 processes) |
22
+ | `launch.sh` | Launch one agent slot (ports, DB clone, toolchain provisioning, PM2 processes) |
23
+ | `provision-toolchain.sh` | Install deps and write toolchain paths to `.env.agent.<id>` |
23
24
  | `verify.sh` | Verification pipeline (smoke by default; --full adds tests, lint, e2e) |
24
25
  | `teardown.sh` | Tear down one agent slot |
25
26
  | `agent-cli.sh` | Manage a running agent (status, logs, psql, health) |
@@ -49,8 +50,8 @@ In Cursor with HAR MCP configured: use `har_launch_environment`, `har_run_verifi
49
50
  ```bash
50
51
  ./.har/setup-infra.sh # when HARNESS_INFRA_SERVICES is non-empty
51
52
  ./.har/launch.sh 1
52
- ./.har/verify.sh 1 # quick: smoke (typecheck + health)
53
- ./.har/verify.sh 1 --full # + unit tests, lint, browser-e2e (if Playwright stage installed)
53
+ ./.har/verify.sh 1 # quick: ecosystem smoke + health
54
+ ./.har/verify.sh 1 --full # + conventional tests/lint, browser-e2e (if installed)
54
55
  ./.har/teardown.sh 1
55
56
  ```
56
57
 
@@ -64,8 +65,13 @@ not a fixed command list.
64
65
 
65
66
  | Mode | Command | Typical steps |
66
67
  |------|---------|---------------|
67
- | Quick | `har env verify <id>` or `verify.sh <id>` | Smoke: compile / typecheck / health (stops early on failure) |
68
- | Full | `har env verify <id> --full` or `verify.sh <id> --full` | + unit tests, lint, optional readiness smoke + **`browser-e2e`** when `.har/stages/browser-e2e.sh` exists |
68
+ | Quick | `har env verify <id>` or `verify.sh <id>` | Stock ecosystem smoke + health (stops early on failure) |
69
+ | Full | `har env verify <id> --full` or `verify.sh <id> --full` | Stock conventional tests/lint, optional readiness smoke + **`browser-e2e`** when `.har/stages/browser-e2e.sh` exists |
70
+
71
+ The stock commands are deliberately generic conventions keyed by
72
+ `HARNESS_ECOSYSTEM`. Replace them with the repository's real commands during
73
+ adaptation; do not leave Node/npm, Python, Go, Rust, Java, or Ruby defaults in
74
+ place when they do not match the project.
69
75
 
70
76
  Install Playwright stage: `har env add-stage playwright` (optional). UI changes should add or update specs under `tests/`.
71
77
 
@@ -107,14 +113,48 @@ Always use `./.har/agent-cli.sh <id> ...` — never hardcoded ports.
107
113
 
108
114
  ## Architecture
109
115
 
110
- Each agent slot gets isolated ports: `BASE + (AGENT_ID × 10)`.
116
+ Each agent slot gets isolated app ports. Defaults follow `BASE + (AGENT_ID × HARNESS_PORT_STEP)`; when a default is busy, `launch.sh` scans the slot lane (`STEP` increments) and writes the resolved ports to `.env.agent.<id>` and `.har/slots/agent-<id>.json`.
111
117
 
112
118
  Configure how many slots your machine can run in parallel in `stages.json` (`agentSlots`) and `harness.env` (`HARNESS_AGENT_SLOT_MIN` / `HARNESS_AGENT_SLOT_MAX`). Keep both in sync.
113
119
 
114
- | Service | Agent 1 | Agent 2 |
115
- |---------|---------|---------|
120
+ | Service | Agent 1 (default) | Agent 2 (default) |
121
+ |---------|-------------------|-------------------|
116
122
  | Frontend | 3010 | 3020 |
117
123
  | API | 8010 | 8020 |
124
+ | Node debug | 9210 | 9220 |
125
+
126
+ ## Port & shared services
127
+
128
+ ### Port allocation
129
+
130
+ | Layer | Scope | Rule | On conflict |
131
+ |-------|-------|------|-------------|
132
+ | App — frontend | Per slot | `HARNESS_FE_BASE_PORT + (AGENT_ID × HARNESS_PORT_STEP)` | Scan `STEP` increments within the slot lane |
133
+ | App — API | Per slot | `HARNESS_API_BASE_PORT + (AGENT_ID × STEP)` | Same scan policy |
134
+ | Node debug | Per slot | `9200 + (AGENT_ID × STEP)` | Same scan policy |
135
+ | Shared Postgres | Per machine | `HARNESS_DB_PORT_DEFAULT` | Scan `HARNESS_DB_PORT_SCAN_START..END` |
136
+ | MinIO / S3 | Per machine | `HARNESS_MINIO_PORT_DEFAULT` (+ console port) | Scan configured ranges in `harness.env` |
137
+ | Mailpit | Per machine | `HARNESS_MAILPIT_*_PORT_DEFAULT` | Scan configured ranges |
138
+ | Headless browser | Per machine | `HARNESS_BROWSER_PORT_DEFAULT` | Scan configured ranges |
139
+
140
+ Resolved ports may differ from the formula when something else is already bound. Always use `./.har/agent-cli.sh <id>` or read `.har/slots/agent-<id>.json` — never hardcode `3010`, `15432`, etc. in app code or tests.
141
+
142
+ ### Shared vs per-slot
143
+
144
+ | Resource | Model | Configuration |
145
+ |----------|-------|---------------|
146
+ | Postgres | One shared container; per-slot database `agent_<id>` cloned from template | `HARNESS_INFRA_SERVICES="db"` |
147
+ | MinIO / S3 | One shared container; per-slot bucket `agent-<id>` | `HARNESS_INFRA_SERVICES="... minio"` |
148
+ | Mailpit, Redis, etc. | One shared container on a scanned host port | Listed in `HARNESS_INFRA_SERVICES` |
149
+ | Primary application | One PM2 ecosystem per slot (isolated ports) | `HARNESS_PRIMARY_APP`, `ecosystem.agent.template.cjs` |
150
+ | Internal supporting services | Shared across all slots | `docker-compose.agent.yml` or `ecosystem.shared.config.cjs` |
151
+
152
+ Shared infra starts once via `./.har/setup-infra.sh` (also run automatically by `launch.sh`). Per-slot databases are cloned in `launch.sh`.
153
+
154
+ ### Do not
155
+
156
+ - Hardcode default ports (`3000`, `15432`, `3847`, …) in application code, tests, or agent docs — read from `.env.agent.<id>`, `agent-cli.sh`, or the slot registry
157
+ - Run raw `docker compose` for harness infrastructure — use `setup-infra.sh` / `launch.sh` so ports are scanned and persisted in `.har/state/infra.env`
118
158
 
119
159
  ### Primary app vs shared services
120
160
 
@@ -150,6 +190,8 @@ work dir printed by launch, never in the main checkout.
150
190
  after explicit user approval.
151
191
  - `teardown` removes the worktree but **keeps the session branch** so you can push it
152
192
  or open a PR (`--delete-branch` to drop it).
193
+ - If launch fails after creating a worktree/env file, the registry records `status: failed`.
194
+ Resume without `--replace`: `har env launch <id> --resume` or `har env recover <id>`.
153
195
  - `har env complete <id>` finishes a session: full verify (recorded as a validation),
154
196
  then teardown — branch kept.
155
197
  - `--no-worktree` runs the slot from the repo root instead (single-agent mode).
@@ -17,51 +17,94 @@ COMMAND="${2:?Usage: agent-cli.sh <agent-id> <command> [args...]}"
17
17
 
18
18
  validate_agent_id "$AGENT_ID"
19
19
 
20
- FE_PORT=$(( HARNESS_FE_BASE_PORT + AGENT_ID * 10 ))
21
- API_PORT=$(( HARNESS_API_BASE_PORT + AGENT_ID * 10 ))
22
- DB_PORT="${AGENT_DB_PORT:-15432}"
20
+ load_agent_ports "$AGENT_ID" "$REPO_ROOT"
21
+ DB_PORT="${DB_PORT:-${AGENT_DB_PORT:-${HARNESS_DB_PORT_DEFAULT:-15432}}}"
23
22
  export PGPASSWORD="password"
24
23
 
24
+ PM2_SLOT_PREFIX="$(har_pm2_slot_prefix "$AGENT_ID")"
25
+
25
26
  case "$COMMAND" in
26
27
  status)
27
28
  ENV_FILE="$(resolve_agent_env_file "$AGENT_ID" "$REPO_ROOT" || true)"
28
29
  WORKTREE_DIR="$(existing_slot_worktree "$AGENT_ID")"
29
-
30
+ REG_FILE="$(slot_registry_file "$AGENT_ID")"
30
31
  PM2_RAW=$(npx --yes pm2 jlist 2>/dev/null || true)
31
32
  PM2_FOUND=false
33
+ PM2_FOREIGN=false
34
+ PM2_LEGACY=false
32
35
 
33
36
  if [ -n "$PM2_RAW" ]; then
37
+ set +e
34
38
  echo "$PM2_RAW" | node -e "
35
39
  const agentId = '${AGENT_ID}';
40
+ const project = '${HARNESS_PROJECT_NAME}';
41
+ const slotPrefix = 'har-' + project + '-agent-' + agentId + '-';
42
+ const legacyPrefix = 'agent-' + agentId + '-';
36
43
  let raw = '';
37
44
  process.stdin.on('data', c => raw += c);
38
45
  process.stdin.on('end', () => {
39
46
  try {
40
47
  const arr = JSON.parse(raw);
41
48
  if (!Array.isArray(arr)) process.exit(1);
42
- const procs = arr.filter(x => x.name && x.name.startsWith('agent-' + agentId + '-'));
43
- if (procs.length === 0) process.exit(1);
44
- console.log('Agent ' + agentId + ' processes:');
45
- procs.forEach(p => {
46
- const s = (p.pm2_env?.status || 'unknown').padEnd(10);
47
- const mem = Math.round((p.monit?.memory || 0) / 1024 / 1024) + 'MB';
48
- const cpu = (p.monit?.cpu || 0) + '%';
49
- console.log(' ' + p.name.padEnd(42) + s + ' mem=' + mem + ' cpu=' + cpu);
50
- });
49
+ const owned = arr.filter(x => x.name && x.name.startsWith(slotPrefix));
50
+ const foreignProject = arr.filter(x =>
51
+ x.name && x.name.startsWith('har-') && x.name.includes('-agent-' + agentId + '-') && !x.name.startsWith(slotPrefix));
52
+ const legacy = arr.filter(x => x.name && x.name.startsWith(legacyPrefix) && !x.name.startsWith('har-'));
53
+ if (owned.length > 0) {
54
+ console.log('Agent ' + agentId + ' processes (' + project + '):');
55
+ owned.forEach(p => {
56
+ const s = (p.pm2_env?.status || 'unknown').padEnd(10);
57
+ const mem = Math.round((p.monit?.memory || 0) / 1024 / 1024) + 'MB';
58
+ const cpu = (p.monit?.cpu || 0) + '%';
59
+ const cwd = p.pm2_env?.pm_cwd || p.pm2_env?.cwd || '';
60
+ console.log(' ' + p.name.padEnd(48) + s + ' mem=' + mem + ' cpu=' + cpu);
61
+ if (cwd) console.log(' cwd: ' + cwd);
62
+ });
63
+ process.exit(0);
64
+ }
65
+ if (foreignProject.length > 0 || legacy.length > 0) {
66
+ if (foreignProject.length > 0) {
67
+ console.log('ERROR: foreign PM2 processes match agent ' + agentId + ' but belong to another harness:');
68
+ foreignProject.forEach(p => console.log(' ' + p.name + ' cwd=' + (p.pm2_env?.pm_cwd || p.pm2_env?.cwd || 'unknown')));
69
+ }
70
+ if (legacy.length > 0) {
71
+ console.log('ERROR: legacy PM2 processes (pre project-scoping) match agent ' + agentId + ':');
72
+ legacy.forEach(p => console.log(' ' + p.name + ' cwd=' + (p.pm2_env?.pm_cwd || p.pm2_env?.cwd || 'unknown')));
73
+ }
74
+ process.exit(2);
75
+ }
76
+ process.exit(1);
51
77
  } catch {
52
78
  process.exit(1);
53
79
  }
54
80
  });
55
- " && PM2_FOUND=true
81
+ "
82
+ pm2_status=$?
83
+ set -e
84
+ case "$pm2_status" in
85
+ 0) PM2_FOUND=true ;;
86
+ 2) PM2_FOREIGN=true ;;
87
+ esac
88
+ fi
89
+
90
+ if [ "$PM2_FOREIGN" = true ]; then
91
+ exit 1
56
92
  fi
57
93
 
58
94
  if [ "$PM2_FOUND" = true ]; then
59
- REG_FILE="$(slot_registry_file "$AGENT_ID")"
60
95
  if [ ! -f "$REG_FILE" ]; then
61
- echo "Warning: slot registry missing at $REG_FILE verify/teardown may not resolve this slot."
96
+ echo "ERROR: slot registry missing at $REG_FILE but PM2 processes exist for this project." >&2
97
+ echo " Another harness may have been torn down incorrectly, or this slot was started outside launch.sh." >&2
98
+ exit 1
99
+ fi
100
+ REG_PROJECT="$(read_slot_field "$REG_FILE" projectName || true)"
101
+ if [ -n "$REG_PROJECT" ] && [ "$REG_PROJECT" != "$HARNESS_PROJECT_NAME" ]; then
102
+ echo "ERROR: slot registry projectName=${REG_PROJECT} does not match harness ${HARNESS_PROJECT_NAME}." >&2
103
+ exit 1
62
104
  fi
63
105
  if [ -z "$ENV_FILE" ]; then
64
- echo "Warning: .env.agent.${AGENT_ID} could not be resolved — relaunch with --replace when ready."
106
+ echo "ERROR: .env.agent.${AGENT_ID} could not be resolved — relaunch with --replace when ready." >&2
107
+ exit 1
65
108
  fi
66
109
  elif [ -n "$ENV_FILE" ] && [ -f "$ENV_FILE" ]; then
67
110
  echo "Agent ${AGENT_ID}: active (no PM2 processes)"
@@ -69,6 +112,8 @@ process.stdin.on('end', () => {
69
112
  source "$ENV_FILE" 2>/dev/null || true
70
113
  echo " Work dir: $(resolve_agent_work_dir "$ENV_FILE" "$AGENT_ID")"
71
114
  [ -n "$WORKTREE_DIR" ] && [ -d "$WORKTREE_DIR" ] && echo " Worktree: $WORKTREE_DIR"
115
+ echo " Frontend: http://localhost:${FE_PORT}"
116
+ echo " API: http://localhost:${API_PORT}"
72
117
  else
73
118
  echo "No active environment for agent ${AGENT_ID}"
74
119
  echo " Run: ./.har/launch.sh ${AGENT_ID}"
@@ -78,22 +123,23 @@ process.stdin.on('end', () => {
78
123
  logs)
79
124
  SERVICE="${3:-}"
80
125
  if [ -n "$SERVICE" ]; then
81
- npx pm2 logs "agent-${AGENT_ID}-${SERVICE}" --lines 100
126
+ npx pm2 logs "${PM2_SLOT_PREFIX}-${SERVICE}" --lines 100
82
127
  else
83
- npx pm2 logs --name "agent-${AGENT_ID}" --lines 100
128
+ npx pm2 logs --name "${PM2_SLOT_PREFIX}" --lines 100
84
129
  fi
85
130
  ;;
86
131
 
87
132
  restart)
88
133
  SERVICE="${3:-}"
89
134
  if [ -n "$SERVICE" ]; then
90
- npx pm2 restart "agent-${AGENT_ID}-${SERVICE}"
135
+ npx pm2 restart "${PM2_SLOT_PREFIX}-${SERVICE}"
91
136
  else
92
137
  npx pm2 jlist 2>/dev/null | node -e "
138
+ const prefix = '${PM2_SLOT_PREFIX}-';
93
139
  const d = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
94
- const names = d.filter(x => x.name.startsWith('agent-${AGENT_ID}-')).map(x => x.name);
140
+ const names = d.filter(x => x.name && x.name.startsWith(prefix)).map(x => x.name);
95
141
  names.forEach(n => require('child_process').execSync('npx pm2 restart ' + n, {stdio:'inherit'}));
96
- if (names.length === 0) console.log('No processes found for agent ${AGENT_ID}');
142
+ if (names.length === 0) console.log('No processes found for ${PM2_SLOT_PREFIX}');
97
143
  " 2>/dev/null || true
98
144
  fi
99
145
  ;;
@@ -122,9 +168,9 @@ try { console.log(JSON.stringify(JSON.parse(d), null, 2)); } catch { console.log
122
168
  echo "Frontend: http://localhost:${FE_PORT}"
123
169
  echo "API: http://localhost:${API_PORT}"
124
170
  har_infra_enabled db && echo "Database: agent_${AGENT_ID} @ localhost:${DB_PORT}"
125
- har_infra_enabled minio && echo "MinIO: http://localhost:19001"
126
- har_infra_enabled headless-browser && echo "Browser: http://localhost:13001"
127
- har_infra_enabled mailpit && echo "Mailpit: http://localhost:18025"
171
+ har_infra_enabled minio && echo "MinIO: http://localhost:${AGENT_MINIO_CONSOLE_PORT:-19001}"
172
+ har_infra_enabled headless-browser && echo "Browser: http://localhost:${AGENT_BROWSER_PORT:-13001}"
173
+ har_infra_enabled mailpit && echo "Mailpit: http://localhost:${AGENT_MAILPIT_WEB_PORT:-18025}"
128
174
  ;;
129
175
 
130
176
  reset-db)
@@ -160,7 +206,7 @@ LIMIT 20;" 2>/dev/null || echo "pg_stat_statements extension not available"
160
206
  ;;
161
207
 
162
208
  attach)
163
- SESSION="agent-${AGENT_ID}"
209
+ SESSION="$(har_tmux_session "$AGENT_ID")"
164
210
  if ! tmux has-session -t "$SESSION" 2>/dev/null; then
165
211
  echo "No tmux session found: $SESSION" >&2
166
212
  exit 1