@osfactory/har 0.1.1

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 (71) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +208 -0
  3. package/control/docker-compose.build.yml +7 -0
  4. package/control/docker-compose.yml +31 -0
  5. package/dist/index.js +41753 -0
  6. package/dist/prompts/system-authoring.md +104 -0
  7. package/dist/templates/AGENT.md.template +40 -0
  8. package/dist/templates/CLAUDE.md.template +5 -0
  9. package/dist/templates/adaptation-prompt-init.md +124 -0
  10. package/dist/templates/adaptation-prompt-maintain.md +73 -0
  11. package/dist/templates/cursor-rule.mdc.template +101 -0
  12. package/dist/templates/har-boilerplate/CLAUDE.agent.md +54 -0
  13. package/dist/templates/har-boilerplate/README.md +134 -0
  14. package/dist/templates/har-boilerplate/agent-cli.sh +172 -0
  15. package/dist/templates/har-boilerplate/agent-slot.sh +270 -0
  16. package/dist/templates/har-boilerplate/attach.sh +16 -0
  17. package/dist/templates/har-boilerplate/docker-compose.agent.yml +66 -0
  18. package/dist/templates/har-boilerplate/ecosystem.agent.template.cjs +44 -0
  19. package/dist/templates/har-boilerplate/env.template +37 -0
  20. package/dist/templates/har-boilerplate/harness.env +50 -0
  21. package/dist/templates/har-boilerplate/justfile +39 -0
  22. package/dist/templates/har-boilerplate/launch.sh +229 -0
  23. package/dist/templates/har-boilerplate/setup-infra.sh +102 -0
  24. package/dist/templates/har-boilerplate/stages/README.sh +9 -0
  25. package/dist/templates/har-boilerplate/stages.json +67 -0
  26. package/dist/templates/har-boilerplate/teardown.sh +86 -0
  27. package/dist/templates/har-boilerplate/verify.sh +163 -0
  28. package/dist/templates/har-boilerplate-cli/CLAUDE.agent.md +41 -0
  29. package/dist/templates/har-boilerplate-cli/README.md +112 -0
  30. package/dist/templates/har-boilerplate-cli/agent-cli.sh +162 -0
  31. package/dist/templates/har-boilerplate-cli/agent-slot.sh +270 -0
  32. package/dist/templates/har-boilerplate-cli/docker-compose.agent.yml +64 -0
  33. package/dist/templates/har-boilerplate-cli/harness.env +40 -0
  34. package/dist/templates/har-boilerplate-cli/justfile +27 -0
  35. package/dist/templates/har-boilerplate-cli/launch.sh +132 -0
  36. package/dist/templates/har-boilerplate-cli/setup-infra.sh +102 -0
  37. package/dist/templates/har-boilerplate-cli/stages/README.sh +9 -0
  38. package/dist/templates/har-boilerplate-cli/stages.json +61 -0
  39. package/dist/templates/har-boilerplate-cli/teardown.sh +66 -0
  40. package/dist/templates/har-boilerplate-cli/verify.sh +113 -0
  41. package/dist/templates/har-boilerplate-ios/ADAPT-PROMPT.md +57 -0
  42. package/dist/templates/har-boilerplate-ios/CLAUDE.agent.md +65 -0
  43. package/dist/templates/har-boilerplate-ios/README.md +88 -0
  44. package/dist/templates/har-boilerplate-ios/agent-cli.sh +125 -0
  45. package/dist/templates/har-boilerplate-ios/agent-slot.sh +270 -0
  46. package/dist/templates/har-boilerplate-ios/docker-compose.agent.yml +17 -0
  47. package/dist/templates/har-boilerplate-ios/harness.env +42 -0
  48. package/dist/templates/har-boilerplate-ios/justfile +33 -0
  49. package/dist/templates/har-boilerplate-ios/launch.sh +132 -0
  50. package/dist/templates/har-boilerplate-ios/setup-infra.sh +84 -0
  51. package/dist/templates/har-boilerplate-ios/stages/README.sh +12 -0
  52. package/dist/templates/har-boilerplate-ios/stages.json +61 -0
  53. package/dist/templates/har-boilerplate-ios/teardown.sh +64 -0
  54. package/dist/templates/har-boilerplate-ios/verify.sh +165 -0
  55. package/dist/templates/stage-templates/playwright/.github/workflows/playwright.yml +30 -0
  56. package/dist/templates/stage-templates/playwright/.har/stages/PLAYWRIGHT.md +21 -0
  57. package/dist/templates/stage-templates/playwright/.har/stages/browser-e2e.sh +83 -0
  58. package/dist/templates/stage-templates/playwright/README.md +14 -0
  59. package/dist/templates/stage-templates/playwright/package.fragment.json +14 -0
  60. package/dist/templates/stage-templates/playwright/playwright.config.js +30 -0
  61. package/dist/templates/stage-templates/playwright/template.manifest.json +44 -0
  62. package/dist/templates/stage-templates/playwright/tests/a11y/smoke.spec.js +14 -0
  63. package/dist/templates/stage-templates/playwright/tests/api/health.spec.js +10 -0
  64. package/dist/templates/stage-templates/playwright/tests/frontend/smoke.spec.js +9 -0
  65. package/dist/templates/stage-templates/rocketsim/.har/stages/ROCKETSIM.md +126 -0
  66. package/dist/templates/stage-templates/rocketsim/.har/stages/rocketsim-flows.sh +188 -0
  67. package/dist/templates/stage-templates/rocketsim/README.md +66 -0
  68. package/dist/templates/stage-templates/rocketsim/flows/README.md +36 -0
  69. package/dist/templates/stage-templates/rocketsim/flows/example-smoke.sh +60 -0
  70. package/dist/templates/stage-templates/rocketsim/template.manifest.json +38 -0
  71. package/package.json +78 -0
@@ -0,0 +1,39 @@
1
+ # Agent harness shortcuts — requires `just` (https://github.com/casey/just)
2
+
3
+ set shell := ["bash", "-cu"]
4
+
5
+ setup:
6
+ ./setup-infra.sh
7
+
8
+ launch id:
9
+ ./launch.sh {{id}}
10
+
11
+ teardown id:
12
+ ./teardown.sh {{id}}
13
+
14
+ verify id:
15
+ ./verify.sh {{id}}
16
+
17
+ verify-full id:
18
+ ./verify.sh {{id}} --full
19
+
20
+ status:
21
+ #!/usr/bin/env bash
22
+ for id in 1 2 3 4 5; do
23
+ ./agent-cli.sh "$id" status 2>/dev/null || true
24
+ done
25
+
26
+ launch-all count:
27
+ #!/usr/bin/env bash
28
+ for id in $(seq 1 {{count}}); do
29
+ ./launch.sh "$id"
30
+ done
31
+
32
+ teardown-all:
33
+ #!/usr/bin/env bash
34
+ for id in 1 2 3 4 5; do
35
+ ./teardown.sh "$id" 2>/dev/null || true
36
+ done
37
+
38
+ cli id cmd *args:
39
+ ./agent-cli.sh {{id}} {{cmd}} {{args}}
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env bash
2
+ # Launches a single agent instance with isolated ports, database, and PM2 processes.
3
+ # Every launch starts a FRESH session: any previous session for the slot is torn
4
+ # down (its branch is kept) and a new suffixed worktree is created from HEAD.
5
+ #
6
+ # Usage: ./.har/launch.sh <agent-id> [--no-worktree] [--claude] [--replace] [--force]
7
+ set -euo pipefail
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
11
+
12
+ # shellcheck source=/dev/null
13
+ source "$SCRIPT_DIR/harness.env"
14
+ # shellcheck source=/dev/null
15
+ source "$SCRIPT_DIR/agent-slot.sh"
16
+
17
+ AGENT_ID="${1:-}"
18
+ USE_WORKTREE="${HARNESS_USE_WORKTREE:-true}"
19
+ USE_CLAUDE=false
20
+ FORCE=false
21
+ REPLACE=false
22
+ PURPOSE="${HAR_SESSION_PURPOSE:-}"
23
+
24
+ for arg in "$@"; do
25
+ case "$arg" in
26
+ --no-worktree) USE_WORKTREE=false ;;
27
+ --worktree) USE_WORKTREE=true ;;
28
+ --claude) USE_CLAUDE=true ;;
29
+ --replace) REPLACE=true ;;
30
+ --force) FORCE=true ;;
31
+ --purpose=*) PURPOSE="${arg#--purpose=}" ;;
32
+ esac
33
+ done
34
+
35
+ if [[ -z "$AGENT_ID" ]]; then
36
+ echo "Usage: $0 <agent-id> [--no-worktree] [--claude] [--replace] [--force] [--purpose=label]" >&2
37
+ echo " agent-id must be between ${HARNESS_AGENT_SLOT_MIN} and ${HARNESS_AGENT_SLOT_MAX}" >&2
38
+ exit 1
39
+ fi
40
+
41
+ validate_agent_id "$AGENT_ID"
42
+
43
+ log() { echo "==> [agent-$AGENT_ID] $*" >&2; }
44
+
45
+ FE_PORT=$(( HARNESS_FE_BASE_PORT + AGENT_ID * 10 ))
46
+ API_PORT=$(( HARNESS_API_BASE_PORT + AGENT_ID * 10 ))
47
+ DEBUG_PORT=$(( 9200 + AGENT_ID * 10 ))
48
+ DB_PORT="${AGENT_DB_PORT:-15432}"
49
+ MINIO_PORT="${AGENT_MINIO_PORT:-19000}"
50
+ BROWSER_PORT="${AGENT_BROWSER_PORT:-13001}"
51
+
52
+ log "Ports: frontend=$FE_PORT api=$API_PORT debug=$DEBUG_PORT"
53
+
54
+ # Replace any previous session for this slot — requires explicit confirmation.
55
+ if slot_is_occupied "$AGENT_ID"; then
56
+ require_slot_replace_confirm "$AGENT_ID" "$FORCE" "$REPLACE"
57
+ log "Replacing previous session for slot ${AGENT_ID}..."
58
+ "$SCRIPT_DIR/teardown.sh" "$AGENT_ID" >&2
59
+ fi
60
+
61
+ # Ensure shared infra is running
62
+ "$SCRIPT_DIR/setup-infra.sh"
63
+
64
+ # Clone agent database from template
65
+ if har_infra_enabled db && [ -n "${HARNESS_TEMPLATE_DB:-}" ]; then
66
+ AGENT_DB="agent_${AGENT_ID}"
67
+ PSQL="har_pg psql -d postgres"
68
+ if $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '$AGENT_DB'" | grep -q 1; then
69
+ log "Database '$AGENT_DB' already exists."
70
+ else
71
+ log "Creating database '$AGENT_DB' from template..."
72
+ $PSQL -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$HARNESS_TEMPLATE_DB' AND pid <> pg_backend_pid()" >/dev/null 2>&1 || true
73
+ har_pg createdb -T "$HARNESS_TEMPLATE_DB" "$AGENT_DB"
74
+ log "Database '$AGENT_DB' created."
75
+ fi
76
+ fi
77
+
78
+ # Create MinIO bucket
79
+ if har_infra_enabled minio; then
80
+ MINIO_BUCKET="agent-${AGENT_ID}"
81
+ HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \
82
+ -u "minioadmin:minioadmin" \
83
+ "http://localhost:${MINIO_PORT}/${MINIO_BUCKET}/" 2>/dev/null) || true
84
+ log "MinIO bucket '$MINIO_BUCKET' ready (HTTP $HTTP_STATUS)."
85
+ fi
86
+
87
+ # Session worktree (default — use --no-worktree to work in repo root).
88
+ # Name encodes what the session is based on: <base-branch>-<sha4>-har-agent-<id>-<rand4>.
89
+ WORK_DIR="$REPO_ROOT"
90
+ WORKTREE_DIR=""
91
+ BRANCH=""
92
+ SUFFIX=""
93
+ BASE_BRANCH="$(git -C "$REPO_ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "detached")"
94
+ BASE_COMMIT="$(git -C "$REPO_ROOT" rev-parse HEAD 2>/dev/null || true)"
95
+ if [ "$USE_WORKTREE" = true ]; then
96
+ SHORT_SHA="$(git -C "$REPO_ROOT" rev-parse --short=4 HEAD)"
97
+ SUFFIX="$(LC_ALL=C tr -dc 'a-z0-9' </dev/urandom 2>/dev/null | head -c 4 || true)"
98
+ [ -n "$SUFFIX" ] || SUFFIX="$(printf '%04d' $(( RANDOM % 10000 )))"
99
+ SESSION_NAME="${BASE_BRANCH//\//-}-${SHORT_SHA}-har-agent-${AGENT_ID}-${SUFFIX}"
100
+ BRANCH="$SESSION_NAME"
101
+ WORKTREE_DIR="$HOME/worktrees/${SESSION_NAME}"
102
+ log "Creating session worktree at $WORKTREE_DIR (branch $BRANCH)..."
103
+ git -C "$REPO_ROOT" worktree add "$WORKTREE_DIR" -b "$BRANCH"
104
+ # Worktrees are always rooted at the git repo — if this project lives in a
105
+ # subdirectory (monorepo), point WORK_DIR at the project inside the worktree.
106
+ REL_PREFIX="$(git -C "$REPO_ROOT" rev-parse --show-prefix 2>/dev/null || true)"
107
+ WORK_DIR="${WORKTREE_DIR%/}/${REL_PREFIX}"
108
+ WORK_DIR="${WORK_DIR%/}"
109
+ else
110
+ log "Using repo root (worktree disabled)"
111
+ fi
112
+
113
+ # Install dependencies (fresh worktrees have no node_modules — PM2 config needs them at load time)
114
+ if [ -f "$WORK_DIR/package.json" ] && [ ! -d "$WORK_DIR/node_modules" ]; then
115
+ log "Installing dependencies in $WORK_DIR..."
116
+ (cd "$WORK_DIR" && npm install --silent)
117
+ elif [ -f "$WORK_DIR/go.mod" ] && [ ! -d "$WORK_DIR/vendor" ]; then
118
+ log "Go module detected in work dir (run go mod download if needed)"
119
+ fi
120
+
121
+ # Monorepo: file:/workspace deps may resolve modules from the repo root — install there too
122
+ if [ -n "${REL_PREFIX:-}" ] && [ -f "$WORKTREE_DIR/package.json" ] && [ ! -d "$WORKTREE_DIR/node_modules" ]; then
123
+ log "Installing monorepo root dependencies in $WORKTREE_DIR..."
124
+ (cd "$WORKTREE_DIR" && npm install --silent)
125
+ fi
126
+
127
+ # Generate .env.agent.N
128
+ # Keep harness-generated files out of accidental `git add -A` commits in repos
129
+ # whose .gitignore doesn't know about har (applies to all worktrees too).
130
+ GIT_EXCLUDE="$(git -C "$REPO_ROOT" rev-parse --git-common-dir 2>/dev/null)/info/exclude"
131
+ if [ -n "$GIT_EXCLUDE" ] && [ -d "$(dirname "$GIT_EXCLUDE")" ]; then
132
+ for pattern in '.env.agent.*' 'ecosystem.agent.*.config.cjs'; do
133
+ grep -qxF "$pattern" "$GIT_EXCLUDE" 2>/dev/null || echo "$pattern" >> "$GIT_EXCLUDE"
134
+ done
135
+ fi
136
+
137
+ ENV_FILE="$WORK_DIR/.env.agent.${AGENT_ID}"
138
+ log "Generating $ENV_FILE..."
139
+ AGENT_ID="$AGENT_ID" \
140
+ API_PORT="$API_PORT" \
141
+ FE_PORT="$FE_PORT" \
142
+ DEBUG_PORT="$DEBUG_PORT" \
143
+ DB_PORT="$DB_PORT" \
144
+ MINIO_PORT="$MINIO_PORT" \
145
+ BROWSER_PORT="$BROWSER_PORT" \
146
+ REPO_ROOT="$WORK_DIR" \
147
+ envsubst '${AGENT_ID} ${API_PORT} ${FE_PORT} ${DEBUG_PORT} ${DB_PORT} ${MINIO_PORT} ${BROWSER_PORT} ${REPO_ROOT}' \
148
+ < "$SCRIPT_DIR/env.template" > "$ENV_FILE"
149
+
150
+ # Generate PM2 ecosystem config
151
+ ECOSYSTEM_FILE="$WORK_DIR/ecosystem.agent.${AGENT_ID}.config.cjs"
152
+ log "Generating $ECOSYSTEM_FILE..."
153
+ AGENT_ID="$AGENT_ID" \
154
+ FE_PORT="$FE_PORT" \
155
+ DEBUG_PORT="$DEBUG_PORT" \
156
+ envsubst '${AGENT_ID} ${FE_PORT} ${DEBUG_PORT}' \
157
+ < "$SCRIPT_DIR/ecosystem.agent.template.cjs" > "$ECOSYSTEM_FILE"
158
+
159
+ # Stop existing processes for this agent
160
+ npx --yes pm2 delete "/^agent-${AGENT_ID}-/" 2>/dev/null || true
161
+
162
+ # Start PM2 processes
163
+ log "Starting PM2 processes..."
164
+ cd "$WORK_DIR"
165
+ npx pm2 start "$ECOSYSTEM_FILE"
166
+ npx pm2 save --force >/dev/null 2>&1 || true
167
+
168
+ # Health check
169
+ if [ -n "${HARNESS_HEALTH_CHECK_PATH:-}" ]; then
170
+ HEALTH_URL="http://localhost:${API_PORT}${HARNESS_HEALTH_CHECK_PATH}"
171
+ log "Waiting for health check at $HEALTH_URL..."
172
+ TIMEOUT=60
173
+ ELAPSED=0
174
+ while [ "$ELAPSED" -lt "$TIMEOUT" ]; do
175
+ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL" 2>/dev/null) || true
176
+ if [ "$HTTP_CODE" = "200" ]; then
177
+ log "Health check passed!"
178
+ break
179
+ fi
180
+ sleep 2
181
+ ELAPSED=$((ELAPSED + 2))
182
+ done
183
+ if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
184
+ log "Warning: Health check did not pass within ${TIMEOUT}s."
185
+ log "Check logs: ./.har/agent-cli.sh $AGENT_ID logs"
186
+ fi
187
+ fi
188
+
189
+ # Record the session in the slot registry — the source of truth for where
190
+ # this slot's code lives (status/verify/teardown resolve through it).
191
+ SLOT_AGENT_ID="$AGENT_ID" \
192
+ SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
193
+ SLOT_WORK_DIR="$WORK_DIR" \
194
+ SLOT_SUFFIX="${SUFFIX:-}" \
195
+ SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
196
+ SLOT_BRANCH="${BRANCH:-}" \
197
+ SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
198
+ SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
199
+ SLOT_PURPOSE="${PURPOSE}" \
200
+ SLOT_PORTS_JSON="{\"frontend\":${FE_PORT},\"api\":${API_PORT},\"debug\":${DEBUG_PORT}}" \
201
+ SLOT_PREVIEW_URLS_JSON="{\"frontend\":\"http://localhost:${FE_PORT}\",\"api\":\"http://localhost:${API_PORT}\"}" \
202
+ write_slot_registry
203
+
204
+ # Launch Claude Code in tmux (optional)
205
+ if [ "$USE_CLAUDE" = true ]; then
206
+ TMUX_SESSION="agent-${AGENT_ID}"
207
+ if tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then
208
+ tmux kill-session -t "$TMUX_SESSION"
209
+ fi
210
+ tmux new-session -d -s "$TMUX_SESSION" -c "$WORK_DIR" "claude"
211
+ log "Claude session: tmux attach -t $TMUX_SESSION"
212
+ fi
213
+
214
+ echo ""
215
+ log "Agent $AGENT_ID is ready!"
216
+ log ""
217
+ log " WORK DIR (make ALL file edits under this path — never the main checkout):"
218
+ log " ${WORK_DIR}"
219
+ if [ "$USE_WORKTREE" = true ]; then
220
+ log " Branch: ${BRANCH} (based on ${BASE_BRANCH} @ ${BASE_COMMIT})"
221
+ fi
222
+ log " Frontend: http://localhost:${FE_PORT}"
223
+ log " API: http://localhost:${API_PORT}"
224
+ har_infra_enabled db && log " Database: agent_${AGENT_ID} @ localhost:${DB_PORT}"
225
+ log ""
226
+ log " Edits under the work dir hot-reload in the running slot;"
227
+ log " use ./.har/agent-cli.sh $AGENT_ID restart if a change doesn't take."
228
+ log " Verify: ./.har/verify.sh $AGENT_ID"
229
+ log " Teardown: ./.har/teardown.sh $AGENT_ID (keeps the branch)"
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env bash
2
+ # Sets up shared infrastructure for all agents.
3
+ # Starts the docker compose services listed in HARNESS_INFRA_SERVICES, creates
4
+ # the template database (when "db" is enabled), and starts optional shared app
5
+ # services (ecosystem.shared.config.cjs). One instance serves every agent slot.
6
+ # Idempotent — safe to run multiple times.
7
+ #
8
+ # Usage: ./.har/setup-infra.sh
9
+ set -euo pipefail
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
13
+ COMPOSE_FILE="$SCRIPT_DIR/docker-compose.agent.yml"
14
+
15
+ # shellcheck source=/dev/null
16
+ source "$SCRIPT_DIR/harness.env"
17
+
18
+ COMPOSE_PROJECT="har-${HARNESS_PROJECT_NAME}"
19
+ DB_PORT="${AGENT_DB_PORT:-15432}"
20
+ PSQL="har_pg psql -d postgres"
21
+
22
+ log() { echo "==> $*" >&2; }
23
+
24
+ SERVICES="${HARNESS_INFRA_SERVICES:-}"
25
+
26
+ if [ -n "$SERVICES" ]; then
27
+ log "Starting shared infrastructure (project: $COMPOSE_PROJECT): $SERVICES"
28
+ 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}" \
34
+ docker compose -p "$COMPOSE_PROJECT" -f "$COMPOSE_FILE" up -d $SERVICES
35
+ else
36
+ log "No shared infra services enabled in harness.env (HARNESS_INFRA_SERVICES)"
37
+ fi
38
+
39
+ # Wait for PostgreSQL and prepare the template database
40
+ if har_infra_enabled db; then
41
+ log "Waiting for PostgreSQL on port $DB_PORT..."
42
+ for i in $(seq 1 30); do
43
+ if har_pg pg_isready -q 2>/dev/null; then
44
+ log "PostgreSQL is ready."
45
+ break
46
+ fi
47
+ if [ "$i" = "30" ]; then
48
+ echo "Error: PostgreSQL did not become ready within 30 seconds." >&2
49
+ exit 1
50
+ fi
51
+ sleep 1
52
+ done
53
+
54
+ log "Enabling pg_stat_statements extension..."
55
+ $PSQL -c "CREATE EXTENSION IF NOT EXISTS pg_stat_statements" 2>/dev/null || true
56
+
57
+ if [ -n "${HARNESS_TEMPLATE_DB:-}" ]; then
58
+ if $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '$HARNESS_TEMPLATE_DB'" | grep -q 1; then
59
+ log "Template database '$HARNESS_TEMPLATE_DB' already exists. Skipping creation."
60
+ else
61
+ log "Creating template database '$HARNESS_TEMPLATE_DB'..."
62
+ $PSQL -c "CREATE DATABASE $HARNESS_TEMPLATE_DB"
63
+
64
+ if [ -n "${HARNESS_DB_MIGRATE_CMD:-}" ] && [ "$HARNESS_DB_MIGRATE_CMD" != "echo 'TODO: set migrate command'" ]; then
65
+ log "Running migrations..."
66
+ PGPASSWORD=password PGHOST=localhost PGPORT="$DB_PORT" PGUSER=postgres \
67
+ PGDATABASE="$HARNESS_TEMPLATE_DB" \
68
+ bash -c "cd '$REPO_ROOT' && $HARNESS_DB_MIGRATE_CMD"
69
+ fi
70
+
71
+ if [ -n "${HARNESS_DB_SEED_CMD:-}" ] && [ "$HARNESS_DB_SEED_CMD" != "echo 'TODO: set seed command'" ]; then
72
+ log "Running seeds..."
73
+ PGPASSWORD=password PGHOST=localhost PGPORT="$DB_PORT" PGUSER=postgres \
74
+ PGDATABASE="$HARNESS_TEMPLATE_DB" \
75
+ bash -c "cd '$REPO_ROOT' && $HARNESS_DB_SEED_CMD"
76
+ fi
77
+
78
+ log "Marking '$HARNESS_TEMPLATE_DB' as a PostgreSQL template..."
79
+ $PSQL -c "UPDATE pg_database SET datistemplate = true WHERE datname = '$HARNESS_TEMPLATE_DB'"
80
+ log "Template database ready: $HARNESS_TEMPLATE_DB"
81
+ fi
82
+ fi
83
+ fi
84
+
85
+ # Shared app services — supporting services of a monolith/monorepo that agents
86
+ # depend on but do not modify. Started ONCE on fixed ports, shared by all slots.
87
+ # Create .har/ecosystem.shared.config.cjs (PM2 format, processes named
88
+ # "har-shared-<name>") only when the primary app needs sibling services running.
89
+ SHARED_ECOSYSTEM="$SCRIPT_DIR/ecosystem.shared.config.cjs"
90
+ if [ -f "$SHARED_ECOSYSTEM" ]; then
91
+ log "Starting shared app services from ecosystem.shared.config.cjs..."
92
+ (cd "$REPO_ROOT" && npx --yes pm2 startOrReload "$SHARED_ECOSYSTEM" >/dev/null)
93
+ log "Shared app services running (pm2 ls | grep har-shared-)."
94
+ fi
95
+
96
+ echo ""
97
+ log "Infrastructure is ready."
98
+ 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}"
102
+ exit 0
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ # Optional per-stage scripts live here.
3
+ # Example: ./.har/stages/browser-e2e.sh <agent-id>
4
+ #
5
+ # Register custom stages in .har/stages.json with a project-owned command:
6
+ # "command": "./.har/stages/browser-e2e.sh {agentId}"
7
+
8
+ echo "Add stage scripts under .har/stages/ and register them in stages.json." >&2
9
+ exit 1
@@ -0,0 +1,67 @@
1
+ {
2
+ "version": "1",
3
+ "agentSlots": {
4
+ "min": 1,
5
+ "max": 5
6
+ },
7
+ "stages": [
8
+ {
9
+ "id": "setup-infra",
10
+ "kind": "setup",
11
+ "description": "Shared infrastructure (Docker, template DB)",
12
+ "command": "./.har/setup-infra.sh"
13
+ },
14
+ {
15
+ "id": "launch",
16
+ "kind": "launch",
17
+ "description": "Launch an isolated agent environment slot",
18
+ "command": "./.har/launch.sh {agentId}",
19
+ "artifacts": [
20
+ {
21
+ "path": ".env.agent.{agentId}",
22
+ "kind": "file",
23
+ "description": "Generated per-agent environment file"
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ "id": "verify",
29
+ "kind": "verify",
30
+ "description": "Verification pipeline (quick by default; --full adds lint and browser-e2e when installed)",
31
+ "command": "./.har/verify.sh {agentId}",
32
+ "acceptsArgs": ["--full"],
33
+ "artifacts": [
34
+ {
35
+ "path": ".har/artifacts",
36
+ "kind": "directory",
37
+ "description": "Optional verification outputs such as reports, traces, screenshots, and logs"
38
+ }
39
+ ]
40
+ },
41
+ {
42
+ "id": "status",
43
+ "kind": "inspect",
44
+ "description": "Inspect a running agent environment slot",
45
+ "command": "./.har/agent-cli.sh {agentId} status",
46
+ "artifacts": [
47
+ {
48
+ "path": ".har/logs",
49
+ "kind": "directory",
50
+ "description": "Optional harness logs"
51
+ }
52
+ ]
53
+ },
54
+ {
55
+ "id": "logs",
56
+ "kind": "inspect",
57
+ "description": "Read recent logs for an agent slot",
58
+ "command": "./.har/agent-cli.sh {agentId} logs"
59
+ },
60
+ {
61
+ "id": "teardown",
62
+ "kind": "teardown",
63
+ "description": "Stop processes and release agent slot resources",
64
+ "command": "./.har/teardown.sh {agentId}"
65
+ }
66
+ ]
67
+ }
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env bash
2
+ # Safely tears down one agent: stops PM2, drops DB, removes bucket, cleans up files.
3
+ # The session's git branch is KEPT by default so you can push it / open a PR —
4
+ # pass --delete-branch to remove it too.
5
+ #
6
+ # Usage: ./.har/teardown.sh <agent-id> [--delete-branch]
7
+ set -euo pipefail
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
11
+
12
+ # shellcheck source=/dev/null
13
+ source "$SCRIPT_DIR/harness.env"
14
+ # shellcheck source=/dev/null
15
+ source "$SCRIPT_DIR/agent-slot.sh"
16
+
17
+ AGENT_ID="${1:?Usage: teardown.sh <agent-id> [--delete-branch]}"
18
+ DELETE_BRANCH=false
19
+
20
+ for arg in "${@:2}"; do
21
+ case "$arg" in
22
+ --delete-branch) DELETE_BRANCH=true ;;
23
+ esac
24
+ done
25
+
26
+ validate_agent_id "$AGENT_ID"
27
+
28
+ echo "==> Tearing down agent ${AGENT_ID}..."
29
+
30
+ # Resolve the session from the registry; fall back to the legacy fixed path so
31
+ # pre-registry sessions stay removable.
32
+ REGISTRY_FILE="$(slot_registry_file "$AGENT_ID")"
33
+ WORKTREE_PATH=""
34
+ WORK_DIR=""
35
+ BRANCH=""
36
+ if [ -f "$REGISTRY_FILE" ]; then
37
+ WORKTREE_PATH="$(read_slot_field "$REGISTRY_FILE" worktreePath || true)"
38
+ WORK_DIR="$(read_slot_field "$REGISTRY_FILE" workDir || true)"
39
+ BRANCH="$(read_slot_field "$REGISTRY_FILE" branch || true)"
40
+ fi
41
+ [ -n "$WORKTREE_PATH" ] || WORKTREE_PATH="$HOME/worktrees/${HARNESS_PROJECT_NAME}-agent-${AGENT_ID}"
42
+
43
+ npx --yes pm2 delete "/^agent-${AGENT_ID}-/" 2>/dev/null || true
44
+ echo "✓ Stopped PM2 processes"
45
+
46
+ if har_infra_enabled db; then
47
+ har_pg psql -d postgres -c \
48
+ "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='agent_${AGENT_ID}';" \
49
+ >/dev/null 2>&1 || true
50
+ har_pg dropdb --if-exists "agent_${AGENT_ID}" 2>/dev/null || true
51
+ echo "✓ Dropped database: agent_${AGENT_ID}"
52
+ fi
53
+
54
+ if har_infra_enabled minio; then
55
+ curl -sf -X DELETE "http://minioadmin:minioadmin@localhost:19000/agent-${AGENT_ID}?force=true" \
56
+ >/dev/null 2>&1 || true
57
+ echo "✓ Removed MinIO bucket: agent-${AGENT_ID}"
58
+ fi
59
+
60
+ rm -f "$REPO_ROOT/.env.agent.${AGENT_ID}"
61
+ rm -f "$REPO_ROOT/ecosystem.agent.${AGENT_ID}.config.cjs"
62
+ if [ -n "$WORK_DIR" ] && [ -d "$WORK_DIR" ]; then
63
+ rm -f "$WORK_DIR/.env.agent.${AGENT_ID}"
64
+ rm -f "$WORK_DIR/ecosystem.agent.${AGENT_ID}.config.cjs"
65
+ fi
66
+
67
+ if [ -d "$WORKTREE_PATH" ]; then
68
+ rm -f "$WORKTREE_PATH/.env.agent.${AGENT_ID}"
69
+ rm -f "$WORKTREE_PATH/ecosystem.agent.${AGENT_ID}.config.cjs"
70
+ git -C "$REPO_ROOT" worktree remove "$WORKTREE_PATH" --force 2>/dev/null || rm -rf "$WORKTREE_PATH"
71
+ echo "✓ Removed worktree: $WORKTREE_PATH"
72
+ fi
73
+ git -C "$REPO_ROOT" worktree prune 2>/dev/null || true
74
+
75
+ if [ -n "$BRANCH" ]; then
76
+ if [ "$DELETE_BRANCH" = true ]; then
77
+ git -C "$REPO_ROOT" branch -D "$BRANCH" 2>/dev/null || true
78
+ echo "✓ Deleted branch: $BRANCH"
79
+ else
80
+ echo "✓ Kept branch: $BRANCH (push it or delete with: git branch -D $BRANCH)"
81
+ fi
82
+ fi
83
+
84
+ remove_slot_registry "$AGENT_ID"
85
+
86
+ echo "✓ Agent ${AGENT_ID} torn down"