@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.
- package/README.md +2 -0
- package/dist/index.js +1817 -651
- package/dist/prompts/system-authoring.md +27 -2
- package/dist/templates/adaptation-prompt-init.md +60 -5
- package/dist/templates/adaptation-prompt-maintain.md +7 -4
- package/dist/templates/har-boilerplate/CLAUDE.agent.md +5 -3
- package/dist/templates/har-boilerplate/README.md +51 -9
- package/dist/templates/har-boilerplate/agent-cli.sh +72 -26
- package/dist/templates/har-boilerplate/agent-slot.sh +351 -0
- package/dist/templates/har-boilerplate/attach.sh +7 -1
- package/dist/templates/har-boilerplate/ecosystem.agent.template.cjs +3 -1
- package/dist/templates/har-boilerplate/env.template +4 -1
- package/dist/templates/har-boilerplate/harness.env +32 -0
- package/dist/templates/har-boilerplate/launch.sh +167 -120
- package/dist/templates/har-boilerplate/preflight.sh +41 -0
- package/dist/templates/har-boilerplate/provision-toolchain.sh +285 -0
- package/dist/templates/har-boilerplate/setup-infra.sh +87 -11
- package/dist/templates/har-boilerplate/teardown.sh +2 -1
- package/dist/templates/har-boilerplate/verify.sh +91 -11
- package/dist/templates/har-boilerplate-cli/CLAUDE.agent.md +5 -2
- package/dist/templates/har-boilerplate-cli/README.md +40 -6
- package/dist/templates/har-boilerplate-cli/agent-cli.sh +1 -1
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +331 -0
- package/dist/templates/har-boilerplate-cli/harness.env +13 -0
- package/dist/templates/har-boilerplate-cli/launch.sh +140 -95
- package/dist/templates/har-boilerplate-cli/preflight.sh +37 -0
- package/dist/templates/har-boilerplate-cli/provision-toolchain.sh +285 -0
- package/dist/templates/har-boilerplate-cli/setup-infra.sh +87 -11
- package/dist/templates/har-boilerplate-cli/verify.sh +93 -12
- package/dist/templates/har-boilerplate-ios/README.md +29 -2
- package/dist/templates/har-boilerplate-ios/harness.env +6 -1
- package/dist/templates/har-boilerplate-ios/launch.sh +8 -13
- package/dist/templates/har-boilerplate-ios/provision-toolchain.sh +285 -0
- package/dist/templates/har-boilerplate-ios/verify.sh +20 -20
- package/package.json +1 -1
- package/dist/templates/har-boilerplate-ios/ADAPT-PROMPT.md +0 -57
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Launch readiness gate for CLI/library harnesses (occupied slot only).
|
|
3
|
+
# Usage: ./.har/preflight.sh <agent-id> [--replace] [--force]
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
8
|
+
|
|
9
|
+
# shellcheck source=/dev/null
|
|
10
|
+
source "$SCRIPT_DIR/harness.env"
|
|
11
|
+
# shellcheck source=/dev/null
|
|
12
|
+
source "$SCRIPT_DIR/agent-slot.sh"
|
|
13
|
+
|
|
14
|
+
AGENT_ID="${1:-}"
|
|
15
|
+
FORCE=false
|
|
16
|
+
REPLACE=false
|
|
17
|
+
|
|
18
|
+
for arg in "$@"; do
|
|
19
|
+
case "$arg" in
|
|
20
|
+
--replace) REPLACE=true ;;
|
|
21
|
+
--force) FORCE=true ;;
|
|
22
|
+
esac
|
|
23
|
+
done
|
|
24
|
+
|
|
25
|
+
if [[ -z "$AGENT_ID" ]]; then
|
|
26
|
+
echo "Usage: $0 <agent-id> [--replace] [--force]" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
validate_agent_id "$AGENT_ID"
|
|
31
|
+
|
|
32
|
+
if har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE"; then
|
|
33
|
+
echo "Slot ${AGENT_ID}: ready to launch."
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
exit $?
|
|
@@ -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="$
|
|
30
|
-
AGENT_MINIO_CONSOLE_PORT="$
|
|
31
|
-
AGENT_BROWSER_PORT="$
|
|
32
|
-
AGENT_MAILPIT_WEB_PORT="$
|
|
33
|
-
AGENT_MAILPIT_SMTP_PORT="$
|
|
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
|
|
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:${
|
|
100
|
-
har_infra_enabled headless-browser && log " Browser: http://localhost:${
|
|
101
|
-
har_infra_enabled mailpit && log " Mailpit: http://localhost:${
|
|
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
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
#
|
|
5
5
|
# Usage: ./.har/verify.sh <agent-id> [--full]
|
|
6
6
|
#
|
|
7
|
-
# Quick (default): smoke — compile
|
|
8
|
-
# Full (--full): +
|
|
9
|
-
#
|
|
7
|
+
# Quick (default): ecosystem smoke — compile/import/build 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)"
|
|
@@ -61,7 +61,7 @@ run_step() {
|
|
|
61
61
|
start=$(now_ms)
|
|
62
62
|
|
|
63
63
|
set +e
|
|
64
|
-
output=$(cd "$WORK_DIR" && eval "$cmd" 2>&1)
|
|
64
|
+
output=$(cd "$WORK_DIR" && set -a && . "$ENV_FILE" && set +a && eval "$cmd" 2>&1)
|
|
65
65
|
exit_code=$?
|
|
66
66
|
set -e
|
|
67
67
|
|
|
@@ -93,16 +93,97 @@ process.stdout.write(JSON.stringify(arr));
|
|
|
93
93
|
fi
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
node_package_script_exists() {
|
|
97
|
+
local script="$1"
|
|
98
|
+
"${NODE_BIN:-node}" -e "
|
|
99
|
+
const fs = require('fs');
|
|
100
|
+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
101
|
+
const script = process.argv[1];
|
|
102
|
+
process.exit(pkg.scripts && pkg.scripts[script] ? 0 : 1);
|
|
103
|
+
" "$script"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
run_node_script_or_skip() {
|
|
107
|
+
local name="$1"
|
|
108
|
+
local script="$2"
|
|
109
|
+
if node_package_script_exists "$script"; then
|
|
110
|
+
run_step "$name" "${NPM_BIN:-npm} run ${script}"
|
|
111
|
+
else
|
|
112
|
+
run_step "$name" "echo 'No ${script} script configured; skipping.'"
|
|
113
|
+
fi
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
run_node_quick_smoke() {
|
|
117
|
+
if node_package_script_exists typecheck; then
|
|
118
|
+
run_step "node-typecheck" '${NPM_BIN:-npm} run typecheck'
|
|
119
|
+
elif node_package_script_exists build; then
|
|
120
|
+
run_step "node-build" '${NPM_BIN:-npm} run build'
|
|
121
|
+
else
|
|
122
|
+
run_step "node-package-load" '${NODE_BIN:-node} -e "require(\"./package.json\")"'
|
|
123
|
+
fi
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
run_quick_smoke() {
|
|
127
|
+
case "${HARNESS_ECOSYSTEM:-none}" in
|
|
128
|
+
node)
|
|
129
|
+
run_node_quick_smoke
|
|
130
|
+
;;
|
|
131
|
+
python)
|
|
132
|
+
run_step "python-compile" '${PYTHON_BIN:-python3} -m compileall -q .'
|
|
133
|
+
;;
|
|
134
|
+
go)
|
|
135
|
+
run_step "go-build" '${GO_BIN:-go} build ./...'
|
|
136
|
+
;;
|
|
137
|
+
rust)
|
|
138
|
+
run_step "rust-check" '${CARGO_BIN:-cargo} check'
|
|
139
|
+
;;
|
|
140
|
+
java)
|
|
141
|
+
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'
|
|
142
|
+
;;
|
|
143
|
+
ruby)
|
|
144
|
+
run_step "ruby-smoke" '${RUBY_BIN:-ruby} -e "puts RUBY_VERSION"'
|
|
145
|
+
;;
|
|
146
|
+
custom|none|*)
|
|
147
|
+
run_step "smoke-not-configured" 'echo "No stock smoke for HARNESS_ECOSYSTEM=${HARNESS_ECOSYSTEM:-none}; adapt .har/verify.sh for this repo."'
|
|
148
|
+
;;
|
|
149
|
+
esac
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
run_full_checks() {
|
|
153
|
+
case "${HARNESS_ECOSYSTEM:-none}" in
|
|
154
|
+
node)
|
|
155
|
+
run_node_script_or_skip "unit-tests" test || true
|
|
156
|
+
run_node_script_or_skip "lint" lint || true
|
|
157
|
+
;;
|
|
158
|
+
python)
|
|
159
|
+
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
|
|
160
|
+
;;
|
|
161
|
+
go)
|
|
162
|
+
run_step "unit-tests" '${GO_BIN:-go} test ./...' || true
|
|
163
|
+
;;
|
|
164
|
+
rust)
|
|
165
|
+
run_step "unit-tests" '${CARGO_BIN:-cargo} test' || true
|
|
166
|
+
;;
|
|
167
|
+
java)
|
|
168
|
+
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
|
|
169
|
+
;;
|
|
170
|
+
ruby)
|
|
171
|
+
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
|
|
172
|
+
;;
|
|
173
|
+
custom|none|*)
|
|
174
|
+
run_step "unit-tests" 'echo "No stock full checks for HARNESS_ECOSYSTEM=${HARNESS_ECOSYSTEM:-none}; adapt .har/verify.sh for this repo."' || true
|
|
175
|
+
;;
|
|
176
|
+
esac
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# ── Verification stages ─────────────────────────────────────────────────────
|
|
180
|
+
# These stock steps are intentionally generic conventions. Adapt this section
|
|
181
|
+
# to the repository's real commands from package.json, Makefile, CI, pyproject,
|
|
182
|
+
# Cargo.toml, go.mod, pom.xml, etc.
|
|
183
|
+
run_quick_smoke || { [ -z "$FULL" ] && true; }
|
|
101
184
|
|
|
102
185
|
if [ -n "$FULL" ]; then
|
|
103
|
-
|
|
104
|
-
run_step "unit-tests" "npm test" || true
|
|
105
|
-
run_step "lint" "npm run lint" || true
|
|
186
|
+
run_full_checks
|
|
106
187
|
run_step "readiness" "run_readiness_if_configured \"$AGENT_ID\"" || true
|
|
107
188
|
run_step "browser-e2e" "run_browser_e2e_if_present \"$SCRIPT_DIR\" \"$AGENT_ID\"" || true
|
|
108
189
|
fi
|
|
@@ -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: Xcode scheme, simulator name, bundle ID, infra flags |
|
|
15
|
+
| `harness.env` | Shared config: Xcode scheme, simulator name, bundle ID, toolchain provisioning, infra flags |
|
|
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 — gitignored |
|
|
19
19
|
| `artifacts/` | Stage outputs: test results, screenshots, logs |
|
|
20
20
|
| `agent-slot.sh` | Shared agent-id validation and slot registry helpers |
|
|
21
21
|
| `setup-infra.sh` | Boot the iOS Simulator; start optional Docker services |
|
|
22
|
-
| `launch.sh` | Launch one agent slot (git worktree,
|
|
22
|
+
| `launch.sh` | Launch one agent slot (git worktree, toolchain provisioning, env file) |
|
|
23
|
+
| `provision-toolchain.sh` | Write Xcode/simulator paths (`XCODEBUILD_BIN`, …) to `.env.agent.<id>` |
|
|
23
24
|
| `verify.sh` | Verification pipeline (build smoke by default; --full adds tests, lint, flows) |
|
|
24
25
|
| `teardown.sh` | Tear down one agent slot (worktree + env file) |
|
|
25
26
|
| `agent-cli.sh` | Inspect slot status, run xcodebuild commands, install/launch app |
|
|
@@ -84,6 +85,32 @@ Edit **`harness.env`** to set:
|
|
|
84
85
|
- `HARNESS_SIMULATOR_NAME` — target simulator (must be listed in `xcrun simctl list devices`)
|
|
85
86
|
- `HARNESS_BUNDLE_ID` — app bundle identifier
|
|
86
87
|
|
|
88
|
+
## Port & shared services (iOS profile)
|
|
89
|
+
|
|
90
|
+
Pure iOS apps have **no per-slot TCP ports** — agents build and run on a shared iOS Simulator. Optional backend containers (mock API, etc.) use the same shared-infra model as other profiles.
|
|
91
|
+
|
|
92
|
+
### Port allocation
|
|
93
|
+
|
|
94
|
+
| Layer | Scope | Rule | On conflict |
|
|
95
|
+
|-------|-------|------|-------------|
|
|
96
|
+
| Optional backend (compose) | Per machine | `HARNESS_*_PORT_DEFAULT` for that service | Scan configured ranges in `harness.env` |
|
|
97
|
+
| iOS Simulator | Shared | No harness port — one booted simulator for all slots | N/A |
|
|
98
|
+
|
|
99
|
+
When your app talks to a local backend, read the resolved host port from `.env.agent.<id>` (set by `setup-infra.sh`) — never hardcode `15432` or similar in tests or flow scripts.
|
|
100
|
+
|
|
101
|
+
### Shared vs per-slot
|
|
102
|
+
|
|
103
|
+
| Resource | Model | Configuration |
|
|
104
|
+
|----------|-------|---------------|
|
|
105
|
+
| iOS Simulator | One booted simulator shared by all slots | `HARNESS_SIMULATOR_NAME`, `setup-infra.sh` |
|
|
106
|
+
| Optional backend | One shared compose service | `HARNESS_INFRA_SERVICES` (e.g. `"mock-server"`) |
|
|
107
|
+
| Application code | Isolated git worktree per slot | `HARNESS_USE_WORKTREE=true` |
|
|
108
|
+
|
|
109
|
+
### Do not
|
|
110
|
+
|
|
111
|
+
- Hardcode backend ports in RocketSim flows or unit tests — read from agent env
|
|
112
|
+
- Run raw `docker compose` for harness infrastructure — use `setup-infra.sh`
|
|
113
|
+
|
|
87
114
|
## Session lifecycle
|
|
88
115
|
|
|
89
116
|
Every `launch` starts a **fresh session**: a new git worktree from the current HEAD at
|
|
@@ -7,6 +7,10 @@ export HARNESS_USE_WORKTREE=true
|
|
|
7
7
|
export HARNESS_AGENT_SLOT_MIN=1
|
|
8
8
|
export HARNESS_AGENT_SLOT_MAX=3
|
|
9
9
|
|
|
10
|
+
# Toolchain provisioning — launch writes Xcode/simulator paths into .env.agent.<id>
|
|
11
|
+
export HARNESS_ECOSYSTEM="ios"
|
|
12
|
+
# export HARNESS_INSTALL_CMD="xcodebuild -resolvePackageDependencies ..."
|
|
13
|
+
|
|
10
14
|
# ── Xcode project ─────────────────────────────────────────────────────────────
|
|
11
15
|
# Set one of HARNESS_XCODE_WORKSPACE or HARNESS_XCODE_PROJECT (workspace wins).
|
|
12
16
|
export HARNESS_XCODE_WORKSPACE="" # e.g. MyApp.xcworkspace (relative to repo root)
|
|
@@ -29,8 +33,9 @@ export HARNESS_IOS_DESTINATION="platform=iOS Simulator,name=${HARNESS_SIMULATOR_
|
|
|
29
33
|
export HARNESS_SWIFTLINT_CMD="${HARNESS_SWIFTLINT_CMD:-swiftlint}"
|
|
30
34
|
|
|
31
35
|
# ── Infrastructure ────────────────────────────────────────────────────────────
|
|
32
|
-
# No Docker infra for pure iOS apps. Set
|
|
36
|
+
# No Docker infra for pure iOS apps. Set service names here (space-separated)
|
|
33
37
|
# only if your app needs a local backend container (e.g. a mock server).
|
|
38
|
+
# When enabled, add matching HARNESS_*_PORT_DEFAULT / SCAN_* vars for each service.
|
|
34
39
|
export HARNESS_INFRA_SERVICES=""
|
|
35
40
|
|
|
36
41
|
# Returns 0 when a shared infra service is enabled: har_infra_enabled mock-server
|