@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
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env bash
2
+ # Provision the project toolchain and append resolved paths to .env.agent.<id>.
3
+ # Called from launch.sh after the agent env file is created.
4
+ #
5
+ # Configure in harness.env:
6
+ # HARNESS_ECOSYSTEM — auto (default) | node | python | go | rust | java | ruby | ios | none
7
+ # HARNESS_INSTALL_CMD — optional override (eval in HAR_WORK_DIR)
8
+ # Ecosystem-specific optional overrides:
9
+ # HARNESS_PYTHON_VENV_DIR — Python venv path relative to HAR_WORK_DIR (default: .har/venv)
10
+ #
11
+ # Required env from caller: HAR_WORK_DIR, HAR_ENV_FILE
12
+ # Optional: HAR_WORKTREE_DIR, HAR_REL_PREFIX, HAR_AGENT_ID (for logging)
13
+ set -euo pipefail
14
+
15
+ : "${HAR_WORK_DIR:?HAR_WORK_DIR is required}"
16
+ : "${HAR_ENV_FILE:?HAR_ENV_FILE is required}"
17
+
18
+ HAR_WORKTREE_DIR="${HAR_WORKTREE_DIR:-}"
19
+ HAR_REL_PREFIX="${HAR_REL_PREFIX:-}"
20
+ HAR_AGENT_ID="${HAR_AGENT_ID:-}"
21
+
22
+ pt_log() {
23
+ if [ -n "$HAR_AGENT_ID" ]; then
24
+ echo "==> [agent-${HAR_AGENT_ID}] toolchain: $*" >&2
25
+ else
26
+ echo "==> [provision-toolchain] $*" >&2
27
+ fi
28
+ }
29
+
30
+ append_env() {
31
+ local key="$1"
32
+ local value="$2"
33
+ printf '%s=%s\n' "$key" "$value" >> "$HAR_ENV_FILE"
34
+ }
35
+
36
+ append_path_prefix() {
37
+ local prefix="$1"
38
+ [ -n "$prefix" ] && [ -d "$prefix" ] || return 0
39
+ append_env "PATH" "${prefix}:${PATH:-$PATH}"
40
+ }
41
+
42
+ detect_ecosystem() {
43
+ local dir="$1"
44
+ local configured="${HARNESS_ECOSYSTEM:-auto}"
45
+ if [ -n "$configured" ] && [ "$configured" != "auto" ]; then
46
+ echo "$configured"
47
+ return
48
+ fi
49
+ if [ -f "$dir/package.json" ]; then echo node; return; fi
50
+ if [ -f "$dir/pyproject.toml" ] || [ -f "$dir/setup.py" ] || [ -f "$dir/setup.cfg" ] \
51
+ || [ -f "$dir/requirements.txt" ] || [ -f "$dir/Pipfile" ]; then
52
+ echo python
53
+ return
54
+ fi
55
+ if [ -f "$dir/go.mod" ]; then echo go; return; fi
56
+ if [ -f "$dir/Cargo.toml" ]; then echo rust; return; fi
57
+ if [ -f "$dir/pom.xml" ] || [ -f "$dir/build.gradle" ] || [ -f "$dir/build.gradle.kts" ]; then
58
+ echo java
59
+ return
60
+ fi
61
+ if [ -f "$dir/Gemfile" ]; then echo ruby; return; fi
62
+ if [ -n "${HARNESS_XCODE_SCHEME:-}" ] || [ -n "${HARNESS_XCODE_PROJECT:-}" ] \
63
+ || [ -n "${HARNESS_XCODE_WORKSPACE:-}" ]; then
64
+ echo ios
65
+ return
66
+ fi
67
+ echo none
68
+ }
69
+
70
+ run_install_cmd() {
71
+ local dir="$1"
72
+ if [ -n "${HARNESS_INSTALL_CMD:-}" ]; then
73
+ pt_log "Running HARNESS_INSTALL_CMD..."
74
+ (cd "$dir" && eval "$HARNESS_INSTALL_CMD")
75
+ return
76
+ fi
77
+ return 1
78
+ }
79
+
80
+ provision_node() {
81
+ local dir="$1"
82
+ local npm_bin="npm"
83
+ local node_bin="node"
84
+
85
+ if ! run_install_cmd "$dir"; then
86
+ pt_log "Installing Node dependencies..."
87
+ if [ -f "$dir/pnpm-lock.yaml" ] && command -v pnpm >/dev/null 2>&1; then
88
+ (cd "$dir" && pnpm install --silent)
89
+ npm_bin="pnpm"
90
+ elif [ -f "$dir/yarn.lock" ] && command -v yarn >/dev/null 2>&1; then
91
+ (cd "$dir" && yarn install --silent)
92
+ npm_bin="yarn"
93
+ else
94
+ (cd "$dir" && npm install --silent)
95
+ fi
96
+ fi
97
+
98
+ if command -v node >/dev/null 2>&1; then
99
+ node_bin="$(command -v node)"
100
+ fi
101
+ if [ "$npm_bin" = "npm" ] && command -v npm >/dev/null 2>&1; then
102
+ npm_bin="$(command -v npm)"
103
+ fi
104
+
105
+ append_env "HARNESS_ECOSYSTEM" "node"
106
+ append_env "NODE_BIN" "$node_bin"
107
+ append_env "NPM_BIN" "$npm_bin"
108
+ append_path_prefix "$dir/node_modules/.bin"
109
+ }
110
+
111
+ provision_python() {
112
+ local dir="$1"
113
+ local venv_rel="${HARNESS_PYTHON_VENV_DIR:-.har/venv}"
114
+ local venv_dir="$dir/$venv_rel"
115
+ local python_bin="python3"
116
+
117
+ if ! command -v python3 >/dev/null 2>&1; then
118
+ pt_log "python3 not found on PATH — skipping venv provisioning"
119
+ append_env "HARNESS_ECOSYSTEM" "python"
120
+ append_env "PYTHON_BIN" "${PYTHON_BIN:-python3}"
121
+ return
122
+ fi
123
+
124
+ if [ ! -d "$venv_dir" ]; then
125
+ pt_log "Creating Python venv at $venv_rel..."
126
+ if ! python3 -m venv "$venv_dir"; then
127
+ rm -rf "$venv_dir"
128
+ pt_log "python3 -m venv failed (on Debian/Ubuntu install python3-venv) — using system python3"
129
+ append_env "HARNESS_ECOSYSTEM" "python"
130
+ append_env "PYTHON_BIN" "$(command -v python3)"
131
+ return
132
+ fi
133
+ fi
134
+
135
+ if [ ! -x "$venv_dir/bin/python" ]; then
136
+ pt_log "Python venv at $venv_rel is missing or broken — using system python3"
137
+ append_env "HARNESS_ECOSYSTEM" "python"
138
+ append_env "PYTHON_BIN" "$(command -v python3)"
139
+ return
140
+ fi
141
+
142
+ python_bin="$venv_dir/bin/python"
143
+ # shellcheck disable=SC1091
144
+ source "$venv_dir/bin/activate"
145
+
146
+ if ! run_install_cmd "$dir"; then
147
+ pt_log "Installing Python dependencies..."
148
+ if [ -f "$dir/pyproject.toml" ]; then
149
+ (cd "$dir" && pip install -q -e ".[dev]" 2>/dev/null) || (cd "$dir" && pip install -q -e .)
150
+ elif [ -f "$dir/requirements.txt" ]; then
151
+ (cd "$dir" && pip install -q -r requirements.txt)
152
+ elif [ -f "$dir/setup.py" ] || [ -f "$dir/setup.cfg" ]; then
153
+ (cd "$dir" && pip install -q -e .)
154
+ elif [ -f "$dir/Pipfile" ] && command -v pipenv >/dev/null 2>&1; then
155
+ (cd "$dir" && pipenv install --dev)
156
+ python_bin="$(cd "$dir" && pipenv --py)"
157
+ fi
158
+ fi
159
+
160
+ append_env "HARNESS_ECOSYSTEM" "python"
161
+ append_env "PYTHON_BIN" "$python_bin"
162
+ append_env "VIRTUAL_ENV" "$venv_dir"
163
+ append_path_prefix "$venv_dir/bin"
164
+ }
165
+
166
+ provision_go() {
167
+ local dir="$1"
168
+ if ! run_install_cmd "$dir"; then
169
+ if command -v go >/dev/null 2>&1; then
170
+ pt_log "Downloading Go modules..."
171
+ (cd "$dir" && go mod download)
172
+ else
173
+ pt_log "go not found on PATH — record paths only"
174
+ fi
175
+ fi
176
+ append_env "HARNESS_ECOSYSTEM" "go"
177
+ append_env "GO_BIN" "$(command -v go 2>/dev/null || echo go)"
178
+ if [ -n "${GOPATH:-}" ]; then append_env "GOPATH" "$GOPATH"; fi
179
+ if [ -n "${GOROOT:-}" ]; then append_env "GOROOT" "$GOROOT"; fi
180
+ }
181
+
182
+ provision_rust() {
183
+ local dir="$1"
184
+ if ! run_install_cmd "$dir"; then
185
+ if command -v cargo >/dev/null 2>&1; then
186
+ pt_log "Fetching Rust dependencies..."
187
+ (cd "$dir" && cargo fetch)
188
+ else
189
+ pt_log "cargo not found on PATH — record paths only"
190
+ fi
191
+ fi
192
+ append_env "HARNESS_ECOSYSTEM" "rust"
193
+ append_env "CARGO_BIN" "$(command -v cargo 2>/dev/null || echo cargo)"
194
+ append_env "RUSTC_BIN" "$(command -v rustc 2>/dev/null || echo rustc)"
195
+ }
196
+
197
+ provision_java() {
198
+ local dir="$1"
199
+ run_install_cmd "$dir" || true
200
+ append_env "HARNESS_ECOSYSTEM" "java"
201
+ if [ -n "${JAVA_HOME:-}" ]; then
202
+ append_env "JAVA_HOME" "$JAVA_HOME"
203
+ append_path_prefix "$JAVA_HOME/bin"
204
+ fi
205
+ if command -v mvn >/dev/null 2>&1; then
206
+ append_env "MVN_BIN" "$(command -v mvn)"
207
+ elif [ -f "$dir/gradlew" ]; then
208
+ append_env "GRADLE_BIN" "$dir/gradlew"
209
+ elif command -v gradle >/dev/null 2>&1; then
210
+ append_env "GRADLE_BIN" "$(command -v gradle)"
211
+ fi
212
+ }
213
+
214
+ provision_ruby() {
215
+ local dir="$1"
216
+ if ! run_install_cmd "$dir"; then
217
+ if command -v bundle >/dev/null 2>&1; then
218
+ pt_log "Installing Ruby gems..."
219
+ (cd "$dir" && bundle install --quiet)
220
+ else
221
+ pt_log "bundle not found on PATH — record paths only"
222
+ fi
223
+ fi
224
+ append_env "HARNESS_ECOSYSTEM" "ruby"
225
+ append_env "RUBY_BIN" "$(command -v ruby 2>/dev/null || echo ruby)"
226
+ append_env "BUNDLE_BIN" "$(command -v bundle 2>/dev/null || echo bundle)"
227
+ append_path_prefix "$dir/vendor/bundle/bin"
228
+ }
229
+
230
+ provision_ios() {
231
+ local dir="$1"
232
+ run_install_cmd "$dir" || true
233
+ append_env "HARNESS_ECOSYSTEM" "ios"
234
+ append_env "XCODEBUILD_BIN" "$(command -v xcodebuild 2>/dev/null || echo xcodebuild)"
235
+ if [ -n "${HARNESS_XCODE_SCHEME:-}" ]; then
236
+ append_env "HARNESS_XCODE_SCHEME" "$HARNESS_XCODE_SCHEME"
237
+ fi
238
+ if [ -n "${HARNESS_SIMULATOR_NAME:-}" ]; then
239
+ append_env "HARNESS_SIMULATOR_NAME" "$HARNESS_SIMULATOR_NAME"
240
+ fi
241
+ if [ -n "${HARNESS_BUNDLE_ID:-}" ]; then
242
+ append_env "HARNESS_BUNDLE_ID" "$HARNESS_BUNDLE_ID"
243
+ fi
244
+ if [ -n "${DEVELOPER_DIR:-}" ]; then
245
+ append_env "DEVELOPER_DIR" "$DEVELOPER_DIR"
246
+ fi
247
+ }
248
+
249
+ provision_monorepo_root() {
250
+ [ -n "$HAR_REL_PREFIX" ] || return 0
251
+ [ -n "$HAR_WORKTREE_DIR" ] || return 0
252
+ [ -f "$HAR_WORKTREE_DIR/package.json" ] || return 0
253
+ [ -d "$HAR_WORKTREE_DIR/node_modules" ] && return 0
254
+ pt_log "Installing monorepo root dependencies in $HAR_WORKTREE_DIR..."
255
+ (cd "$HAR_WORKTREE_DIR" && npm install --silent)
256
+ }
257
+
258
+ provision_ecosystem() {
259
+ local dir="$1"
260
+ local ecosystem
261
+ ecosystem="$(detect_ecosystem "$dir")"
262
+ pt_log "Toolchain ecosystem: ${ecosystem} (work dir: ${dir})"
263
+
264
+ case "$ecosystem" in
265
+ node) provision_node "$dir" ;;
266
+ python) provision_python "$dir" ;;
267
+ go) provision_go "$dir" ;;
268
+ rust) provision_rust "$dir" ;;
269
+ java) provision_java "$dir" ;;
270
+ ruby) provision_ruby "$dir" ;;
271
+ ios) provision_ios "$dir" ;;
272
+ none)
273
+ if run_install_cmd "$dir"; then
274
+ append_env "HARNESS_ECOSYSTEM" "custom"
275
+ else
276
+ pt_log "No ecosystem manifest detected — set HARNESS_ECOSYSTEM or HARNESS_INSTALL_CMD in harness.env"
277
+ append_env "HARNESS_ECOSYSTEM" "none"
278
+ fi
279
+ ;;
280
+ esac
281
+ }
282
+
283
+ append_env "HARNESS_TOOLCHAIN_PROVISIONED" "true"
284
+ provision_ecosystem "$HAR_WORK_DIR"
285
+ provision_monorepo_root
@@ -11,26 +11,101 @@ set -euo pipefail
11
11
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
12
  REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
13
13
  COMPOSE_FILE="$SCRIPT_DIR/docker-compose.agent.yml"
14
+ INFRA_STATE="$SCRIPT_DIR/state/infra.env"
14
15
 
15
16
  # shellcheck source=/dev/null
16
17
  source "$SCRIPT_DIR/harness.env"
18
+ # shellcheck source=/dev/null
19
+ source "$SCRIPT_DIR/agent-slot.sh"
17
20
 
18
21
  COMPOSE_PROJECT="har-${HARNESS_PROJECT_NAME}"
19
- DB_PORT="${AGENT_DB_PORT:-15432}"
20
22
  PSQL="har_pg psql -d postgres"
21
23
 
22
24
  log() { echo "==> $*" >&2; }
23
25
 
26
+ har_compose_service_running() {
27
+ local service="$1"
28
+ docker ps --filter "name=${COMPOSE_PROJECT}-${service}-1" --format '{{.Names}}' 2>/dev/null \
29
+ | grep -q "^${COMPOSE_PROJECT}-${service}-1\$"
30
+ }
31
+
32
+ har_resolve_infra_port() {
33
+ local var_name="$1"
34
+ local default_port="$2"
35
+ local scan_start="$3"
36
+ local scan_end="$4"
37
+ local service="${5:-}"
38
+ local current="${!var_name:-}"
39
+
40
+ if [ -n "$current" ] && { ! port_in_use "$current" || { [ -n "$service" ] && har_compose_service_running "$service"; }; }; then
41
+ echo "$current"
42
+ return 0
43
+ fi
44
+ har_allocate_port "$default_port" "$scan_start" "$scan_end"
45
+ }
46
+
47
+ mkdir -p "$(dirname "$INFRA_STATE")"
48
+ if [ -f "$INFRA_STATE" ]; then
49
+ # shellcheck source=/dev/null
50
+ source "$INFRA_STATE"
51
+ fi
52
+
53
+ DB_PORT="$(har_resolve_infra_port AGENT_DB_PORT \
54
+ "${HARNESS_DB_PORT_DEFAULT:-15432}" \
55
+ "${HARNESS_DB_PORT_SCAN_START:-15432}" \
56
+ "${HARNESS_DB_PORT_SCAN_END:-15499}" \
57
+ db)"
58
+ MINIO_PORT="$(har_resolve_infra_port AGENT_MINIO_PORT \
59
+ "${HARNESS_MINIO_PORT_DEFAULT:-19000}" \
60
+ "${HARNESS_MINIO_PORT_SCAN_START:-19000}" \
61
+ "${HARNESS_MINIO_PORT_SCAN_END:-19099}" \
62
+ minio)"
63
+ MINIO_CONSOLE_PORT="$(har_resolve_infra_port AGENT_MINIO_CONSOLE_PORT \
64
+ "${HARNESS_MINIO_CONSOLE_PORT_DEFAULT:-19001}" \
65
+ "${HARNESS_MINIO_CONSOLE_PORT_SCAN_START:-19001}" \
66
+ "${HARNESS_MINIO_CONSOLE_PORT_SCAN_END:-19099}")"
67
+ BROWSER_PORT="$(har_resolve_infra_port AGENT_BROWSER_PORT \
68
+ "${HARNESS_BROWSER_PORT_DEFAULT:-13001}" \
69
+ "${HARNESS_BROWSER_PORT_SCAN_START:-13001}" \
70
+ "${HARNESS_BROWSER_PORT_SCAN_END:-13099}" \
71
+ headless-browser)"
72
+ MAILPIT_WEB_PORT="$(har_resolve_infra_port AGENT_MAILPIT_WEB_PORT \
73
+ "${HARNESS_MAILPIT_WEB_PORT_DEFAULT:-18025}" \
74
+ "${HARNESS_MAILPIT_WEB_PORT_SCAN_START:-18025}" \
75
+ "${HARNESS_MAILPIT_WEB_PORT_SCAN_END:-18099}" \
76
+ mailpit)"
77
+ MAILPIT_SMTP_PORT="$(har_resolve_infra_port AGENT_MAILPIT_SMTP_PORT \
78
+ "${HARNESS_MAILPIT_SMTP_PORT_DEFAULT:-11025}" \
79
+ "${HARNESS_MAILPIT_SMTP_PORT_SCAN_START:-11025}" \
80
+ "${HARNESS_MAILPIT_SMTP_PORT_SCAN_END:-11099}")"
81
+
82
+ export AGENT_DB_PORT="$DB_PORT"
83
+ export AGENT_MINIO_PORT="$MINIO_PORT"
84
+ export AGENT_MINIO_CONSOLE_PORT="$MINIO_CONSOLE_PORT"
85
+ export AGENT_BROWSER_PORT="$BROWSER_PORT"
86
+ export AGENT_MAILPIT_WEB_PORT="$MAILPIT_WEB_PORT"
87
+ export AGENT_MAILPIT_SMTP_PORT="$MAILPIT_SMTP_PORT"
88
+
89
+ cat > "$INFRA_STATE" <<EOF
90
+ # Persisted by setup-infra.sh — host ports for shared docker compose services.
91
+ export AGENT_DB_PORT=${DB_PORT}
92
+ export AGENT_MINIO_PORT=${MINIO_PORT}
93
+ export AGENT_MINIO_CONSOLE_PORT=${MINIO_CONSOLE_PORT}
94
+ export AGENT_BROWSER_PORT=${BROWSER_PORT}
95
+ export AGENT_MAILPIT_WEB_PORT=${MAILPIT_WEB_PORT}
96
+ export AGENT_MAILPIT_SMTP_PORT=${MAILPIT_SMTP_PORT}
97
+ EOF
98
+
24
99
  SERVICES="${HARNESS_INFRA_SERVICES:-}"
25
100
 
26
101
  if [ -n "$SERVICES" ]; then
27
102
  log "Starting shared infrastructure (project: $COMPOSE_PROJECT): $SERVICES"
28
103
  AGENT_DB_PORT="$DB_PORT" \
29
- AGENT_MINIO_PORT="${AGENT_MINIO_PORT:-19000}" \
30
- AGENT_MINIO_CONSOLE_PORT="${AGENT_MINIO_CONSOLE_PORT:-19001}" \
31
- AGENT_BROWSER_PORT="${AGENT_BROWSER_PORT:-13001}" \
32
- AGENT_MAILPIT_WEB_PORT="${AGENT_MAILPIT_WEB_PORT:-18025}" \
33
- AGENT_MAILPIT_SMTP_PORT="${AGENT_MAILPIT_SMTP_PORT:-11025}" \
104
+ AGENT_MINIO_PORT="$MINIO_PORT" \
105
+ AGENT_MINIO_CONSOLE_PORT="$MINIO_CONSOLE_PORT" \
106
+ AGENT_BROWSER_PORT="$BROWSER_PORT" \
107
+ AGENT_MAILPIT_WEB_PORT="$MAILPIT_WEB_PORT" \
108
+ AGENT_MAILPIT_SMTP_PORT="$MAILPIT_SMTP_PORT" \
34
109
  docker compose -p "$COMPOSE_PROJECT" -f "$COMPOSE_FILE" up -d $SERVICES
35
110
  else
36
111
  log "No shared infra services enabled in harness.env (HARNESS_INFRA_SERVICES)"
@@ -85,18 +160,19 @@ fi
85
160
  # Shared app services — supporting services of a monolith/monorepo that agents
86
161
  # depend on but do not modify. Started ONCE on fixed ports, shared by all slots.
87
162
  # Create .har/ecosystem.shared.config.cjs (PM2 format, processes named
88
- # "har-shared-<name>") only when the primary app needs sibling services running.
163
+ # "har-${HARNESS_PROJECT_NAME}-shared-<name>") only when the primary app needs
164
+ # sibling services running.
89
165
  SHARED_ECOSYSTEM="$SCRIPT_DIR/ecosystem.shared.config.cjs"
90
166
  if [ -f "$SHARED_ECOSYSTEM" ]; then
91
167
  log "Starting shared app services from ecosystem.shared.config.cjs..."
92
168
  (cd "$REPO_ROOT" && npx --yes pm2 startOrReload "$SHARED_ECOSYSTEM" >/dev/null)
93
- log "Shared app services running (pm2 ls | grep har-shared-)."
169
+ log "Shared app services running (pm2 ls | grep har-${HARNESS_PROJECT_NAME}-shared-)."
94
170
  fi
95
171
 
96
172
  echo ""
97
173
  log "Infrastructure is ready."
98
174
  har_infra_enabled db && log " PostgreSQL: localhost:$DB_PORT"
99
- har_infra_enabled minio && log " MinIO: http://localhost:${AGENT_MINIO_CONSOLE_PORT:-19001}"
100
- har_infra_enabled headless-browser && log " Browser: http://localhost:${AGENT_BROWSER_PORT:-13001}"
101
- har_infra_enabled mailpit && log " Mailpit: http://localhost:${AGENT_MAILPIT_WEB_PORT:-18025}"
175
+ har_infra_enabled minio && log " MinIO: http://localhost:${MINIO_CONSOLE_PORT}"
176
+ har_infra_enabled headless-browser && log " Browser: http://localhost:${BROWSER_PORT}"
177
+ har_infra_enabled mailpit && log " Mailpit: http://localhost:${MAILPIT_WEB_PORT}"
102
178
  exit 0
@@ -40,7 +40,8 @@ if [ -f "$REGISTRY_FILE" ]; then
40
40
  fi
41
41
  [ -n "$WORKTREE_PATH" ] || WORKTREE_PATH="$HOME/worktrees/${HARNESS_PROJECT_NAME}-agent-${AGENT_ID}"
42
42
 
43
- npx --yes pm2 delete "/^agent-${AGENT_ID}-/" 2>/dev/null || true
43
+ PM2_REGEX="$(har_pm2_delete_regex "$AGENT_ID")"
44
+ npx --yes pm2 delete "$PM2_REGEX" 2>/dev/null || true
44
45
  echo "✓ Stopped PM2 processes"
45
46
 
46
47
  if har_infra_enabled db; then
@@ -4,9 +4,9 @@
4
4
  #
5
5
  # Usage: ./.har/verify.sh <agent-id> [--full]
6
6
  #
7
- # Quick (default): smoke compile / typecheck / health only
8
- # Full (--full): + unit tests, lint, optional readiness + browser-e2e
9
- # Step lists are examples not exhaustive. Adapt commands to this repo's stack.
7
+ # Quick (default): ecosystem smoke + health only
8
+ # Full (--full): + conventional tests, lint, optional readiness + browser-e2e
9
+ # Stock steps are examples. Replace them during adaptation to match this repo.
10
10
  set -euo pipefail
11
11
 
12
12
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -136,18 +136,98 @@ process.stdout.write(JSON.stringify(arr));
136
136
  fi
137
137
  }
138
138
 
139
- # ── Verification stages ─────────────────────────────────────────────────────
140
- # Customize these steps for your project — lists below are examples, not exhaustive.
141
- # Edit this section directly — do not use a separate config file.
139
+ node_package_script_exists() {
140
+ local script="$1"
141
+ "${NODE_BIN:-node}" -e "
142
+ const fs = require('fs');
143
+ const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
144
+ const script = process.argv[1];
145
+ process.exit(pkg.scripts && pkg.scripts[script] ? 0 : 1);
146
+ " "$script"
147
+ }
142
148
 
143
- # Quick (default): smoke — prove the slot can compile/load, not full test suites.
144
- run_step "typecheck" "echo 'TODO: npm run typecheck'" || { [ -z "$FULL" ] && true; }
149
+ run_node_script_or_skip() {
150
+ local name="$1"
151
+ local script="$2"
152
+ if node_package_script_exists "$script"; then
153
+ run_step "$name" "${NPM_BIN:-npm} run ${script}"
154
+ else
155
+ run_step "$name" "echo 'No ${script} script configured; skipping.'"
156
+ fi
157
+ }
158
+
159
+ run_node_quick_smoke() {
160
+ if node_package_script_exists typecheck; then
161
+ run_step "node-typecheck" '${NPM_BIN:-npm} run typecheck'
162
+ elif node_package_script_exists build; then
163
+ run_step "node-build" '${NPM_BIN:-npm} run build'
164
+ else
165
+ run_step "node-package-load" '${NODE_BIN:-node} -e "require(\"./package.json\")"'
166
+ fi
167
+ }
168
+
169
+ run_quick_smoke() {
170
+ case "${HARNESS_ECOSYSTEM:-none}" in
171
+ node)
172
+ run_node_quick_smoke
173
+ ;;
174
+ python)
175
+ run_step "python-compile" '${PYTHON_BIN:-python3} -m compileall -q .'
176
+ ;;
177
+ go)
178
+ run_step "go-build" '${GO_BIN:-go} build ./...'
179
+ ;;
180
+ rust)
181
+ run_step "rust-check" '${CARGO_BIN:-cargo} check'
182
+ ;;
183
+ java)
184
+ run_step "java-compile" 'if [ -x ./mvnw ]; then ./mvnw -q -DskipTests compile; elif command -v mvn >/dev/null 2>&1; then mvn -q -DskipTests compile; elif [ -x ./gradlew ]; then ./gradlew classes; elif command -v gradle >/dev/null 2>&1; then gradle classes; else echo "No Maven/Gradle command found; adapt verify.sh for this Java repo."; fi'
185
+ ;;
186
+ ruby)
187
+ run_step "ruby-smoke" '${RUBY_BIN:-ruby} -e "puts RUBY_VERSION"'
188
+ ;;
189
+ custom|none|*)
190
+ run_step "smoke-not-configured" 'echo "No stock smoke for HARNESS_ECOSYSTEM=${HARNESS_ECOSYSTEM:-none}; adapt .har/verify.sh for this repo."'
191
+ ;;
192
+ esac
193
+ }
194
+
195
+ run_full_checks() {
196
+ case "${HARNESS_ECOSYSTEM:-none}" in
197
+ node)
198
+ run_node_script_or_skip "unit-tests" test || true
199
+ run_node_script_or_skip "lint" lint || true
200
+ ;;
201
+ python)
202
+ run_step "unit-tests" 'if ${PYTHON_BIN:-python3} -c "import pytest" >/dev/null 2>&1; then ${PYTHON_BIN:-python3} -m pytest -q; else echo "pytest not installed; adapt verify.sh for this Python repo."; fi' || true
203
+ ;;
204
+ go)
205
+ run_step "unit-tests" '${GO_BIN:-go} test ./...' || true
206
+ ;;
207
+ rust)
208
+ run_step "unit-tests" '${CARGO_BIN:-cargo} test' || true
209
+ ;;
210
+ java)
211
+ run_step "unit-tests" 'if [ -x ./mvnw ]; then ./mvnw -q test; elif command -v mvn >/dev/null 2>&1; then mvn -q test; elif [ -x ./gradlew ]; then ./gradlew test; elif command -v gradle >/dev/null 2>&1; then gradle test; else echo "No Maven/Gradle command found; adapt verify.sh for this Java repo."; fi' || true
212
+ ;;
213
+ ruby)
214
+ run_step "unit-tests" 'if command -v "${BUNDLE_BIN:-bundle}" >/dev/null 2>&1 && [ -f Gemfile ]; then "${BUNDLE_BIN:-bundle}" exec rake test 2>/dev/null || "${BUNDLE_BIN:-bundle}" exec rspec; else echo "No Ruby test command detected; adapt verify.sh for this Ruby repo."; fi' || true
215
+ ;;
216
+ custom|none|*)
217
+ run_step "unit-tests" 'echo "No stock full checks for HARNESS_ECOSYSTEM=${HARNESS_ECOSYSTEM:-none}; adapt .har/verify.sh for this repo."' || true
218
+ ;;
219
+ esac
220
+ }
221
+
222
+ # ── Verification stages ─────────────────────────────────────────────────────
223
+ # These stock steps are intentionally generic conventions. Adapt this section
224
+ # to the repository's real commands from package.json, Makefile, CI, pyproject,
225
+ # Cargo.toml, go.mod, pom.xml, etc.
226
+ run_quick_smoke || { [ -z "$FULL" ] && true; }
145
227
  run_http_step "api-health" "http://localhost:${API_PORT}${HARNESS_HEALTH_CHECK_PATH}" || { [ -z "$FULL" ] && true; }
146
228
 
147
229
  if [ -n "$FULL" ]; then
148
- # Full: project-specific checks — add/remove/reorder steps for this repo.
149
- run_step "unit-tests" "echo 'TODO: npm test'" || true
150
- run_step "lint" "echo 'TODO: npm run lint'" || true
230
+ run_full_checks
151
231
  run_step "readiness" "run_readiness_if_configured \"$AGENT_ID\"" || true
152
232
  run_step "browser-e2e" "run_browser_e2e_if_present \"$SCRIPT_DIR\" \"$AGENT_ID\"" || true
153
233
  fi
@@ -36,9 +36,12 @@ Quick loop: MCP `har_run_verification`, `har env verify ${AGENT_ID}`, or `./.har
36
36
  ## Project commands
37
37
 
38
38
  ```bash
39
- # TODO: adapt for this repository
40
- # npm run typecheck
39
+ # TODO: adapt for this repository. Examples:
41
40
  # npm test
41
+ # pytest -q
42
+ # go test ./...
43
+ # cargo test
44
+ # make test
42
45
  ```
43
46
 
44
47
  ## 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, profile, checksums) — do not edit |
15
- | `harness.env` | Shared config: worktree default, `HARNESS_INFRA_SERVICES`, migrate/seed commands |
15
+ | `harness.env` | Shared config: worktree default, `HARNESS_INFRA_SERVICES`, toolchain provisioning (`HARNESS_ECOSYSTEM`, `HARNESS_INSTALL_CMD`), 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 optional Docker Compose stack + template database |
22
- | `launch.sh` | Launch one agent slot (git worktree by default, deps, env file) |
22
+ | `launch.sh` | Launch one agent slot (git worktree by default, toolchain provisioning, env file) |
23
+ | `provision-toolchain.sh` | Install deps and write toolchain paths (`PYTHON_BIN`, `NPM_BIN`, …) 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 (worktree + env file) |
25
26
  | `agent-cli.sh` | Inspect slot status, run commands in the work dir |
@@ -47,8 +48,8 @@ In Cursor with HAR MCP configured: use `har_launch_environment`, `har_run_verifi
47
48
  ```bash
48
49
  ./.har/setup-infra.sh # when HARNESS_INFRA_SERVICES is non-empty
49
50
  ./.har/launch.sh 1
50
- ./.har/verify.sh 1 # quick: smoke (typecheck + build)
51
- ./.har/verify.sh 1 --full # + unit tests, lint, browser-e2e (if Playwright installed)
51
+ ./.har/verify.sh 1 # quick: ecosystem smoke (compile/import/build)
52
+ ./.har/verify.sh 1 --full # + conventional tests, lint, browser-e2e (if installed)
52
53
  ./.har/teardown.sh 1
53
54
  ```
54
55
 
@@ -62,8 +63,13 @@ each tier's intent, not a fixed command list.
62
63
 
63
64
  | Mode | Command | Typical steps |
64
65
  |------|---------|---------------|
65
- | Quick | `har env verify <id>` or `verify.sh <id>` | Smoke: compile / typecheck / build (language-agnostic) |
66
- | Full | `har env verify <id> --full` or `verify.sh <id> --full` | + unit tests, lint, optional readiness smoke, **browser-e2e** when `stages/browser-e2e.sh` exists |
66
+ | Quick | `har env verify <id>` or `verify.sh <id>` | Stock ecosystem smoke: compile / import / build conventions |
67
+ | Full | `har env verify <id> --full` or `verify.sh <id> --full` | Stock conventional tests/lint + optional readiness smoke, **browser-e2e** when `stages/browser-e2e.sh` exists |
68
+
69
+ The stock commands are deliberately generic conventions keyed by
70
+ `HARNESS_ECOSYSTEM`. Replace them with the repository's real commands during
71
+ adaptation; do not leave Node/npm, Python, Go, Rust, Java, or Ruby defaults in
72
+ place when they do not match the project.
67
73
 
68
74
  For repos that need runtime services, distinguish health from usability. If the
69
75
  harness skips slow local-dev setup, document the skipped steps and add a minimal
@@ -91,6 +97,32 @@ Work in the isolated git worktree created by launch. Use `./.har/agent-cli.sh <i
91
97
 
92
98
  When the project needs Postgres, Redis, or similar, add the service to `docker-compose.agent.yml` (or keep one from the menu), list it in `HARNESS_INFRA_SERVICES` in `harness.env`, and use `setup-infra.sh` — never run raw `docker compose` for shared infra. Shared services run once on fixed ports and serve every agent slot.
93
99
 
100
+ ## Port & shared services (CLI profile)
101
+
102
+ This profile has **no PM2 app ports** — agents run project commands directly in their worktree. Port variables in `harness.env` exist for optional test servers and for shared Docker infra.
103
+
104
+ ### Port allocation
105
+
106
+ | Layer | Scope | Rule | On conflict |
107
+ |-------|-------|------|-------------|
108
+ | Shared Postgres | Per machine | `HARNESS_DB_PORT_DEFAULT` | Scan `HARNESS_DB_PORT_SCAN_START..END` |
109
+ | Other compose services | Per machine | `HARNESS_*_PORT_DEFAULT` for that service | Scan configured ranges in `harness.env` |
110
+
111
+ When a repo adds a local HTTP server for integration tests, prefer reading ports from `.env.agent.<id>` or `./.har/agent-cli.sh <id>` rather than hardcoding values.
112
+
113
+ ### Shared vs per-slot
114
+
115
+ | Resource | Model | Configuration |
116
+ |----------|-------|---------------|
117
+ | Postgres / Redis / mail / … | One shared container on a scanned host port | `HARNESS_INFRA_SERVICES` + matching vars in `harness.env` |
118
+ | Per-slot databases | Cloned from template DB when `db` is enabled | `launch.sh` |
119
+ | Application code | Isolated git worktree per slot | `HARNESS_USE_WORKTREE=true` |
120
+
121
+ ### Do not
122
+
123
+ - Hardcode `15432` or other default infra ports in tests — read `AGENT_DB_PORT` from `.env.agent.<id>` or `har_pg`
124
+ - Run raw `docker compose` for harness infrastructure — use `setup-infra.sh`
125
+
94
126
  ## Maintaining this harness
95
127
 
96
128
  When the project stack changes (new test commands, database needs, env vars):
@@ -116,6 +148,8 @@ work dir printed by launch, never in the main checkout.
116
148
  after explicit user approval.
117
149
  - `teardown` removes the worktree but **keeps the session branch** so you can push it
118
150
  or open a PR (`--delete-branch` to drop it).
151
+ - If launch fails after creating a worktree/env file, resume with
152
+ `har env launch <id> --resume` or `har env recover <id>`.
119
153
  - `har env complete <id>` finishes a session: full verify (recorded as a validation),
120
154
  then teardown — branch kept.
121
155
  - `--no-worktree` runs the slot from the repo root instead (single-agent mode).
@@ -61,7 +61,7 @@ case "$COMMAND" in
61
61
  logs)
62
62
  echo "CLI profile has no managed processes (no PM2)." >&2
63
63
  echo "Run project commands in the work dir, e.g.:" >&2
64
- echo " ./.har/agent-cli.sh ${AGENT_ID} exec npm test" >&2
64
+ echo " ./.har/agent-cli.sh ${AGENT_ID} exec make test" >&2
65
65
  exit 1
66
66
  ;;
67
67