@osfactory/har 0.56.1 → 0.58.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.
@@ -0,0 +1,8 @@
1
+ {
2
+ "id": "cli",
3
+ "description": "CLI/library — shared kernel + CLI overlay (no PM2)",
4
+ "bundles": [
5
+ { "id": "shared-kernel", "path": "runtime-bundles/shared-kernel" },
6
+ { "id": "runtime-cli", "path": "har-boilerplate-cli" }
7
+ ]
8
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "default",
3
+ "description": "Web app — shared kernel + PM2 runtime + Docker infra",
4
+ "bundles": [
5
+ { "id": "shared-kernel", "path": "runtime-bundles/shared-kernel" },
6
+ { "id": "pm2-runtime", "path": "runtime-bundles/pm2-runtime" },
7
+ { "id": "runtime-default", "path": "har-boilerplate" }
8
+ ]
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "ios",
3
+ "description": "iOS — shared kernel + Xcode/Simulator overlay",
4
+ "bundles": [
5
+ { "id": "shared-kernel", "path": "runtime-bundles/shared-kernel" },
6
+ { "id": "xcode-sim", "path": "runtime-bundles/xcode-sim" },
7
+ { "id": "runtime-ios", "path": "har-boilerplate-ios" }
8
+ ]
9
+ }
@@ -0,0 +1,4 @@
1
+ # pm2-runtime
2
+
3
+ Runtime bundle for web/SaaS profiles: PM2 ecosystem template, env.template, and attach.sh.
4
+ Presence of `ecosystem.agent.template.cjs` is the capability marker (`harnessUsesPm2`).
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ # Attach to the tmux session for an agent.
3
+ #
4
+ # Usage: ./.har/attach.sh <agent-id>
5
+ set -euo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ # shellcheck source=/dev/null
9
+ source "$SCRIPT_DIR/harness.env"
10
+ # shellcheck source=/dev/null
11
+ source "$SCRIPT_DIR/agent-slot.sh"
12
+
13
+ AGENT_ID="${1:?Usage: attach.sh <agent-id>}"
14
+ SESSION="$(har_tmux_session "$AGENT_ID")"
15
+
16
+ if ! tmux has-session -t "$SESSION" 2>/dev/null; then
17
+ echo "No tmux session found: $SESSION" >&2
18
+ echo "Launch with: ./.har/launch.sh ${AGENT_ID} --claude" >&2
19
+ exit 1
20
+ fi
21
+
22
+ exec tmux attach -t "$SESSION"
@@ -0,0 +1,46 @@
1
+ /**
2
+ * PM2 Ecosystem Configuration for Agent ${AGENT_ID}
3
+ *
4
+ * Auto-generated by launch.sh — do not edit manually.
5
+ * Customize ecosystem.agent.template.cjs in .har/ to change processes.
6
+ */
7
+
8
+ // launch.sh writes this file (and .env.agent.N) to the work dir root,
9
+ // so __dirname IS the work dir — repo root or the agent's git worktree.
10
+ const path = require('path');
11
+ const dotenv = require('dotenv');
12
+
13
+ const agentEnvPath = path.resolve(__dirname, '.env.agent.${AGENT_ID}');
14
+ const env = dotenv.config({ path: agentEnvPath }).parsed || {};
15
+
16
+ module.exports = {
17
+ apps: [
18
+ // TODO: Replace with the PRIMARY application's dev processes — the ONE app
19
+ // agents modify (HARNESS_PRIMARY_APP in harness.env). In a monolith or
20
+ // monorepo with several services, do NOT start them all here: supporting
21
+ // services the agent depends on but does not change run ONCE for all slots,
22
+ // either as compose services in docker-compose.agent.yml or via an optional
23
+ // .har/ecosystem.shared.config.cjs (started by setup-infra.sh, processes
24
+ // named "har-shared-<name>", fixed ports). A primary app may still need two
25
+ // processes (e.g. api + frontend) — add one entry per process.
26
+ // Example Node dev process. Replace with this repo's actual primary app
27
+ // command (Rails, Django, Go, Java, etc. should not keep this npm entry).
28
+ {
29
+ name: 'har-${HARNESS_PROJECT_NAME}-agent-${AGENT_ID}-api',
30
+ script: 'npm',
31
+ args: 'run dev',
32
+ interpreter: 'none',
33
+ cwd: __dirname,
34
+ env: {
35
+ ...env,
36
+ NODE_ENV: 'development',
37
+ PORT: env.API_PORT || env.PORT,
38
+ },
39
+ watch: false,
40
+ autorestart: true,
41
+ kill_timeout: 5000,
42
+ merge_logs: true,
43
+ time: true,
44
+ },
45
+ ],
46
+ };
@@ -0,0 +1,40 @@
1
+ # Agent environment — generated by launch.sh from env.template
2
+ # DO NOT EDIT — regenerated on every launch
3
+ AGENT_ID=${AGENT_ID}
4
+
5
+ # Ports (allocated at launch — may differ from BASE + AGENT_ID × STEP when busy)
6
+ PORT=${API_PORT}
7
+ FE_PORT=${FE_PORT}
8
+ API_PORT=${API_PORT}
9
+ DEBUG_PORT=${DEBUG_PORT}
10
+
11
+ # PostgreSQL
12
+ PGHOST=localhost
13
+ PGPORT=${DB_PORT}
14
+ PGDATABASE=agent_${AGENT_ID}
15
+ PGUSER=postgres
16
+ PGPASSWORD=password
17
+ DATABASE_URL=postgresql://postgres:password@localhost:${DB_PORT}/agent_${AGENT_ID}
18
+
19
+ # Application
20
+ NODE_ENV=development
21
+ SITE_URL=http://localhost:${API_PORT}
22
+ CORS_ORIGIN=http://localhost:${FE_PORT}
23
+
24
+ # MinIO / S3 (if enabled)
25
+ S3_ENDPOINT=http://localhost:${MINIO_PORT}
26
+ S3_ACCESS_KEY=minioadmin
27
+ S3_SECRET_KEY=minioadmin
28
+ S3_BUCKET=agent-${AGENT_ID}
29
+ S3_REGION=us-east-1
30
+ S3_FORCE_PATH_STYLE=true
31
+
32
+ # Headless browser (if enabled)
33
+ HEADLESS_BROWSER_URL=http://localhost:${BROWSER_PORT}
34
+
35
+ # Toolchain paths — appended by provision-toolchain.sh during launch
36
+ # NODE_BIN= NPM_BIN= PYTHON_BIN= VIRTUAL_ENV= GO_BIN= CARGO_BIN= JAVA_HOME=
37
+
38
+ # Mail (if enabled)
39
+ SMTP_HOST=localhost
40
+ SMTP_PORT=11025
@@ -0,0 +1,3 @@
1
+ # shared-kernel
2
+
3
+ Runtime bundle shared by every HAR profile: toolchain provisioning and the stages authoring guide.
@@ -0,0 +1,99 @@
1
+ # HAR Stages — authoring guide
2
+
3
+ Stages are the harness's single vocabulary for runnable checks and lifecycle
4
+ actions. Everything — shipped plugins (`playwright`, `rocketsim`, `kerno`, `gitleaks`, `trivy`, `semgrep`), your
5
+ project's test/lint commands, bespoke validation scripts — registers in
6
+ `.har/stages.json` with the same schema, and agents interact with stages only
7
+ through that registry (CLI `har env verify`, MCP `har_run_stage` /
8
+ `har_run_verification`), never through stack-specific tooling.
9
+
10
+ Philosophy: *plugins install stages; agents only talk to the stage registry.*
11
+
12
+ ## The registry: `.har/stages.json`
13
+
14
+ ```jsonc
15
+ {
16
+ "verificationStages": ["typecheck", "unit-tests", "api-health", "browser-e2e"],
17
+ "stages": [
18
+ {
19
+ "id": "browser-e2e", // stable, shell-friendly slug
20
+ "kind": "test", // setup | launch | verify | test | inspect | reset | teardown | custom
21
+ "description": "Playwright E2E",
22
+ "script": "stages/browser-e2e.sh", // relative to .har/ — OR use "command"
23
+ "requiresAgentId": true, // default true for test/verify/custom kinds
24
+ "artifacts": [{ "path": ".har/artifacts/browser-e2e", "kind": "directory" }]
25
+ },
26
+ {
27
+ "id": "unit-tests-fast",
28
+ "kind": "test",
29
+ "command": "npm test -- --agent {agentId}" // {agentId} is substituted at run time
30
+ }
31
+ ]
32
+ }
33
+ ```
34
+
35
+ Optional stage fields: `cwd` (working directory), `env` (extra env vars),
36
+ `group`, `acceptsArgs` (extra CLI args the stage accepts, e.g. `["--full"]`).
37
+
38
+ ## Two ways to define a stage
39
+
40
+ **Command stages** — the default for simple checks (`npm test`, `swiftlint`,
41
+ `make check`). One JSON entry, zero files:
42
+
43
+ ```bash
44
+ har env add-stage unit-tests-fast --custom --kind test --command "npm test" --verification
45
+ ```
46
+
47
+ **Script stages** — for anything that needs the slot's env, ports, or
48
+ artifacts. Scaffold a contract-compliant skeleton:
49
+
50
+ ```bash
51
+ har env add-stage db-integrity --custom --script --description "Check DB invariants"
52
+ ```
53
+
54
+ then implement the TODO block in `.har/stages/db-integrity.sh`.
55
+
56
+ ## The stage script contract
57
+
58
+ Every script under `.har/stages/` must:
59
+
60
+ 1. Source `harness.env` and `agent-slot.sh` from `.har/`. Before sourcing,
61
+ set `SCRIPT_DIR` to the `.har/` directory (not `stages/`) — `agent-slot.sh`
62
+ resolves the slot registry via `$SCRIPT_DIR/slots/...`.
63
+ 2. Take the agent slot id as `$1` (validate with `validate_agent_id`); extra
64
+ args may follow.
65
+ 3. Load the slot env via `resolve_agent_env_file` and run checks from the
66
+ agent's work dir (`resolve_agent_work_dir`).
67
+ 4. Write artifacts (reports, screenshots, logs) under `.har/artifacts/<id>/`.
68
+ 5. Print **only** the normalized JSON result object on stdout
69
+ (`status`, `stageId`, `agent_id`, `total_ms`, …); log progress to stderr.
70
+ 6. Exit with the real status code (0 = pass).
71
+
72
+ The scaffolded skeleton implements all of this — replace its TODO block.
73
+
74
+ ## Verification membership
75
+
76
+ Listing a stage id in `verificationStages` is what includes it in
77
+ `har env verify <id> --full`. Ids that match a registered stage run via their
78
+ script/command; ids without a registry entry (e.g. `typecheck`, `api-health`)
79
+ are inline steps owned by `.har/verify.sh`. Lifecycle kinds
80
+ (`setup`/`launch`/`reset`/`teardown`/`inspect`) and `verify` itself never run
81
+ as part of verification, even if listed.
82
+
83
+ ## Commit gate
84
+
85
+ The registry also holds the optional `commitGate` config (installed via
86
+ `har hooks install`): `{ "commitGate": { "mode": "block" | "warn", "scope":
87
+ "worktrees" | "all" } }` controls whether unverified change batches may be
88
+ committed.
89
+
90
+ ## Shipped plugins
91
+
92
+ `har env add-plugin --list` shows available plugins; `har env add-plugin
93
+ playwright` (web), `har env add-plugin rocketsim` (iOS), or `har env
94
+ add-plugin kerno` (backend), `har env add-plugin gitleaks` (secrets scanning, any
95
+ stack), or `har env add-plugin trivy` (dependency + IaC security scan, any stack),
96
+ or `har env add-plugin semgrep` (SAST, any stack)
97
+ installs one. A
98
+ plugin is just packaging: it copies files, merges `package.json` fragments,
99
+ and registers stages through the exact same registry as `add-stage --custom`.
@@ -0,0 +1,407 @@
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
+ # ── Node package manager helpers ──────────────────────────────────────────────
31
+ # Mirrored verbatim in harness.env and provision-toolchain.sh so a sourcing
32
+ # script and the provisioning subprocess resolve the same tool.
33
+
34
+ # Package managers HAR can drive, in fallback preference order.
35
+ HAR_NODE_PACKAGE_MANAGERS="npm bun pnpm yarn"
36
+
37
+ # Package manager the repo declares: an explicit HARNESS_NODE_PACKAGE_MANAGER
38
+ # wins, then package.json "packageManager", then the lockfile. Empty when the
39
+ # repo says nothing.
40
+ har_node_declared_package_manager() {
41
+ local dir="${1:-.}"
42
+
43
+ if [ -n "${HARNESS_NODE_PACKAGE_MANAGER:-}" ]; then
44
+ echo "$HARNESS_NODE_PACKAGE_MANAGER"
45
+ return
46
+ fi
47
+ if [ -f "$dir/package.json" ]; then
48
+ local field
49
+ field="$(sed -n 's/.*"packageManager"[[:space:]]*:[[:space:]]*"\([a-z]*\)@.*/\1/p' "$dir/package.json" | head -1)"
50
+ if [ -n "$field" ]; then
51
+ echo "$field"
52
+ return
53
+ fi
54
+ fi
55
+ if [ -f "$dir/bun.lock" ] || [ -f "$dir/bun.lockb" ]; then echo bun; return; fi
56
+ if [ -f "$dir/pnpm-lock.yaml" ]; then echo pnpm; return; fi
57
+ if [ -f "$dir/yarn.lock" ]; then echo yarn; return; fi
58
+ if [ -f "$dir/package-lock.json" ]; then echo npm; return; fi
59
+ echo ""
60
+ }
61
+
62
+ # Package manager to actually run. A manager the repo declares but this machine
63
+ # lacks falls back to one that is installed, so a repo pinned to npm still
64
+ # provisions on a bun-only machine (and the reverse).
65
+ har_node_package_manager() {
66
+ local dir="${1:-.}"
67
+ local declared
68
+ declared="$(har_node_declared_package_manager "$dir")"
69
+
70
+ if [ -n "$declared" ] && command -v "$declared" >/dev/null 2>&1; then
71
+ echo "$declared"
72
+ return
73
+ fi
74
+
75
+ local candidate
76
+ for candidate in $HAR_NODE_PACKAGE_MANAGERS; do
77
+ if command -v "$candidate" >/dev/null 2>&1; then
78
+ echo "$candidate"
79
+ return
80
+ fi
81
+ done
82
+
83
+ echo "${declared:-npm}"
84
+ }
85
+
86
+ # Lockfile a manager writes, so a substitute install can clean up after itself.
87
+ har_node_lockfile() {
88
+ case "$1" in
89
+ bun) echo "bun.lock" ;;
90
+ npm) echo "package-lock.json" ;;
91
+ pnpm) echo "pnpm-lock.yaml" ;;
92
+ yarn) echo "yarn.lock" ;;
93
+ *) echo "" ;;
94
+ esac
95
+ }
96
+
97
+ # Install dependencies in a directory. When a substitute manager stands in for
98
+ # the one the repo declares, any lockfile it creates is removed afterwards:
99
+ # provisioning must not migrate the repo to a different package manager.
100
+ # (bun writes bun.lock even under --no-save, so the flags alone are not enough.)
101
+ har_node_install() {
102
+ local dir="$1"
103
+ local manager="$2"
104
+ local declared="${3:-}"
105
+ local substituting=false
106
+ local lockfile=""
107
+ local had_lockfile=false
108
+
109
+ if [ -n "$declared" ] && [ "$declared" != "$manager" ]; then
110
+ substituting=true
111
+ lockfile="$(har_node_lockfile "$manager")"
112
+ if [ -n "$lockfile" ] && [ -e "$dir/$lockfile" ]; then
113
+ had_lockfile=true
114
+ fi
115
+ fi
116
+
117
+ local code=0
118
+ (cd "$dir" && "$manager" install --silent) || code=$?
119
+
120
+ if [ "$substituting" = true ] && [ "$had_lockfile" = false ] && [ -n "$lockfile" ]; then
121
+ rm -f "$dir/$lockfile"
122
+ [ "$manager" = "bun" ] && rm -f "$dir/bun.lockb"
123
+ fi
124
+ return $code
125
+ }
126
+
127
+ # Package runner (npx equivalent) for one-off CLIs such as pm2, including the
128
+ # flag that keeps it non-interactive. Takes a manager name, or none to resolve
129
+ # from the environment and PATH.
130
+ har_pkg_exec() {
131
+ case "${1:-${HARNESS_NODE_PACKAGE_MANAGER:-}}" in
132
+ bun) echo "bunx"; return ;;
133
+ pnpm) echo "pnpm dlx"; return ;;
134
+ yarn) echo "yarn dlx"; return ;;
135
+ npm) echo "npx --yes"; return ;;
136
+ esac
137
+ if command -v npx >/dev/null 2>&1; then echo "npx --yes"; return; fi
138
+ if command -v bunx >/dev/null 2>&1; then echo "bunx"; return; fi
139
+ if command -v pnpm >/dev/null 2>&1; then echo "pnpm dlx"; return; fi
140
+ echo "npx --yes"
141
+ }
142
+
143
+ append_env() {
144
+ local key="$1"
145
+ local value="$2"
146
+ # %q, not a raw value: an unquoted value with spaces (an Xcode scheme, a
147
+ # simulator name, a path under /Applications) breaks `source .env.agent.<id>`.
148
+ printf '%s=%q\n' "$key" "$value" >> "$HAR_ENV_FILE"
149
+ }
150
+
151
+ append_path_prefix() {
152
+ local prefix="$1"
153
+ [ -n "$prefix" ] && [ -d "$prefix" ] || return 0
154
+ append_env "PATH" "${prefix}:${PATH:-$PATH}"
155
+ }
156
+
157
+ detect_ecosystem() {
158
+ local dir="$1"
159
+ local configured="${HARNESS_ECOSYSTEM:-auto}"
160
+ if [ -n "$configured" ] && [ "$configured" != "auto" ]; then
161
+ echo "$configured"
162
+ return
163
+ fi
164
+ if [ -f "$dir/package.json" ]; then echo node; return; fi
165
+ if [ -f "$dir/pyproject.toml" ] || [ -f "$dir/setup.py" ] || [ -f "$dir/setup.cfg" ] \
166
+ || [ -f "$dir/requirements.txt" ] || [ -f "$dir/Pipfile" ]; then
167
+ echo python
168
+ return
169
+ fi
170
+ if [ -f "$dir/go.mod" ]; then echo go; return; fi
171
+ if [ -f "$dir/Cargo.toml" ]; then echo rust; return; fi
172
+ if [ -f "$dir/pom.xml" ] || [ -f "$dir/build.gradle" ] || [ -f "$dir/build.gradle.kts" ]; then
173
+ echo java
174
+ return
175
+ fi
176
+ if [ -f "$dir/Gemfile" ]; then echo ruby; return; fi
177
+ if [ -n "${HARNESS_XCODE_SCHEME:-}" ] || [ -n "${HARNESS_XCODE_PROJECT:-}" ] \
178
+ || [ -n "${HARNESS_XCODE_WORKSPACE:-}" ]; then
179
+ echo ios
180
+ return
181
+ fi
182
+ echo none
183
+ }
184
+
185
+ run_install_cmd() {
186
+ local dir="$1"
187
+ if [ -n "${HARNESS_INSTALL_CMD:-}" ]; then
188
+ pt_log "Running HARNESS_INSTALL_CMD..."
189
+ (cd "$dir" && eval "$HARNESS_INSTALL_CMD")
190
+ return
191
+ fi
192
+ return 1
193
+ }
194
+
195
+ provision_node() {
196
+ local dir="$1"
197
+ local node_bin="node"
198
+ local pkg_manager declared_manager
199
+ declared_manager="$(har_node_declared_package_manager "$dir")"
200
+ pkg_manager="$(har_node_package_manager "$dir")"
201
+
202
+ if [ -n "$declared_manager" ] && [ "$declared_manager" != "$pkg_manager" ]; then
203
+ pt_log "Repo declares ${declared_manager}, which is not on PATH — installing with ${pkg_manager} and leaving the lockfile untouched."
204
+ fi
205
+
206
+ if ! run_install_cmd "$dir"; then
207
+ pt_log "Installing Node dependencies with ${pkg_manager}..."
208
+ har_node_install "$dir" "$pkg_manager" "$declared_manager"
209
+ fi
210
+
211
+ if command -v node >/dev/null 2>&1; then
212
+ node_bin="$(command -v node)"
213
+ elif [ "$pkg_manager" = "bun" ]; then
214
+ # bun-only machine: bun runs the `node -e` snippets verify.sh relies on.
215
+ node_bin="$(command -v bun)"
216
+ fi
217
+
218
+ local npm_bin="$pkg_manager"
219
+ if command -v "$pkg_manager" >/dev/null 2>&1; then
220
+ npm_bin="$(command -v "$pkg_manager")"
221
+ fi
222
+
223
+ append_env "HARNESS_ECOSYSTEM" "node"
224
+ append_env "NODE_BIN" "$node_bin"
225
+ append_env "NPM_BIN" "$npm_bin"
226
+ append_env "HARNESS_NODE_PACKAGE_MANAGER" "$pkg_manager"
227
+ append_env "HARNESS_PKG_EXEC" "$(har_pkg_exec "$pkg_manager")"
228
+ append_path_prefix "$dir/node_modules/.bin"
229
+ }
230
+
231
+ provision_python() {
232
+ local dir="$1"
233
+ local venv_rel="${HARNESS_PYTHON_VENV_DIR:-.har/venv}"
234
+ local venv_dir="$dir/$venv_rel"
235
+ local python_bin="python3"
236
+
237
+ if ! command -v python3 >/dev/null 2>&1; then
238
+ pt_log "python3 not found on PATH — skipping venv provisioning"
239
+ append_env "HARNESS_ECOSYSTEM" "python"
240
+ append_env "PYTHON_BIN" "${PYTHON_BIN:-python3}"
241
+ return
242
+ fi
243
+
244
+ if [ ! -d "$venv_dir" ]; then
245
+ pt_log "Creating Python venv at $venv_rel..."
246
+ if ! python3 -m venv "$venv_dir"; then
247
+ rm -rf "$venv_dir"
248
+ pt_log "python3 -m venv failed (on Debian/Ubuntu install python3-venv) — using system python3"
249
+ append_env "HARNESS_ECOSYSTEM" "python"
250
+ append_env "PYTHON_BIN" "$(command -v python3)"
251
+ return
252
+ fi
253
+ fi
254
+
255
+ if [ ! -x "$venv_dir/bin/python" ]; then
256
+ pt_log "Python venv at $venv_rel is missing or broken — using system python3"
257
+ append_env "HARNESS_ECOSYSTEM" "python"
258
+ append_env "PYTHON_BIN" "$(command -v python3)"
259
+ return
260
+ fi
261
+
262
+ python_bin="$venv_dir/bin/python"
263
+ # shellcheck disable=SC1091
264
+ source "$venv_dir/bin/activate"
265
+
266
+ if ! run_install_cmd "$dir"; then
267
+ pt_log "Installing Python dependencies..."
268
+ if [ -f "$dir/pyproject.toml" ]; then
269
+ (cd "$dir" && pip install -q -e ".[dev]" 2>/dev/null) || (cd "$dir" && pip install -q -e .)
270
+ elif [ -f "$dir/requirements.txt" ]; then
271
+ (cd "$dir" && pip install -q -r requirements.txt)
272
+ elif [ -f "$dir/setup.py" ] || [ -f "$dir/setup.cfg" ]; then
273
+ (cd "$dir" && pip install -q -e .)
274
+ elif [ -f "$dir/Pipfile" ] && command -v pipenv >/dev/null 2>&1; then
275
+ (cd "$dir" && pipenv install --dev)
276
+ python_bin="$(cd "$dir" && pipenv --py)"
277
+ fi
278
+ fi
279
+
280
+ append_env "HARNESS_ECOSYSTEM" "python"
281
+ append_env "PYTHON_BIN" "$python_bin"
282
+ append_env "VIRTUAL_ENV" "$venv_dir"
283
+ append_path_prefix "$venv_dir/bin"
284
+ }
285
+
286
+ provision_go() {
287
+ local dir="$1"
288
+ if ! run_install_cmd "$dir"; then
289
+ if command -v go >/dev/null 2>&1; then
290
+ pt_log "Downloading Go modules..."
291
+ (cd "$dir" && go mod download)
292
+ else
293
+ pt_log "go not found on PATH — record paths only"
294
+ fi
295
+ fi
296
+ append_env "HARNESS_ECOSYSTEM" "go"
297
+ append_env "GO_BIN" "$(command -v go 2>/dev/null || echo go)"
298
+ if [ -n "${GOPATH:-}" ]; then append_env "GOPATH" "$GOPATH"; fi
299
+ if [ -n "${GOROOT:-}" ]; then append_env "GOROOT" "$GOROOT"; fi
300
+ }
301
+
302
+ provision_rust() {
303
+ local dir="$1"
304
+ if ! run_install_cmd "$dir"; then
305
+ if command -v cargo >/dev/null 2>&1; then
306
+ pt_log "Fetching Rust dependencies..."
307
+ (cd "$dir" && cargo fetch)
308
+ else
309
+ pt_log "cargo not found on PATH — record paths only"
310
+ fi
311
+ fi
312
+ append_env "HARNESS_ECOSYSTEM" "rust"
313
+ append_env "CARGO_BIN" "$(command -v cargo 2>/dev/null || echo cargo)"
314
+ append_env "RUSTC_BIN" "$(command -v rustc 2>/dev/null || echo rustc)"
315
+ }
316
+
317
+ provision_java() {
318
+ local dir="$1"
319
+ run_install_cmd "$dir" || true
320
+ append_env "HARNESS_ECOSYSTEM" "java"
321
+ if [ -n "${JAVA_HOME:-}" ]; then
322
+ append_env "JAVA_HOME" "$JAVA_HOME"
323
+ append_path_prefix "$JAVA_HOME/bin"
324
+ fi
325
+ if command -v mvn >/dev/null 2>&1; then
326
+ append_env "MVN_BIN" "$(command -v mvn)"
327
+ elif [ -f "$dir/gradlew" ]; then
328
+ append_env "GRADLE_BIN" "$dir/gradlew"
329
+ elif command -v gradle >/dev/null 2>&1; then
330
+ append_env "GRADLE_BIN" "$(command -v gradle)"
331
+ fi
332
+ }
333
+
334
+ provision_ruby() {
335
+ local dir="$1"
336
+ if ! run_install_cmd "$dir"; then
337
+ if command -v bundle >/dev/null 2>&1; then
338
+ pt_log "Installing Ruby gems..."
339
+ (cd "$dir" && bundle install --quiet)
340
+ else
341
+ pt_log "bundle not found on PATH — record paths only"
342
+ fi
343
+ fi
344
+ append_env "HARNESS_ECOSYSTEM" "ruby"
345
+ append_env "RUBY_BIN" "$(command -v ruby 2>/dev/null || echo ruby)"
346
+ append_env "BUNDLE_BIN" "$(command -v bundle 2>/dev/null || echo bundle)"
347
+ append_path_prefix "$dir/vendor/bundle/bin"
348
+ }
349
+
350
+ provision_ios() {
351
+ local dir="$1"
352
+ run_install_cmd "$dir" || true
353
+ append_env "HARNESS_ECOSYSTEM" "ios"
354
+ append_env "XCODEBUILD_BIN" "$(command -v xcodebuild 2>/dev/null || echo xcodebuild)"
355
+ if [ -n "${HARNESS_XCODE_SCHEME:-}" ]; then
356
+ append_env "HARNESS_XCODE_SCHEME" "$HARNESS_XCODE_SCHEME"
357
+ fi
358
+ # The simulator is not recorded here: launch.sh writes the device reserved for
359
+ # this slot, and harness.env already carries the shared one.
360
+ if [ -n "${HARNESS_BUNDLE_ID:-}" ]; then
361
+ append_env "HARNESS_BUNDLE_ID" "$HARNESS_BUNDLE_ID"
362
+ fi
363
+ if [ -n "${DEVELOPER_DIR:-}" ]; then
364
+ append_env "DEVELOPER_DIR" "$DEVELOPER_DIR"
365
+ fi
366
+ }
367
+
368
+ provision_monorepo_root() {
369
+ [ -n "$HAR_REL_PREFIX" ] || return 0
370
+ [ -n "$HAR_WORKTREE_DIR" ] || return 0
371
+ [ -f "$HAR_WORKTREE_DIR/package.json" ] || return 0
372
+ [ -d "$HAR_WORKTREE_DIR/node_modules" ] && return 0
373
+ local pkg_manager
374
+ pkg_manager="$(har_node_package_manager "$HAR_WORKTREE_DIR")"
375
+ pt_log "Installing monorepo root dependencies in $HAR_WORKTREE_DIR with ${pkg_manager}..."
376
+ har_node_install "$HAR_WORKTREE_DIR" "$pkg_manager" \
377
+ "$(har_node_declared_package_manager "$HAR_WORKTREE_DIR")"
378
+ }
379
+
380
+ provision_ecosystem() {
381
+ local dir="$1"
382
+ local ecosystem
383
+ ecosystem="$(detect_ecosystem "$dir")"
384
+ pt_log "Toolchain ecosystem: ${ecosystem} (work dir: ${dir})"
385
+
386
+ case "$ecosystem" in
387
+ node) provision_node "$dir" ;;
388
+ python) provision_python "$dir" ;;
389
+ go) provision_go "$dir" ;;
390
+ rust) provision_rust "$dir" ;;
391
+ java) provision_java "$dir" ;;
392
+ ruby) provision_ruby "$dir" ;;
393
+ ios) provision_ios "$dir" ;;
394
+ none)
395
+ if run_install_cmd "$dir"; then
396
+ append_env "HARNESS_ECOSYSTEM" "custom"
397
+ else
398
+ pt_log "No ecosystem manifest detected — set HARNESS_ECOSYSTEM or HARNESS_INSTALL_CMD in harness.env"
399
+ append_env "HARNESS_ECOSYSTEM" "none"
400
+ fi
401
+ ;;
402
+ esac
403
+ }
404
+
405
+ append_env "HARNESS_TOOLCHAIN_PROVISIONED" "true"
406
+ provision_ecosystem "$HAR_WORK_DIR"
407
+ provision_monorepo_root
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bash
2
+ # Per-stage scripts live here (e.g. ./.har/stages/browser-e2e.sh <agent-id>).
3
+ #
4
+ # Read .har/STAGES.md for the authoring guide: the stage script contract,
5
+ # command vs script stages, and how verificationStages controls verify --full.
6
+ #
7
+ # Scaffold a new one with:
8
+ # har env add-stage <id> --custom --script
9
+
10
+ echo "Add stage scripts under .har/stages/ and register them in stages.json — see .har/STAGES.md." >&2
11
+ exit 1
@@ -0,0 +1,4 @@
1
+ # xcode-sim
2
+
3
+ Runtime bundle for iOS profiles: Simulator helpers (`simulator.sh`).
4
+ Presence of this file is the capability marker (`harnessUsesSimulator`).