@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.
- package/LICENSE +674 -0
- package/README.md +208 -0
- package/control/docker-compose.build.yml +7 -0
- package/control/docker-compose.yml +31 -0
- package/dist/index.js +41753 -0
- package/dist/prompts/system-authoring.md +104 -0
- package/dist/templates/AGENT.md.template +40 -0
- package/dist/templates/CLAUDE.md.template +5 -0
- package/dist/templates/adaptation-prompt-init.md +124 -0
- package/dist/templates/adaptation-prompt-maintain.md +73 -0
- package/dist/templates/cursor-rule.mdc.template +101 -0
- package/dist/templates/har-boilerplate/CLAUDE.agent.md +54 -0
- package/dist/templates/har-boilerplate/README.md +134 -0
- package/dist/templates/har-boilerplate/agent-cli.sh +172 -0
- package/dist/templates/har-boilerplate/agent-slot.sh +270 -0
- package/dist/templates/har-boilerplate/attach.sh +16 -0
- package/dist/templates/har-boilerplate/docker-compose.agent.yml +66 -0
- package/dist/templates/har-boilerplate/ecosystem.agent.template.cjs +44 -0
- package/dist/templates/har-boilerplate/env.template +37 -0
- package/dist/templates/har-boilerplate/harness.env +50 -0
- package/dist/templates/har-boilerplate/justfile +39 -0
- package/dist/templates/har-boilerplate/launch.sh +229 -0
- package/dist/templates/har-boilerplate/setup-infra.sh +102 -0
- package/dist/templates/har-boilerplate/stages/README.sh +9 -0
- package/dist/templates/har-boilerplate/stages.json +67 -0
- package/dist/templates/har-boilerplate/teardown.sh +86 -0
- package/dist/templates/har-boilerplate/verify.sh +163 -0
- package/dist/templates/har-boilerplate-cli/CLAUDE.agent.md +41 -0
- package/dist/templates/har-boilerplate-cli/README.md +112 -0
- package/dist/templates/har-boilerplate-cli/agent-cli.sh +162 -0
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +270 -0
- package/dist/templates/har-boilerplate-cli/docker-compose.agent.yml +64 -0
- package/dist/templates/har-boilerplate-cli/harness.env +40 -0
- package/dist/templates/har-boilerplate-cli/justfile +27 -0
- package/dist/templates/har-boilerplate-cli/launch.sh +132 -0
- package/dist/templates/har-boilerplate-cli/setup-infra.sh +102 -0
- package/dist/templates/har-boilerplate-cli/stages/README.sh +9 -0
- package/dist/templates/har-boilerplate-cli/stages.json +61 -0
- package/dist/templates/har-boilerplate-cli/teardown.sh +66 -0
- package/dist/templates/har-boilerplate-cli/verify.sh +113 -0
- package/dist/templates/har-boilerplate-ios/ADAPT-PROMPT.md +57 -0
- package/dist/templates/har-boilerplate-ios/CLAUDE.agent.md +65 -0
- package/dist/templates/har-boilerplate-ios/README.md +88 -0
- package/dist/templates/har-boilerplate-ios/agent-cli.sh +125 -0
- package/dist/templates/har-boilerplate-ios/agent-slot.sh +270 -0
- package/dist/templates/har-boilerplate-ios/docker-compose.agent.yml +17 -0
- package/dist/templates/har-boilerplate-ios/harness.env +42 -0
- package/dist/templates/har-boilerplate-ios/justfile +33 -0
- package/dist/templates/har-boilerplate-ios/launch.sh +132 -0
- package/dist/templates/har-boilerplate-ios/setup-infra.sh +84 -0
- package/dist/templates/har-boilerplate-ios/stages/README.sh +12 -0
- package/dist/templates/har-boilerplate-ios/stages.json +61 -0
- package/dist/templates/har-boilerplate-ios/teardown.sh +64 -0
- package/dist/templates/har-boilerplate-ios/verify.sh +165 -0
- package/dist/templates/stage-templates/playwright/.github/workflows/playwright.yml +30 -0
- package/dist/templates/stage-templates/playwright/.har/stages/PLAYWRIGHT.md +21 -0
- package/dist/templates/stage-templates/playwright/.har/stages/browser-e2e.sh +83 -0
- package/dist/templates/stage-templates/playwright/README.md +14 -0
- package/dist/templates/stage-templates/playwright/package.fragment.json +14 -0
- package/dist/templates/stage-templates/playwright/playwright.config.js +30 -0
- package/dist/templates/stage-templates/playwright/template.manifest.json +44 -0
- package/dist/templates/stage-templates/playwright/tests/a11y/smoke.spec.js +14 -0
- package/dist/templates/stage-templates/playwright/tests/api/health.spec.js +10 -0
- package/dist/templates/stage-templates/playwright/tests/frontend/smoke.spec.js +9 -0
- package/dist/templates/stage-templates/rocketsim/.har/stages/ROCKETSIM.md +126 -0
- package/dist/templates/stage-templates/rocketsim/.har/stages/rocketsim-flows.sh +188 -0
- package/dist/templates/stage-templates/rocketsim/README.md +66 -0
- package/dist/templates/stage-templates/rocketsim/flows/README.md +36 -0
- package/dist/templates/stage-templates/rocketsim/flows/example-smoke.sh +60 -0
- package/dist/templates/stage-templates/rocketsim/template.manifest.json +38 -0
- package/package.json +78 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Progressive verification pipeline for an agent environment.
|
|
3
|
+
# Outputs JSON to stdout, human-readable progress to stderr.
|
|
4
|
+
#
|
|
5
|
+
# Usage: ./.har/verify.sh <agent-id> [--full]
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
10
|
+
|
|
11
|
+
# shellcheck source=/dev/null
|
|
12
|
+
source "$SCRIPT_DIR/harness.env"
|
|
13
|
+
# shellcheck source=/dev/null
|
|
14
|
+
source "$SCRIPT_DIR/agent-slot.sh"
|
|
15
|
+
|
|
16
|
+
AGENT_ID="${1:?Usage: verify.sh <agent-id> [--full]}"
|
|
17
|
+
FULL=""
|
|
18
|
+
|
|
19
|
+
for arg in "${@:2}"; do
|
|
20
|
+
[ "$arg" = "--full" ] && FULL=1
|
|
21
|
+
done
|
|
22
|
+
|
|
23
|
+
validate_agent_id "$AGENT_ID"
|
|
24
|
+
|
|
25
|
+
API_PORT=$(( HARNESS_API_BASE_PORT + AGENT_ID * 10 ))
|
|
26
|
+
|
|
27
|
+
ENV_FILE="$(resolve_agent_env_file "$AGENT_ID" "$REPO_ROOT")" || {
|
|
28
|
+
echo "No .env.agent.${AGENT_ID} found. Run: ./.har/launch.sh ${AGENT_ID}" >&2
|
|
29
|
+
exit 1
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
set -a
|
|
33
|
+
# shellcheck source=/dev/null
|
|
34
|
+
source "$ENV_FILE"
|
|
35
|
+
set +a
|
|
36
|
+
|
|
37
|
+
WORK_DIR="$(resolve_agent_work_dir "$ENV_FILE")"
|
|
38
|
+
API_PORT="${API_PORT:-$(( HARNESS_API_BASE_PORT + AGENT_ID * 10 ))}"
|
|
39
|
+
|
|
40
|
+
echo "==> Verifying agent ${AGENT_ID} (work dir: ${WORK_DIR})..." >&2
|
|
41
|
+
|
|
42
|
+
OVERALL_PASS=true
|
|
43
|
+
START_TOTAL=$(date +%s%3N 2>/dev/null || echo "0")
|
|
44
|
+
RESULTS_JSON="[]"
|
|
45
|
+
|
|
46
|
+
run_step() {
|
|
47
|
+
local name="$1"
|
|
48
|
+
local cmd="$2"
|
|
49
|
+
local start end elapsed exit_code output
|
|
50
|
+
|
|
51
|
+
printf " → %-40s" "$name..." >&2
|
|
52
|
+
start=$(date +%s%3N 2>/dev/null || echo "0")
|
|
53
|
+
|
|
54
|
+
set +e
|
|
55
|
+
output=$(cd "$WORK_DIR" && set -a && . "$ENV_FILE" && set +a && eval "$cmd" 2>&1)
|
|
56
|
+
exit_code=$?
|
|
57
|
+
set -e
|
|
58
|
+
|
|
59
|
+
end=$(date +%s%3N 2>/dev/null || echo "0")
|
|
60
|
+
elapsed=$(( end - start ))
|
|
61
|
+
|
|
62
|
+
local pass_bool step_output_escaped
|
|
63
|
+
if [ "$exit_code" = "0" ]; then
|
|
64
|
+
echo "✓ (${elapsed}ms)" >&2
|
|
65
|
+
pass_bool="true"
|
|
66
|
+
else
|
|
67
|
+
echo "✗ (${elapsed}ms)" >&2
|
|
68
|
+
echo "$output" | head -30 | sed 's/^/ /' >&2
|
|
69
|
+
pass_bool="false"
|
|
70
|
+
OVERALL_PASS=false
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
step_output_escaped=$(echo "$output" | head -50 | \
|
|
74
|
+
node -e "let d=''; process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>process.stdout.write(JSON.stringify(d.trim())))" \
|
|
75
|
+
2>/dev/null || echo '""')
|
|
76
|
+
|
|
77
|
+
RESULTS_JSON=$(echo "$RESULTS_JSON" | node -e "
|
|
78
|
+
const fs = require('fs');
|
|
79
|
+
let arr = JSON.parse(fs.readFileSync('/dev/stdin','utf8'));
|
|
80
|
+
arr.push({name:'$name',pass:$pass_bool,ms:$elapsed,output:$step_output_escaped});
|
|
81
|
+
process.stdout.write(JSON.stringify(arr));
|
|
82
|
+
" 2>/dev/null || echo "$RESULTS_JSON")
|
|
83
|
+
|
|
84
|
+
if [ "$pass_bool" = "false" ] && [ -z "$FULL" ]; then
|
|
85
|
+
return 1
|
|
86
|
+
fi
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
run_http_step() {
|
|
90
|
+
local name="$1"
|
|
91
|
+
local url="$2"
|
|
92
|
+
local start end elapsed exit_code output
|
|
93
|
+
|
|
94
|
+
printf " → %-40s" "$name..." >&2
|
|
95
|
+
start=$(date +%s%3N 2>/dev/null || echo "0")
|
|
96
|
+
|
|
97
|
+
set +e
|
|
98
|
+
output=$(curl -sf "$url" 2>&1)
|
|
99
|
+
exit_code=$?
|
|
100
|
+
set -e
|
|
101
|
+
|
|
102
|
+
end=$(date +%s%3N 2>/dev/null || echo "0")
|
|
103
|
+
elapsed=$(( end - start ))
|
|
104
|
+
|
|
105
|
+
local pass_bool step_output_escaped
|
|
106
|
+
if [ "$exit_code" = "0" ]; then
|
|
107
|
+
echo "✓ (${elapsed}ms)" >&2
|
|
108
|
+
pass_bool="true"
|
|
109
|
+
else
|
|
110
|
+
echo "✗ (${elapsed}ms)" >&2
|
|
111
|
+
pass_bool="false"
|
|
112
|
+
OVERALL_PASS=false
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
step_output_escaped=$(echo "$output" | head -50 | \
|
|
116
|
+
node -e "let d=''; process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>process.stdout.write(JSON.stringify(d.trim())))" \
|
|
117
|
+
2>/dev/null || echo '""')
|
|
118
|
+
|
|
119
|
+
RESULTS_JSON=$(echo "$RESULTS_JSON" | node -e "
|
|
120
|
+
const fs = require('fs');
|
|
121
|
+
let arr = JSON.parse(fs.readFileSync('/dev/stdin','utf8'));
|
|
122
|
+
arr.push({name:'$name',pass:$pass_bool,ms:$elapsed,output:$step_output_escaped});
|
|
123
|
+
process.stdout.write(JSON.stringify(arr));
|
|
124
|
+
" 2>/dev/null || echo "$RESULTS_JSON")
|
|
125
|
+
|
|
126
|
+
if [ "$pass_bool" = "false" ] && [ -z "$FULL" ]; then
|
|
127
|
+
return 1
|
|
128
|
+
fi
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# ── Verification stages ─────────────────────────────────────────────────────
|
|
132
|
+
# TODO: Customize these steps for your project.
|
|
133
|
+
# Edit this section directly — do not use a separate config file.
|
|
134
|
+
|
|
135
|
+
run_step "typecheck" "echo 'TODO: npm run typecheck'" || { [ -z "$FULL" ] && true; }
|
|
136
|
+
run_step "unit-tests" "echo 'TODO: npm test'" || { [ -z "$FULL" ] && true; }
|
|
137
|
+
run_http_step "api-health" "http://localhost:${API_PORT}${HARNESS_HEALTH_CHECK_PATH}" || { [ -z "$FULL" ] && true; }
|
|
138
|
+
|
|
139
|
+
if [ -n "$FULL" ]; then
|
|
140
|
+
run_step "lint" "echo 'TODO: npm run lint'" || true
|
|
141
|
+
run_step "browser-e2e" "run_browser_e2e_if_present \"$SCRIPT_DIR\" \"$AGENT_ID\"" || true
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# ── Output results ────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
END_TOTAL=$(date +%s%3N 2>/dev/null || echo "0")
|
|
147
|
+
TOTAL_MS=$(( END_TOTAL - START_TOTAL ))
|
|
148
|
+
|
|
149
|
+
node -e "
|
|
150
|
+
const results = $RESULTS_JSON;
|
|
151
|
+
const overall = results.length > 0 && results.every(r => r.pass);
|
|
152
|
+
const out = {
|
|
153
|
+
status: overall ? 'pass' : 'fail',
|
|
154
|
+
agent_id: $AGENT_ID,
|
|
155
|
+
total_ms: $TOTAL_MS,
|
|
156
|
+
stages: results,
|
|
157
|
+
};
|
|
158
|
+
process.stdout.write(JSON.stringify(out, null, 2) + '\n');
|
|
159
|
+
" 2>/dev/null || echo "{\"status\":\"fail\",\"agent_id\":${AGENT_ID},\"stages\":[]}"
|
|
160
|
+
|
|
161
|
+
if [ "$OVERALL_PASS" = "false" ]; then
|
|
162
|
+
exit 1
|
|
163
|
+
fi
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Agent ${AGENT_ID} — Development Environment
|
|
2
|
+
|
|
3
|
+
> [`AGENT.md`](../AGENT.md) · [`.har/README.md`](./README.md) · [`stages.json`](./stages.json)
|
|
4
|
+
|
|
5
|
+
## Environment
|
|
6
|
+
|
|
7
|
+
| | |
|
|
8
|
+
|--|--|
|
|
9
|
+
| **Agent ID** | ${AGENT_ID} |
|
|
10
|
+
| **Work dir** | Fresh session worktree per launch — see the launch output or `.har/slots/agent-${AGENT_ID}.json` |
|
|
11
|
+
|
|
12
|
+
**Never edit the main checkout** — launch FIRST, then make ALL file edits under the work dir from the launch output. Relaunching replaces the session (branch kept) and requires explicit confirmation (`--replace` / `confirmReplace`); dirty worktrees also need `--force` after user approval — never autonomously.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
./.har/agent-cli.sh ${AGENT_ID} status
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Definition of done
|
|
19
|
+
|
|
20
|
+
- [ ] Full verification returns `"status": "pass"` (`har env verify ${AGENT_ID} --full`, MCP `har_run_verification` with `full: true`, or `./.har/verify.sh ${AGENT_ID} --full`)
|
|
21
|
+
- [ ] When `stages/browser-e2e.sh` exists, full verify includes Playwright — adapt specs under `tests/` for UI changes
|
|
22
|
+
- [ ] New behavior has automated test coverage
|
|
23
|
+
- [ ] Changes committed **in the session worktree** with a clear message
|
|
24
|
+
- [ ] Finish with `har env complete ${AGENT_ID}` (or MCP `har_complete_environment`) — records the validation and tears down while **keeping the session branch** for the user to push / open a PR
|
|
25
|
+
|
|
26
|
+
Quick loop: MCP `har_run_verification`, `har env verify ${AGENT_ID}`, or `./.har/verify.sh ${AGENT_ID}`
|
|
27
|
+
|
|
28
|
+
## Project commands
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# TODO: adapt for this repository
|
|
32
|
+
# npm run typecheck
|
|
33
|
+
# npm test
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Do not
|
|
37
|
+
|
|
38
|
+
- Work around a failing harness command with ad-hoc setup — fix the harness or report the failure
|
|
39
|
+
- Edit `.env.agent.${AGENT_ID}` by hand
|
|
40
|
+
- Run `verify` before `launch` when e2e needs a running server
|
|
41
|
+
- Edit the main checkout — all edits go under the session work dir
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# .har — Agent Harness (CLI / library profile)
|
|
2
|
+
|
|
3
|
+
This directory is the **agent harness** for this repository. It lets AI coding agents run the project in isolated git worktrees with optional Docker-backed shared infra.
|
|
4
|
+
|
|
5
|
+
Generated and maintained by [`har`](https://github.com/antoineFrau/har). Run `har env maintain` when the repo stack changes.
|
|
6
|
+
|
|
7
|
+
**The harness is how you run this project.** Launch a slot to exercise the code in isolation; don't hand-roll setup. If a harness command fails, fix the harness or report it — don't silently fall back to ad-hoc commands.
|
|
8
|
+
|
|
9
|
+
## What's in here
|
|
10
|
+
|
|
11
|
+
| File | Purpose |
|
|
12
|
+
|------|---------|
|
|
13
|
+
| `README.md` | This file — index of the harness |
|
|
14
|
+
| `manifest.json` | Generator metadata (version, profile, checksums) — do not edit |
|
|
15
|
+
| `harness.env` | Shared config: worktree default, `HARNESS_INFRA_SERVICES`, migrate/seed commands |
|
|
16
|
+
| `stages.json` | Machine-readable registry of runnable harness stages |
|
|
17
|
+
| `stages/` | Optional custom stage scripts registered from `stages.json` |
|
|
18
|
+
| `runs/` | Run history from `har env` / MCP only — `.har/runs/YYYY-MM-DD/HH-mm-ss_<stageId>_agent-<id>.json` (gitignore) |
|
|
19
|
+
| `artifacts/` | Stage outputs: reports, traces, screenshots, logs |
|
|
20
|
+
| `agent-slot.sh` | Shared agent-id validation (reads limits from `harness.env`) |
|
|
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) |
|
|
23
|
+
| `verify.sh` | Verification pipeline (typecheck, tests, lint, build) |
|
|
24
|
+
| `teardown.sh` | Tear down one agent slot (worktree + env file) |
|
|
25
|
+
| `agent-cli.sh` | Inspect slot status, run commands in the work dir |
|
|
26
|
+
| `docker-compose.agent.yml` | Shared infrastructure containers (services listed in `HARNESS_INFRA_SERVICES`) |
|
|
27
|
+
| `CLAUDE.agent.md` | Detailed instructions for coding agents |
|
|
28
|
+
| `justfile` | Optional shortcuts (requires `just`) |
|
|
29
|
+
|
|
30
|
+
No PM2 or `ecosystem.agent.template.cjs` in this profile — agents run project commands directly in their worktree.
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
**Preferred — har CLI or MCP** (persists run history under `.har/runs/`):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
har env launch 1
|
|
38
|
+
har env verify 1
|
|
39
|
+
har env verify 1 --full
|
|
40
|
+
har env teardown 1
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
In Cursor with HAR MCP configured: use `har_launch_environment`, `har_run_verification`, and `har_teardown_environment`.
|
|
44
|
+
|
|
45
|
+
**Shell fallback** (no CLI/MCP installed):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
./.har/setup-infra.sh # when HARNESS_INFRA_SERVICES is non-empty
|
|
49
|
+
./.har/launch.sh 1
|
|
50
|
+
./.har/verify.sh 1
|
|
51
|
+
./.har/verify.sh 1 --full # + lint, build, browser-e2e (if Playwright installed)
|
|
52
|
+
./.har/teardown.sh 1
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Read **`stages.json`** and **`verificationStages`**. Optional: `har env add-stage playwright`.
|
|
56
|
+
|
|
57
|
+
## Verification contract
|
|
58
|
+
|
|
59
|
+
| Mode | Command | Typical steps |
|
|
60
|
+
|------|---------|---------------|
|
|
61
|
+
| Quick | `har env verify <id>` or `verify.sh <id>` | typecheck, unit tests |
|
|
62
|
+
| Full | `har env verify <id> --full` or `verify.sh <id> --full` | + lint, build, **browser-e2e** when `stages/browser-e2e.sh` exists |
|
|
63
|
+
|
|
64
|
+
Use `har env launch 1 --no-worktree` or `./.har/launch.sh 1 --no-worktree` only when working in the repo root.
|
|
65
|
+
|
|
66
|
+
## Run history
|
|
67
|
+
|
|
68
|
+
| Entry point | Writes `.har/runs/`? |
|
|
69
|
+
|-------------|------------------------|
|
|
70
|
+
| `./.har/*.sh` | No |
|
|
71
|
+
| `har env …` / MCP | Yes — main checkout `.har/runs/YYYY-MM-DD/` |
|
|
72
|
+
|
|
73
|
+
With worktree slots, tests run in the worktree; run JSON lives in the main repo. See `workDir` in each record.
|
|
74
|
+
|
|
75
|
+
## For coding agents
|
|
76
|
+
|
|
77
|
+
**Start here:** read [`AGENT.md`](../AGENT.md) at the repo root for a short pointer, then [`.har/CLAUDE.agent.md`](./CLAUDE.agent.md) for full instructions.
|
|
78
|
+
|
|
79
|
+
Prefer HAR MCP tools or `har env …` for launch, verify, and teardown. Use `./.har/*.sh` only when the CLI is not installed.
|
|
80
|
+
|
|
81
|
+
Work in the isolated git worktree created by launch. Use `./.har/agent-cli.sh <id> exec ...` to run ad-hoc project commands in that work dir.
|
|
82
|
+
|
|
83
|
+
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.
|
|
84
|
+
|
|
85
|
+
## Maintaining this harness
|
|
86
|
+
|
|
87
|
+
When the project stack changes (new test commands, database needs, env vars):
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
har env maintain
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The authoring agent updates scripts and this README. Review changes before committing.
|
|
94
|
+
|
|
95
|
+
**Do not** put runtime behavior in YAML — edit the scripts directly.
|
|
96
|
+
|
|
97
|
+
## Session lifecycle
|
|
98
|
+
|
|
99
|
+
Every `launch` starts a **fresh session**: a new git worktree from the current HEAD at
|
|
100
|
+
`~/worktrees/<base-branch>-<sha4>-har-agent-<id>-<rand4>`, on a branch of the same name.
|
|
101
|
+
The session is recorded in `.har/slots/agent-<id>.json` (the slot registry) — status,
|
|
102
|
+
verify, and teardown resolve the work dir through it. Make ALL file edits under the
|
|
103
|
+
work dir printed by launch, never in the main checkout.
|
|
104
|
+
|
|
105
|
+
- Relaunching a slot **replaces** its previous session; replacement requires `--replace` /
|
|
106
|
+
`confirmReplace=true` (or an interactive prompt). Uncommitted changes also need `--force`
|
|
107
|
+
after explicit user approval.
|
|
108
|
+
- `teardown` removes the worktree but **keeps the session branch** so you can push it
|
|
109
|
+
or open a PR (`--delete-branch` to drop it).
|
|
110
|
+
- `har env complete <id>` finishes a session: full verify (recorded as a validation),
|
|
111
|
+
then teardown — branch kept.
|
|
112
|
+
- `--no-worktree` runs the slot from the repo root instead (single-agent mode).
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Namespaced CLI for managing a CLI/library agent slot.
|
|
3
|
+
#
|
|
4
|
+
# Usage: ./.har/agent-cli.sh <agent-id> <command> [args...]
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
9
|
+
|
|
10
|
+
# shellcheck source=/dev/null
|
|
11
|
+
source "$SCRIPT_DIR/harness.env"
|
|
12
|
+
# shellcheck source=/dev/null
|
|
13
|
+
source "$SCRIPT_DIR/agent-slot.sh"
|
|
14
|
+
|
|
15
|
+
AGENT_ID="${1:?Usage: agent-cli.sh <agent-id> <command> [args...]}"
|
|
16
|
+
COMMAND="${2:?Usage: agent-cli.sh <agent-id> <command> [args...]}"
|
|
17
|
+
|
|
18
|
+
validate_agent_id "$AGENT_ID"
|
|
19
|
+
|
|
20
|
+
WORKTREE_DIR="$(existing_slot_worktree "$AGENT_ID")"
|
|
21
|
+
DB_PORT="${AGENT_DB_PORT:-15432}"
|
|
22
|
+
export PGPASSWORD="password"
|
|
23
|
+
|
|
24
|
+
resolve_work_dir() {
|
|
25
|
+
local env_file
|
|
26
|
+
env_file="$(resolve_agent_env_file "$AGENT_ID" "$REPO_ROOT")" || {
|
|
27
|
+
echo "No active environment for agent ${AGENT_ID}" >&2
|
|
28
|
+
echo " Run: ./.har/launch.sh ${AGENT_ID}" >&2
|
|
29
|
+
exit 1
|
|
30
|
+
}
|
|
31
|
+
# shellcheck source=/dev/null
|
|
32
|
+
source "$env_file"
|
|
33
|
+
resolve_agent_work_dir "$env_file" "$AGENT_ID"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
case "$COMMAND" in
|
|
37
|
+
status)
|
|
38
|
+
ENV_FILE="$(resolve_agent_env_file "$AGENT_ID" "$REPO_ROOT" || true)"
|
|
39
|
+
REGISTRY_FILE="$(slot_registry_file "$AGENT_ID")"
|
|
40
|
+
|
|
41
|
+
if [ -n "$ENV_FILE" ]; then
|
|
42
|
+
# shellcheck source=/dev/null
|
|
43
|
+
source "$ENV_FILE"
|
|
44
|
+
WT="$(existing_slot_worktree "$AGENT_ID")"
|
|
45
|
+
echo "Agent ${AGENT_ID}: active"
|
|
46
|
+
echo " Work dir: $(resolve_agent_work_dir "$ENV_FILE" "$AGENT_ID")"
|
|
47
|
+
[ -n "$WT" ] && [ -d "$WT" ] && echo " Worktree: $WT"
|
|
48
|
+
BRANCH="$(read_slot_field "$REGISTRY_FILE" branch || true)"
|
|
49
|
+
PURPOSE="$(read_slot_field "$REGISTRY_FILE" purpose || true)"
|
|
50
|
+
CREATED="$(read_slot_field "$REGISTRY_FILE" createdAt || true)"
|
|
51
|
+
[ -n "$PURPOSE" ] && echo " Purpose: $PURPOSE"
|
|
52
|
+
[ -n "$BRANCH" ] && echo " Branch: $BRANCH"
|
|
53
|
+
[ -n "$CREATED" ] && echo " Since: $CREATED"
|
|
54
|
+
[ -n "$WT" ] && echo " Git: $(slot_dirty_summary "$WT")"
|
|
55
|
+
else
|
|
56
|
+
echo "No active environment for agent ${AGENT_ID}"
|
|
57
|
+
echo " Run: ./.har/launch.sh ${AGENT_ID}"
|
|
58
|
+
fi
|
|
59
|
+
;;
|
|
60
|
+
|
|
61
|
+
logs)
|
|
62
|
+
echo "CLI profile has no managed processes (no PM2)." >&2
|
|
63
|
+
echo "Run project commands in the work dir, e.g.:" >&2
|
|
64
|
+
echo " ./.har/agent-cli.sh ${AGENT_ID} exec npm test" >&2
|
|
65
|
+
exit 1
|
|
66
|
+
;;
|
|
67
|
+
|
|
68
|
+
restart)
|
|
69
|
+
echo "CLI profile has no managed processes to restart." >&2
|
|
70
|
+
echo "Re-run launch or use exec for project-specific commands." >&2
|
|
71
|
+
exit 1
|
|
72
|
+
;;
|
|
73
|
+
|
|
74
|
+
psql)
|
|
75
|
+
QUERY="${3:-}"
|
|
76
|
+
if ! har_infra_enabled db; then
|
|
77
|
+
echo "PostgreSQL infra is disabled in harness.env" >&2
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
if [ -n "$QUERY" ]; then
|
|
81
|
+
har_pg psql -d "agent_${AGENT_ID}" -c "$QUERY"
|
|
82
|
+
else
|
|
83
|
+
har_pg psql -d "agent_${AGENT_ID}"
|
|
84
|
+
fi
|
|
85
|
+
;;
|
|
86
|
+
|
|
87
|
+
health)
|
|
88
|
+
API_PORT=$(( HARNESS_API_BASE_PORT + AGENT_ID * 10 ))
|
|
89
|
+
if [ -n "${HARNESS_HEALTH_CHECK_PATH:-}" ]; then
|
|
90
|
+
curl -sf "http://localhost:${API_PORT}${HARNESS_HEALTH_CHECK_PATH}" | node -e "
|
|
91
|
+
const d = require('fs').readFileSync('/dev/stdin','utf8');
|
|
92
|
+
try { console.log(JSON.stringify(JSON.parse(d), null, 2)); } catch { console.log(d); }
|
|
93
|
+
" || curl -v "http://localhost:${API_PORT}${HARNESS_HEALTH_CHECK_PATH}"
|
|
94
|
+
else
|
|
95
|
+
echo "No health check path configured in harness.env"
|
|
96
|
+
fi
|
|
97
|
+
;;
|
|
98
|
+
|
|
99
|
+
url)
|
|
100
|
+
FE_PORT=$(( HARNESS_FE_BASE_PORT + AGENT_ID * 10 ))
|
|
101
|
+
API_PORT=$(( HARNESS_API_BASE_PORT + AGENT_ID * 10 ))
|
|
102
|
+
WORK_DIR="$(resolve_work_dir 2>/dev/null || echo "$REPO_ROOT")"
|
|
103
|
+
echo "Work dir: $WORK_DIR"
|
|
104
|
+
[ -d "$WORKTREE_DIR" ] && echo "Worktree: $WORKTREE_DIR"
|
|
105
|
+
[ -n "${HARNESS_HEALTH_CHECK_PATH:-}" ] && echo "API: http://localhost:${API_PORT}${HARNESS_HEALTH_CHECK_PATH}"
|
|
106
|
+
har_infra_enabled db && echo "Database: agent_${AGENT_ID} @ localhost:${DB_PORT}"
|
|
107
|
+
har_infra_enabled minio && echo "MinIO: http://localhost:19001"
|
|
108
|
+
har_infra_enabled headless-browser && echo "Browser: http://localhost:13001"
|
|
109
|
+
har_infra_enabled mailpit && echo "Mailpit: http://localhost:18025"
|
|
110
|
+
;;
|
|
111
|
+
|
|
112
|
+
reset-db)
|
|
113
|
+
if ! har_infra_enabled db; then
|
|
114
|
+
echo "PostgreSQL infra is disabled in harness.env" >&2
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
echo "==> Resetting database for agent ${AGENT_ID}..."
|
|
118
|
+
har_pg psql -d postgres -c \
|
|
119
|
+
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='agent_${AGENT_ID}';" \
|
|
120
|
+
>/dev/null
|
|
121
|
+
har_pg dropdb --if-exists "agent_${AGENT_ID}"
|
|
122
|
+
har_pg createdb -T "$HARNESS_TEMPLATE_DB" "agent_${AGENT_ID}"
|
|
123
|
+
echo "✓ Database reset to clean state"
|
|
124
|
+
;;
|
|
125
|
+
|
|
126
|
+
slow-queries)
|
|
127
|
+
if ! har_infra_enabled db; then
|
|
128
|
+
echo "PostgreSQL infra is disabled in harness.env" >&2
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
har_pg psql -d "agent_${AGENT_ID}" -c "
|
|
132
|
+
SELECT round(mean_exec_time::numeric, 2) AS mean_ms,
|
|
133
|
+
calls,
|
|
134
|
+
left(query, 120) AS query
|
|
135
|
+
FROM pg_stat_statements
|
|
136
|
+
ORDER BY mean_exec_time DESC
|
|
137
|
+
LIMIT 20;" 2>/dev/null || echo "pg_stat_statements extension not available"
|
|
138
|
+
;;
|
|
139
|
+
|
|
140
|
+
exec)
|
|
141
|
+
shift 2
|
|
142
|
+
if [ $# -eq 0 ]; then
|
|
143
|
+
echo "Usage: agent-cli.sh ${AGENT_ID} exec <command>" >&2
|
|
144
|
+
exit 1
|
|
145
|
+
fi
|
|
146
|
+
WORK_DIR="$(resolve_work_dir)"
|
|
147
|
+
if har_infra_enabled db; then
|
|
148
|
+
PGHOST=localhost PGPORT="$DB_PORT" PGUSER=postgres PGDATABASE="agent_${AGENT_ID}" \
|
|
149
|
+
bash -c "cd '$WORK_DIR' && $*"
|
|
150
|
+
else
|
|
151
|
+
bash -c "cd '$WORK_DIR' && $*"
|
|
152
|
+
fi
|
|
153
|
+
;;
|
|
154
|
+
|
|
155
|
+
*)
|
|
156
|
+
echo "Unknown command: $COMMAND" >&2
|
|
157
|
+
echo ""
|
|
158
|
+
echo "Commands: status, url, exec <cmd>"
|
|
159
|
+
har_infra_enabled db && echo " psql [query], reset-db, slow-queries"
|
|
160
|
+
exit 1
|
|
161
|
+
;;
|
|
162
|
+
esac
|