@osfactory/har 0.64.2 → 1.0.0-rc.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 (141) hide show
  1. package/dist/index.js +15675 -6536
  2. package/dist/prompts/system-authoring.md +155 -0
  3. package/dist/templates/adaptation-prompt-init.md +67 -43
  4. package/dist/templates/adaptation-prompt-maintain.md +38 -38
  5. package/dist/templates/agent-skills/har-maintain.md +2 -2
  6. package/dist/templates/agent-skills/setup-har.md +6 -4
  7. package/dist/templates/cursor-rule.mdc.template +3 -3
  8. package/dist/templates/har-boilerplate/docker-compose.agent.yml +1 -1
  9. package/dist/templates/har-boilerplate/harness.env +11 -155
  10. package/dist/templates/har-boilerplate/stages.json +51 -2
  11. package/dist/templates/har-boilerplate-cli/docker-compose.agent.yml +1 -1
  12. package/dist/templates/har-boilerplate-cli/harness.env +12 -138
  13. package/dist/templates/har-boilerplate-cli/stages.json +42 -2
  14. package/dist/templates/har-boilerplate-ios/harness.env +10 -126
  15. package/dist/templates/har-boilerplate-ios/stages.json +42 -2
  16. package/dist/templates/migration-prompt.md +54 -0
  17. package/dist/templates/plugins/custom-stage-skeleton.sh +11 -20
  18. package/dist/templates/plugins/gitleaks/.github/workflows/gitleaks.yml +1 -1
  19. package/dist/templates/plugins/gitleaks/.har/stages/GITLEAKS.md +1 -1
  20. package/dist/templates/plugins/gitleaks/.har/stages/secrets-scan.sh +8 -23
  21. package/dist/templates/plugins/gitleaks/README.md +1 -1
  22. package/dist/templates/plugins/gitleaks/template.manifest.json +20 -5
  23. package/dist/templates/plugins/kerno/.github/workflows/kerno.yml +1 -1
  24. package/dist/templates/plugins/kerno/.har/stages/backend-validation.sh +8 -23
  25. package/dist/templates/plugins/kerno/README.md +1 -1
  26. package/dist/templates/plugins/kerno/template.manifest.json +20 -9
  27. package/dist/templates/plugins/local-plugin-skeleton/README.md +46 -0
  28. package/dist/templates/plugins/playwright/.har/stages/browser-e2e.sh +8 -23
  29. package/dist/templates/plugins/playwright/template.manifest.json +36 -13
  30. package/dist/templates/plugins/rocketsim/.har/stages/rocketsim-flows.sh +8 -23
  31. package/dist/templates/plugins/rocketsim/template.manifest.json +20 -8
  32. package/dist/templates/plugins/semgrep/.har/stages/sast.sh +10 -26
  33. package/dist/templates/plugins/semgrep/README.md +1 -1
  34. package/dist/templates/plugins/semgrep/template.manifest.json +16 -7
  35. package/dist/templates/plugins/trivy/.har/stages/vuln-scan.sh +8 -23
  36. package/dist/templates/plugins/trivy/README.md +1 -1
  37. package/dist/templates/plugins/trivy/template.manifest.json +20 -8
  38. package/dist/templates/{har-boilerplate-cli/CLAUDE.agent.md → profiles/cli/docs/claude-agent/definition-of-done.md} +0 -42
  39. package/dist/templates/profiles/cli/docs/claude-agent/do-not.md +6 -0
  40. package/dist/templates/profiles/cli/docs/claude-agent/environment.md +12 -0
  41. package/dist/templates/profiles/cli/docs/claude-agent/project-commands.md +10 -0
  42. package/dist/templates/profiles/cli/docs/claude-agent/readiness.md +6 -0
  43. package/dist/templates/profiles/cli/docs/readme/for-coding-agents.md +9 -0
  44. package/dist/templates/profiles/cli/docs/readme/intro.md +7 -0
  45. package/dist/templates/profiles/cli/docs/readme/port-shared-services-cli-profile.md +25 -0
  46. package/dist/templates/profiles/cli/docs/readme/quick-start.md +24 -0
  47. package/dist/templates/profiles/cli/docs/readme/verification-contract.md +22 -0
  48. package/dist/templates/profiles/cli/docs/readme/what-s-in-here.md +27 -0
  49. package/dist/templates/profiles/cli/profile.manifest.json +56 -1
  50. package/dist/templates/profiles/default/docs/claude-agent/definition-of-done.md +24 -0
  51. package/dist/templates/profiles/default/docs/claude-agent/do-not.md +10 -0
  52. package/dist/templates/profiles/default/docs/claude-agent/environment.md +19 -0
  53. package/dist/templates/profiles/default/docs/claude-agent/project-commands.md +10 -0
  54. package/dist/templates/profiles/default/docs/claude-agent/readiness.md +12 -0
  55. package/dist/templates/profiles/default/docs/readme/architecture.md +11 -0
  56. package/dist/templates/profiles/default/docs/readme/for-coding-agents.md +9 -0
  57. package/dist/templates/profiles/default/docs/readme/intro.md +7 -0
  58. package/dist/templates/profiles/default/docs/readme/port-shared-services.md +41 -0
  59. package/dist/templates/profiles/default/docs/readme/quick-start.md +24 -0
  60. package/dist/templates/profiles/default/docs/readme/readiness-layers.md +16 -0
  61. package/dist/templates/profiles/default/docs/readme/verification-contract.md +17 -0
  62. package/dist/templates/profiles/default/docs/readme/what-s-in-here.md +28 -0
  63. package/dist/templates/profiles/default/profile.manifest.json +62 -1
  64. package/dist/templates/profiles/ios/docs/claude-agent/definition-of-done.md +23 -0
  65. package/dist/templates/profiles/ios/docs/claude-agent/do-not.md +6 -0
  66. package/dist/templates/profiles/ios/docs/claude-agent/environment.md +14 -0
  67. package/dist/templates/profiles/ios/docs/claude-agent/intro.md +3 -0
  68. package/dist/templates/profiles/ios/docs/claude-agent/project-commands.md +21 -0
  69. package/dist/templates/profiles/ios/docs/claude-agent/readiness.md +6 -0
  70. package/dist/templates/profiles/ios/docs/claude-agent/user-flow-validation-rocketsim.md +11 -0
  71. package/dist/templates/profiles/ios/docs/readme/configuration.md +7 -0
  72. package/dist/templates/profiles/ios/docs/readme/intro.md +7 -0
  73. package/dist/templates/profiles/ios/docs/readme/port-shared-services-ios-profile.md +25 -0
  74. package/dist/templates/profiles/ios/docs/readme/quick-start.md +22 -0
  75. package/dist/templates/profiles/ios/docs/readme/simulators.md +45 -0
  76. package/dist/templates/profiles/ios/docs/readme/user-flow-validation.md +9 -0
  77. package/dist/templates/profiles/ios/docs/readme/verification-contract.md +13 -0
  78. package/dist/templates/profiles/ios/docs/readme/what-s-in-here.md +27 -0
  79. package/dist/templates/profiles/ios/profile.manifest.json +59 -3
  80. package/dist/templates/runtime-bundles/pm2-runtime/attach.sh +8 -15
  81. package/dist/templates/runtime-bundles/pm2-runtime/env.template +1 -1
  82. package/dist/templates/runtime-bundles/pm2-runtime/stages/api-health.sh +9 -0
  83. package/dist/templates/runtime-bundles/shared-kernel/STAGES.md +32 -18
  84. package/dist/templates/runtime-bundles/shared-kernel/agent-cli.sh +35 -0
  85. package/dist/templates/runtime-bundles/shared-kernel/launch.sh +35 -0
  86. package/dist/templates/runtime-bundles/shared-kernel/lib/verify-runner.mjs +129 -0
  87. package/dist/templates/runtime-bundles/shared-kernel/preflight.sh +35 -0
  88. package/dist/templates/runtime-bundles/shared-kernel/setup-infra.sh +35 -0
  89. package/dist/templates/runtime-bundles/shared-kernel/stages/README.sh +1 -1
  90. package/dist/templates/runtime-bundles/shared-kernel/stages/readiness.sh +14 -0
  91. package/dist/templates/runtime-bundles/shared-kernel/teardown.sh +35 -0
  92. package/dist/templates/runtime-bundles/shared-kernel/verify.sh +35 -0
  93. package/dist/templates/shared-docs/claude-agent/intro.md +3 -0
  94. package/dist/templates/shared-docs/readme/maintaining-this-harness.md +14 -0
  95. package/dist/templates/shared-docs/readme/run-history.md +5 -0
  96. package/dist/templates/shared-docs/readme/session-lifecycle.md +20 -0
  97. package/package.json +1 -1
  98. package/dist/templates/har-boilerplate/CLAUDE.agent.md +0 -83
  99. package/dist/templates/har-boilerplate/README.md +0 -199
  100. package/dist/templates/har-boilerplate/STAGES.md +0 -99
  101. package/dist/templates/har-boilerplate/agent-cli.sh +0 -225
  102. package/dist/templates/har-boilerplate/agent-slot.sh +0 -850
  103. package/dist/templates/har-boilerplate/attach.sh +0 -22
  104. package/dist/templates/har-boilerplate/ecosystem.agent.template.cjs +0 -52
  105. package/dist/templates/har-boilerplate/env.template +0 -40
  106. package/dist/templates/har-boilerplate/launch.sh +0 -323
  107. package/dist/templates/har-boilerplate/preflight.sh +0 -32
  108. package/dist/templates/har-boilerplate/provision-toolchain.sh +0 -766
  109. package/dist/templates/har-boilerplate/setup-infra.sh +0 -178
  110. package/dist/templates/har-boilerplate/stages/README.sh +0 -11
  111. package/dist/templates/har-boilerplate/teardown.sh +0 -87
  112. package/dist/templates/har-boilerplate/verify.sh +0 -249
  113. package/dist/templates/har-boilerplate-cli/README.md +0 -157
  114. package/dist/templates/har-boilerplate-cli/STAGES.md +0 -99
  115. package/dist/templates/har-boilerplate-cli/agent-cli.sh +0 -160
  116. package/dist/templates/har-boilerplate-cli/agent-slot.sh +0 -832
  117. package/dist/templates/har-boilerplate-cli/launch.sh +0 -216
  118. package/dist/templates/har-boilerplate-cli/preflight.sh +0 -28
  119. package/dist/templates/har-boilerplate-cli/provision-toolchain.sh +0 -766
  120. package/dist/templates/har-boilerplate-cli/setup-infra.sh +0 -178
  121. package/dist/templates/har-boilerplate-cli/stages/README.sh +0 -11
  122. package/dist/templates/har-boilerplate-cli/teardown.sh +0 -66
  123. package/dist/templates/har-boilerplate-cli/verify.sh +0 -210
  124. package/dist/templates/har-boilerplate-ios/ADAPT-PROMPT.md +0 -63
  125. package/dist/templates/har-boilerplate-ios/CLAUDE.agent.md +0 -90
  126. package/dist/templates/har-boilerplate-ios/README.md +0 -173
  127. package/dist/templates/har-boilerplate-ios/STAGES.md +0 -99
  128. package/dist/templates/har-boilerplate-ios/agent-cli.sh +0 -149
  129. package/dist/templates/har-boilerplate-ios/agent-slot.sh +0 -479
  130. package/dist/templates/har-boilerplate-ios/launch.sh +0 -206
  131. package/dist/templates/har-boilerplate-ios/provision-toolchain.sh +0 -766
  132. package/dist/templates/har-boilerplate-ios/setup-infra.sh +0 -112
  133. package/dist/templates/har-boilerplate-ios/simulator.sh +0 -492
  134. package/dist/templates/har-boilerplate-ios/stages/README.sh +0 -11
  135. package/dist/templates/har-boilerplate-ios/teardown.sh +0 -68
  136. package/dist/templates/har-boilerplate-ios/verify.sh +0 -185
  137. package/dist/templates/runtime-bundles/pm2-runtime/README.md +0 -4
  138. package/dist/templates/runtime-bundles/shared-kernel/README.md +0 -3
  139. package/dist/templates/runtime-bundles/shared-kernel/provision-toolchain.sh +0 -766
  140. package/dist/templates/runtime-bundles/xcode-sim/README.md +0 -4
  141. package/dist/templates/runtime-bundles/xcode-sim/simulator.sh +0 -492
@@ -1,112 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Sets up shared infrastructure for all iOS agent slots.
3
- # Checks the toolchain, and boots the configured simulator when slots share one
4
- # (HARNESS_SIMULATOR_SHARED=true) — otherwise launch.sh boots each slot's own
5
- # device. No Docker required for pure iOS apps — extend only when a local
6
- # backend container is needed. 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
-
13
- # shellcheck source=/dev/null
14
- source "$SCRIPT_DIR/harness.env"
15
- # shellcheck source=/dev/null
16
- source "$SCRIPT_DIR/agent-slot.sh"
17
- # shellcheck source=/dev/null
18
- source "$SCRIPT_DIR/simulator.sh"
19
-
20
- log() { echo "==> $*" >&2; }
21
-
22
- # simctl could not be asked, so nothing it did not return is evidence of anything
23
- # missing on this machine. The wording lives in simulator.sh so launch and this
24
- # script cannot drift apart on which of the two causes they name.
25
- fail_simctl_unavailable() {
26
- har_sim_log_simctl_unavailable
27
- exit 1
28
- }
29
-
30
- # ── Xcode check ───────────────────────────────────────────────────────────────
31
- if ! command -v xcodebuild >/dev/null 2>&1; then
32
- echo "Error: xcodebuild not found — install Xcode from the App Store." >&2
33
- exit 1
34
- fi
35
-
36
- XCODE_VERSION="$(xcodebuild -version 2>/dev/null | head -1 || echo 'Xcode (unknown)')"
37
- log "Found: $XCODE_VERSION"
38
-
39
- # ── Simulator ─────────────────────────────────────────────────────────────────
40
- SIMULATOR_NAME="${HARNESS_SIMULATOR_NAME:-iPhone 16}"
41
-
42
- if har_sim_per_slot_enabled; then
43
- # Each slot creates and boots its own device at launch — booting a shared one
44
- # here would only waste a device no slot is going to use. Resolve the model now
45
- # so a missing runtime is reported before any worktree is created.
46
- FAMILY="$(har_sim_preferred_family)"
47
- PLAN="$(har_sim_plan_creation "${HARNESS_SIMULATOR_NAME:-}" "$FAMILY")"
48
- PLAN_STATUS="${PLAN%%$'\t'*}"
49
- if [ "$PLAN_STATUS" = "OK" ]; then
50
- log "Per-slot simulators: launch creates one $(printf '%s' "$PLAN" | cut -f3) per agent."
51
- elif [ "$PLAN_STATUS" = "SIMCTL_UNAVAILABLE" ]; then
52
- fail_simctl_unavailable
53
- elif [ "$PLAN_STATUS" = "NO_MODEL" ] && [ -n "$(har_sim_device_by_name "${HARNESS_SIMULATOR_NAME:-}")" ]; then
54
- # Supported fallback: the name is an existing device rather than a model.
55
- log "Per-slot simulators: launch reuses the existing device '${SIMULATOR_NAME}'."
56
- else
57
- # Fail here rather than at launch, which would already have created a
58
- # worktree, a branch and an env file the user then has to tear down.
59
- echo "Error: no simulator can be prepared for '${SIMULATOR_NAME}'." >&2
60
- if [ "$PLAN" != "$PLAN_STATUS" ]; then
61
- echo " Available ${FAMILY} models: ${PLAN#*$'\t'}" >&2
62
- else
63
- echo " No iOS runtime is installed — add one in Xcode → Settings → Components." >&2
64
- fi
65
- echo " Update HARNESS_SIMULATOR_NAME in .har/harness.env." >&2
66
- exit 1
67
- fi
68
- else
69
- log "Checking simulator: ${SIMULATOR_NAME}"
70
-
71
- # Exact name match: a substring match on "iPhone 16" also hits "iPhone 16 Pro Max".
72
- RESOLVED="$(har_sim_resolve_configured)"
73
- UDID="${RESOLVED%%$'\t'*}"
74
-
75
- if [ -z "$UDID" ]; then
76
- har_sim_simctl_available || fail_simctl_unavailable
77
- echo "Error: Simulator '${SIMULATOR_NAME}' not found in available devices." >&2
78
- echo " Available simulators:" >&2
79
- xcrun simctl list devices available 2>/dev/null | grep -E 'iPhone|iPad' | head -20 >&2
80
- echo " Update HARNESS_SIMULATOR_NAME in .har/harness.env." >&2
81
- exit 1
82
- fi
83
-
84
- if [ "$(har_sim_device_state "$UDID")" = "Booted" ]; then
85
- log "Simulator '${SIMULATOR_NAME}' ($UDID) is already booted."
86
- else
87
- log "Booting simulator '${SIMULATOR_NAME}' ($UDID)..."
88
- if har_sim_boot "$UDID"; then
89
- log "Simulator is ready."
90
- else
91
- echo "Warning: Simulator did not reach Booted state." >&2
92
- fi
93
- fi
94
- fi
95
-
96
- # ── Optional Docker infra ─────────────────────────────────────────────────────
97
- SERVICES="${HARNESS_INFRA_SERVICES:-}"
98
- if [ -n "$SERVICES" ]; then
99
- COMPOSE_FILE="$SCRIPT_DIR/docker-compose.agent.yml"
100
- COMPOSE_PROJECT="har-${HARNESS_PROJECT_NAME}"
101
- log "Starting shared infrastructure (project: $COMPOSE_PROJECT): $SERVICES"
102
- docker compose -p "$COMPOSE_PROJECT" -f "$COMPOSE_FILE" up -d $SERVICES
103
- fi
104
-
105
- log ""
106
- log "Infrastructure is ready."
107
- # In per-slot mode the device is reported by launch, once it exists.
108
- if [ -n "${UDID:-}" ]; then
109
- log " Simulator: ${SIMULATOR_NAME} ($UDID)"
110
- fi
111
- har_infra_enabled mock-server && log " Mock server: running (see docker-compose.agent.yml)"
112
- exit 0
@@ -1,492 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Per-slot iOS Simulator allocation.
3
- #
4
- # One booted simulator shared by every agent slot means concurrent xcodebuild
5
- # destinations, installs of the same bundle id, and UI flows collide. Each slot
6
- # instead gets a simulator of its own, created at launch and deleted at teardown:
7
- # unique by construction, pristine on every launch, and never a device the
8
- # developer is using by hand.
9
- #
10
- # Source AFTER harness.env and agent-slot.sh — read_slot_field, slot_registry_file
11
- # and SCRIPT_DIR come from there.
12
- #
13
- # What a slot holds is recorded in .har/simulators/agent-<id>.json, so teardown
14
- # knows whether the device was HAR's to delete.
15
-
16
- # ── Configuration ─────────────────────────────────────────────────────────────
17
-
18
- # Preferred device family: auto (infer from HARNESS_SIMULATOR_NAME) | iPhone | iPad
19
- har_sim_preferred_family() {
20
- local configured="${HARNESS_SIMULATOR_FAMILY:-auto}"
21
- case "$configured" in
22
- iPhone|iPad) echo "$configured"; return ;;
23
- esac
24
- case "${HARNESS_SIMULATOR_NAME:-}" in
25
- iPad*|iPAD*|ipad*) echo "iPad" ;;
26
- *) echo "iPhone" ;;
27
- esac
28
- }
29
-
30
- har_sim_per_slot_enabled() {
31
- [ "${HARNESS_SIMULATOR_SHARED:-false}" != "true" ]
32
- }
33
-
34
- har_sim_log() { echo "==> simulator: $*" >&2; }
35
-
36
- # "iPad Air 11-inch (M4)" → "iPad-Air-11-inch-M4"
37
- har_sim_slug() {
38
- printf '%s' "$1" | tr -c 'A-Za-z0-9._-' '-' | tr -s '-' | sed 's/^-//; s/-$//'
39
- }
40
-
41
- # Short digest of the repository path. Two checkouts can share a folder name,
42
- # and therefore HARNESS_PROJECT_NAME, but not a path — without it the prefix
43
- # sweep below would delete the other checkout's live device.
44
- har_sim_repo_digest() {
45
- local repo
46
- # -P so the same checkout reached through a symlink yields the same digest.
47
- repo="$(cd -P "${SCRIPT_DIR}/.." 2>/dev/null && pwd -P || echo "${SCRIPT_DIR}")"
48
- node -e 'process.stdout.write(require("crypto").createHash("sha1").update(process.argv[1]).digest("hex").slice(0, 6))' "$repo"
49
- }
50
-
51
- # Stable part of a slot's device name. The model is appended after it, so a
52
- # device left over from a launch on another model is still recognised as this
53
- # slot's and cleaned up.
54
- har_sim_device_label_prefix() {
55
- echo "har-$(har_sim_slug "${HARNESS_PROJECT_NAME:-har}")-$(har_sim_repo_digest)-agent-${1}"
56
- }
57
-
58
- har_sim_device_label() {
59
- local prefix model
60
- prefix="$(har_sim_device_label_prefix "$1")"
61
- model="$(har_sim_slug "${2:-}")"
62
- [ -n "$model" ] && echo "${prefix}-${model}" || echo "$prefix"
63
- }
64
-
65
- # ── Claims ────────────────────────────────────────────────────────────────────
66
- # Slots never compete for a device, so a claim only records what teardown must
67
- # release — no locking, no cross-slot exclusion.
68
-
69
- har_sim_claims_dir() { echo "${SCRIPT_DIR}/simulators"; }
70
- har_sim_claim_file() { echo "$(har_sim_claims_dir)/agent-${1}.json"; }
71
-
72
- har_sim_write_claim() {
73
- local agent_id="$1" udid="$2" name="$3" created="$4" file
74
- file="$(har_sim_claim_file "$agent_id")"
75
- mkdir -p "$(dirname "$file")"
76
- node -e '
77
- const fs = require("fs");
78
- const [file, agentId, udid, name, created] = process.argv.slice(1);
79
- fs.writeFileSync(file, JSON.stringify({
80
- agentId: Number(agentId),
81
- udid,
82
- name,
83
- createdByHar: created === "true",
84
- claimedAt: new Date().toISOString(),
85
- }, null, 2) + "\n");
86
- ' "$file" "$agent_id" "$udid" "$name" "$created"
87
- }
88
-
89
- # Agent id of another live slot holding this udid, if any. Only a pinned
90
- # HARNESS_SIMULATOR_UDID can produce that overlap.
91
- har_sim_claim_owner_of() {
92
- local udid="$1" self="$2" dir file id
93
- dir="$(har_sim_claims_dir)"
94
- [ -d "$dir" ] || return 0
95
- for file in "$dir"/agent-*.json; do
96
- [ -f "$file" ] || continue
97
- id="$(basename "$file")"
98
- id="${id#agent-}"
99
- id="${id%.json}"
100
- [ "$id" = "$self" ] && continue
101
- [ -f "$(slot_registry_file "$id")" ] || continue
102
- if [ "$(read_slot_field "$file" udid || true)" = "$udid" ]; then
103
- echo "$id"
104
- return 0
105
- fi
106
- done
107
- }
108
-
109
- # ── Device discovery ──────────────────────────────────────────────────────────
110
-
111
- # Every simctl call goes through here, so "simctl could not be asked" stays
112
- # distinguishable from "simctl answered, and the answer is empty" at the one place
113
- # that still knows the difference. Non-zero means the first; callers check it
114
- # instead of reading an empty list as a fact about the machine.
115
- har_sim_simctl() {
116
- local out rc=0
117
- out="$(xcrun simctl "$@" 2>/dev/null)" || rc=$?
118
- if [ "$rc" -ne 0 ] || [ -z "$out" ]; then
119
- return 1
120
- fi
121
- printf '%s' "$out"
122
- }
123
-
124
- # Unavailable devices are skipped: a device whose runtime was uninstalled must
125
- # not pass validation only to fail at boot.
126
- har_sim_device_field() {
127
- local devices
128
- devices="$(har_sim_simctl list devices --json)" || return 1
129
- printf '%s' "$devices" | node -e '
130
- const data = JSON.parse(require("fs").readFileSync(0, "utf8"));
131
- const [want, field] = process.argv.slice(1);
132
- const device = Object.values(data.devices || {})
133
- .flat()
134
- .filter((entry) => entry && entry.isAvailable !== false)
135
- .find((entry) => entry.udid === want);
136
- if (device && device[field]) process.stdout.write(String(device[field]));
137
- ' "$1" "$2" 2>/dev/null || true
138
- }
139
-
140
- har_sim_device_state() { har_sim_device_field "$1" state; }
141
- har_sim_device_name() { har_sim_device_field "$1" name; }
142
-
143
- # UDID of an existing device with this exact name, newest runtime first.
144
- # A substring match would resolve "iPhone 16" to "iPhone 16 Pro Max".
145
- har_sim_device_by_name() {
146
- local want="${1:-}" devices
147
- [ -n "$want" ] || return 0
148
- devices="$(har_sim_simctl list devices available --json)" || return 1
149
- printf '%s' "$devices" | node -e '
150
- const data = JSON.parse(require("fs").readFileSync(0, "utf8"));
151
- const want = process.argv[1];
152
- const rows = [];
153
- for (const [runtime, devices] of Object.entries(data.devices || {})) {
154
- if (!runtime.includes("SimRuntime.iOS-")) continue;
155
- const parts = runtime.match(/iOS-(\d+)(?:-(\d+))?/);
156
- const version = parts ? Number(parts[1]) * 1000 + Number(parts[2] || 0) : 0;
157
- for (const device of devices || []) {
158
- if (!device.udid || device.isAvailable === false) continue;
159
- if (device.name !== want) continue;
160
- rows.push({ udid: device.udid, version });
161
- }
162
- }
163
- rows.sort((a, b) => b.version - a.version);
164
- if (rows[0]) process.stdout.write(rows[0].udid);
165
- ' "$want" 2>/dev/null || true
166
- }
167
-
168
- # The shared-mode device, as "<udid>\t<name>".
169
- har_sim_resolve_configured() {
170
- local udid
171
- udid="$(har_sim_device_by_name "${HARNESS_SIMULATOR_NAME:-}")"
172
- [ -n "$udid" ] || return 0
173
- printf '%s\t%s' "$udid" "${HARNESS_SIMULATOR_NAME}"
174
- }
175
-
176
- # `simctl list` is a subprocess and a JSON parse, so each answer is cached — but in
177
- # the calling shell, not the process: `$(har_sim_runtimes_json)` fetches, then throws
178
- # the cache and the availability flag away with the subshell that held them. Call
179
- # these directly and read the variable whenever either one matters.
180
- har_sim_devicetypes_json() {
181
- if [ -z "${_HAR_SIM_TYPES_JSON:-}" ]; then
182
- _HAR_SIM_TYPES_JSON="$(har_sim_simctl list devicetypes --json)" || _HAR_SIM_TYPES_JSON='{}'
183
- fi
184
- printf '%s' "$_HAR_SIM_TYPES_JSON"
185
- }
186
-
187
- har_sim_runtimes_json() {
188
- if [ -z "${_HAR_SIM_RUNTIMES_JSON:-}" ]; then
189
- if _HAR_SIM_RUNTIMES_JSON="$(har_sim_simctl list runtimes --json)"; then
190
- _HAR_SIM_SIMCTL_OK=true
191
- else
192
- _HAR_SIM_RUNTIMES_JSON='{}'
193
- _HAR_SIM_SIMCTL_OK=false
194
- # A second call, but only ever when simctl is already failing — and failing
195
- # is the fast path. Its stderr is the only thing that tells the causes apart.
196
- _HAR_SIM_SIMCTL_ERROR="$(xcrun simctl list runtimes --json 2>&1 >/dev/null || true)"
197
- fi
198
- fi
199
- printf '%s' "$_HAR_SIM_RUNTIMES_JSON"
200
- }
201
-
202
- # Ask simctl once, recording in this shell whether it answered and why not.
203
- har_sim_probe_simctl() {
204
- har_sim_runtimes_json >/dev/null
205
- }
206
-
207
- har_sim_simctl_available() {
208
- har_sim_probe_simctl
209
- [ "${_HAR_SIM_SIMCTL_OK:-true}" = "true" ]
210
- }
211
-
212
- # Two unrelated problems make simctl unusable — a sandbox denying the
213
- # CoreSimulatorService lookup, and a developer directory with no simctl in it.
214
- # Naming the wrong one costs the reader the hunt this check exists to prevent.
215
- har_sim_log_simctl_unavailable() {
216
- # Teardown hits two failures in a row; the cause only needs saying once.
217
- [ -z "${_HAR_SIM_SIMCTL_CAUSE_REPORTED:-}" ] || return 0
218
- _HAR_SIM_SIMCTL_CAUSE_REPORTED=1
219
- # For a launch the failing call happened inside the command substitution around
220
- # har_sim_plan_creation, so the reason died with that subshell — ask again here.
221
- har_sim_probe_simctl
222
- case "${_HAR_SIM_SIMCTL_ERROR:-}" in
223
- *"not a developer tool"*|*"unable to find utility"*|*"command not found"*)
224
- har_sim_log "'xcrun simctl' is not in the selected developer directory"
225
- har_sim_log " install Xcode, then point the toolchain at it:"
226
- har_sim_log " sudo xcode-select -s /Applications/Xcode.app"
227
- har_sim_log " current selection: $(xcode-select -p 2>/dev/null || echo 'none')"
228
- return
229
- ;;
230
- esac
231
- har_sim_log "cannot reach CoreSimulatorService — 'xcrun simctl' is unusable here"
232
- har_sim_log " this usually means the command is running inside an agent sandbox"
233
- har_sim_log " run it from a normal terminal, or let the agent run 'xcrun simctl' unsandboxed"
234
- }
235
-
236
- # Model and runtime to create this slot's device from. Failures carry the models
237
- # this machine can create, so the caller never has to re-derive that list.
238
- # Prints one of:
239
- # OK <deviceTypeId> <deviceTypeName> <runtimeId> <runtimeVersion>
240
- # NO_RUNTIME no iOS runtime installed
241
- # NO_MODEL <models> HARNESS_SIMULATOR_NAME is not a device model
242
- # NO_RUNTIME_FOR_MODEL <models> model exists, no installed runtime supports it
243
- # SIMCTL_UNAVAILABLE simctl could not be asked at all
244
- har_sim_plan_creation() {
245
- local types runtimes
246
- types="$(har_sim_devicetypes_json)"
247
- # Not `$(...)`: that would discard the availability flag with the subshell.
248
- har_sim_runtimes_json >/dev/null
249
- runtimes="$_HAR_SIM_RUNTIMES_JSON"
250
- # Checked before the empty lists are read as an answer about the machine.
251
- if ! har_sim_simctl_available; then
252
- printf 'SIMCTL_UNAVAILABLE'
253
- return
254
- fi
255
- node -e '
256
- const [typesRaw, runtimesRaw, wantName, family] = process.argv.slice(1);
257
- const types = JSON.parse(typesRaw).devicetypes || [];
258
- const version = (value) => {
259
- const [major = 0, minor = 0] = String(value || "0").split(".").map(Number);
260
- return major * 1000 + minor;
261
- };
262
- const runtimes = (JSON.parse(runtimesRaw).runtimes || [])
263
- .filter((runtime) => runtime.isAvailable !== false)
264
- .filter((runtime) => String(runtime.identifier || "").includes("SimRuntime.iOS-"))
265
- .sort((a, b) => version(b.version) - version(a.version));
266
-
267
- const isPad = (entry) => /ipad/i.test(entry.productFamily || entry.name || "");
268
- const inFamily = (entry) => (family === "iPad" ? isPad(entry) : !isPad(entry));
269
-
270
- // Models the newest runtime could create, named so an error can list them.
271
- // Truncation is stated rather than silent — a model cut off the list still exists.
272
- function availableModels() {
273
- const runtime = runtimes[0];
274
- const supported =
275
- runtime && Array.isArray(runtime.supportedDeviceTypes) && runtime.supportedDeviceTypes.length
276
- ? runtime.supportedDeviceTypes
277
- : types;
278
- const names = supported.filter(inFamily).map((entry) => entry.name);
279
- const shown = names.slice(0, 8).join(", ");
280
- return names.length > 8 ? `${shown}, … (${names.length - 8} more)` : shown;
281
- }
282
-
283
- function plan() {
284
- if (!runtimes.length) return "NO_RUNTIME";
285
- if (wantName && !types.some((type) => type.name === wantName)) {
286
- return `NO_MODEL\t${availableModels()}`;
287
- }
288
-
289
- for (const runtime of runtimes) {
290
- const supported =
291
- Array.isArray(runtime.supportedDeviceTypes) && runtime.supportedDeviceTypes.length
292
- ? runtime.supportedDeviceTypes
293
- : types;
294
- // Apple lists supported device types newest first within each family.
295
- const pool = supported.filter(inFamily);
296
- const chosen = wantName ? pool.find((type) => type.name === wantName) : pool[0];
297
- if (chosen) {
298
- return `OK\t${chosen.identifier}\t${chosen.name}\t${runtime.identifier}\t${runtime.version}`;
299
- }
300
- }
301
- return wantName ? `NO_RUNTIME_FOR_MODEL\t${availableModels()}` : "NO_RUNTIME";
302
- }
303
-
304
- process.stdout.write(plan());
305
- ' "$types" "$runtimes" "${1:-}" "${2:-iPhone}" 2>/dev/null || true
306
- }
307
-
308
- # Every device whose name is <prefix> or starts with "<prefix>-", so a slot's
309
- # leftovers are found whatever model they were created from.
310
- har_sim_devices_with_prefix() {
311
- local devices
312
- devices="$(har_sim_simctl list devices --json)" || return 1
313
- printf '%s' "$devices" | node -e '
314
- const data = JSON.parse(require("fs").readFileSync(0, "utf8"));
315
- const prefix = process.argv[1];
316
- const matches = Object.values(data.devices || {})
317
- .flat()
318
- .filter((device) => device && device.udid)
319
- .filter((device) => device.name === prefix || String(device.name || "").startsWith(`${prefix}-`))
320
- .map((device) => device.udid);
321
- process.stdout.write(matches.join("\n"));
322
- ' "$1" 2>/dev/null || true
323
- }
324
-
325
- # Returns 0 even when it could not look: teardown.sh runs under `set -e`, and
326
- # aborting there would leave the slot registry behind on top of the device.
327
- har_sim_delete_devices_with_prefix() {
328
- local udid list
329
- if ! list="$(har_sim_devices_with_prefix "$1")"; then
330
- har_sim_log "cannot check for leftover devices named ${1}* — none were removed"
331
- har_sim_log_simctl_unavailable
332
- return 0
333
- fi
334
- while read -r udid; do
335
- [ -n "$udid" ] || continue
336
- xcrun simctl delete "$udid" >/dev/null 2>&1 || true
337
- done <<< "$list"
338
- }
339
-
340
- # ── Boot ──────────────────────────────────────────────────────────────────────
341
-
342
- har_sim_boot() {
343
- local udid="$1" i
344
- xcrun simctl boot "$udid" >/dev/null 2>&1 || true
345
- if xcrun simctl bootstatus "$udid" -b >/dev/null 2>&1; then
346
- return 0
347
- fi
348
- # Fallback for a toolchain without bootstatus: the plain listing is one
349
- # subprocess, where the JSON form costs xcrun plus node on every poll.
350
- for i in $(seq 1 30); do
351
- if xcrun simctl list devices 2>/dev/null | grep -q "${udid}.*Booted"; then
352
- return 0
353
- fi
354
- sleep 2
355
- done
356
- return 1
357
- }
358
-
359
- # ── Acquire / release ─────────────────────────────────────────────────────────
360
-
361
- # Prints "<udid>\t<name>\t<createdByHar>".
362
- _har_sim_select() {
363
- local agent_id="$1"
364
- local want="${HARNESS_SIMULATOR_NAME:-}"
365
- local family owner name plan status models device_type model runtime label udid
366
-
367
- # An explicit UDID is a deliberate override — never fall back off it.
368
- if [ -n "${HARNESS_SIMULATOR_UDID:-}" ]; then
369
- # A failed lookup is "could not ask"; an empty one is "no such device".
370
- name="$(har_sim_device_name "$HARNESS_SIMULATOR_UDID")" || {
371
- har_sim_log_simctl_unavailable
372
- return 1
373
- }
374
- if [ -z "$name" ]; then
375
- har_sim_log "HARNESS_SIMULATOR_UDID=${HARNESS_SIMULATOR_UDID} is not a known device"
376
- return 1
377
- fi
378
- owner="$(har_sim_claim_owner_of "$HARNESS_SIMULATOR_UDID" "$agent_id")"
379
- if [ -n "$owner" ]; then
380
- har_sim_log "warning: agent ${owner} already holds ${name} — a pinned HARNESS_SIMULATOR_UDID"
381
- har_sim_log " gives every slot the same device; unset it to get one per slot."
382
- fi
383
- printf '%s\t%s\t%s' "$HARNESS_SIMULATOR_UDID" "$name" "false"
384
- return 0
385
- fi
386
-
387
- family="$(har_sim_preferred_family)"
388
- plan="$(har_sim_plan_creation "$want" "$family")"
389
- status="${plan%%$'\t'*}"
390
- # Failure statuses carry the creatable models after the first tab.
391
- models=""
392
- [ "$plan" != "$status" ] && models="${plan#*$'\t'}"
393
-
394
- if [ "$status" = "SIMCTL_UNAVAILABLE" ]; then
395
- har_sim_log_simctl_unavailable
396
- return 1
397
- fi
398
-
399
- if [ "$status" = "OK" ]; then
400
- IFS=$'\t' read -r _ device_type model runtime _ <<< "$plan"
401
- label="$(har_sim_device_label "$agent_id" "$model")"
402
- # A crashed launch can leave the slot's device behind — start from scratch,
403
- # including a device this slot created from a different model.
404
- har_sim_delete_devices_with_prefix "$(har_sim_device_label_prefix "$agent_id")"
405
- har_sim_log "creating ${label} (${model}, ${runtime##*SimRuntime.})"
406
- udid="$(xcrun simctl create "$label" "$device_type" "$runtime" 2>/dev/null | tr -d '[:space:]')"
407
- if [ -z "$udid" ]; then
408
- har_sim_log "xcrun simctl create failed for ${label} (${device_type}, ${runtime})"
409
- return 1
410
- fi
411
- printf '%s\t%s\t%s' "$udid" "$label" "true"
412
- return 0
413
- fi
414
-
415
- # Not a model: fall back to an existing device carrying that name, which is
416
- # how a hand-renamed simulator is targeted.
417
- if [ "$status" = "NO_MODEL" ]; then
418
- udid="$(har_sim_device_by_name "$want")"
419
- if [ -n "$udid" ]; then
420
- har_sim_log "'${want}' is not a device model — using the existing device with that name"
421
- printf '%s\t%s\t%s' "$udid" "$want" "false"
422
- return 0
423
- fi
424
- har_sim_log "HARNESS_SIMULATOR_NAME='${want}' is neither a device model nor an existing device"
425
- har_sim_log " available ${family} models: ${models}"
426
- return 1
427
- fi
428
-
429
- if [ "$status" = "NO_RUNTIME_FOR_MODEL" ]; then
430
- har_sim_log "no installed iOS runtime supports '${want}'"
431
- har_sim_log " install a newer runtime in Xcode → Settings → Components, or pick another model"
432
- har_sim_log " available ${family} models: ${models}"
433
- return 1
434
- fi
435
-
436
- har_sim_log "no iOS simulator runtime is installed"
437
- har_sim_log " install one in Xcode → Settings → Components"
438
- return 1
439
- }
440
-
441
- # Reserve a simulator for a slot, boot it, and record the claim.
442
- # Prints "<udid>\t<name>" on success.
443
- har_sim_acquire() {
444
- local agent_id="$1" udid name created
445
- local result="" rc=0
446
- # `|| rc=$?` keeps a failed selection from tripping `set -e` in the caller.
447
- result="$(_har_sim_select "$agent_id")" || rc=$?
448
- [ "$rc" -eq 0 ] || return "$rc"
449
-
450
- udid="$(printf '%s' "$result" | cut -f1)"
451
- name="$(printf '%s' "$result" | cut -f2)"
452
- created="$(printf '%s' "$result" | cut -f3)"
453
- if [ -z "$udid" ]; then
454
- har_sim_log "no simulator could be selected"
455
- return 1
456
- fi
457
- har_sim_write_claim "$agent_id" "$udid" "$name" "$created"
458
-
459
- if ! har_sim_boot "$udid"; then
460
- har_sim_log "device ${name} (${udid}) did not reach Booted state"
461
- return 1
462
- fi
463
- printf '%s\t%s' "$udid" "$name"
464
- }
465
-
466
- # Drop a slot's claim. Devices HAR created are deleted; a device the developer
467
- # owns is left alone, and never even shut down.
468
- har_sim_release() {
469
- local agent_id="$1" file udid created
470
- file="$(har_sim_claim_file "$agent_id")"
471
- if [ -f "$file" ]; then
472
- udid="$(read_slot_field "$file" udid || true)"
473
- created="$(read_slot_field "$file" createdByHar || true)"
474
- rm -f "$file"
475
- if [ "$created" = "true" ] && [ -n "$udid" ]; then
476
- xcrun simctl shutdown "$udid" >/dev/null 2>&1 || true
477
- if xcrun simctl delete "$udid" >/dev/null 2>&1; then
478
- echo "✓ Deleted simulator created for this slot ($udid)"
479
- else
480
- # Naming it is the whole remedy left: the claim is gone, so only the name
481
- # prefix sweep at the next launch can still find this device.
482
- har_sim_log "could not delete ${udid}, created for agent ${agent_id} — it is still on this machine"
483
- har_sim_simctl_available || har_sim_log_simctl_unavailable
484
- fi
485
- fi
486
- fi
487
-
488
- # A launch killed between `simctl create` and the claim leaves a device no
489
- # claim points at. Only HAR creates devices under this prefix, so sweeping it
490
- # can never remove one of the developer's.
491
- har_sim_delete_devices_with_prefix "$(har_sim_device_label_prefix "$agent_id")"
492
- }
@@ -1,11 +0,0 @@
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
@@ -1,68 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Tear down an agent slot for iOS mobile app repos.
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
- # shellcheck source=/dev/null
17
- source "$SCRIPT_DIR/simulator.sh"
18
-
19
- AGENT_ID="${1:?Usage: teardown.sh <agent-id> [--delete-branch]}"
20
- DELETE_BRANCH=false
21
-
22
- for arg in "${@:2}"; do
23
- case "$arg" in
24
- --delete-branch) DELETE_BRANCH=true ;;
25
- esac
26
- done
27
-
28
- validate_agent_id "$AGENT_ID"
29
-
30
- echo "==> Tearing down agent ${AGENT_ID}..."
31
-
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
- rm -f "$REPO_ROOT/.env.agent.${AGENT_ID}"
44
- if [ -n "$WORK_DIR" ] && [ -d "$WORK_DIR" ]; then
45
- rm -f "$WORK_DIR/.env.agent.${AGENT_ID}"
46
- fi
47
-
48
- if [ -d "$WORKTREE_PATH" ]; then
49
- rm -f "$WORKTREE_PATH/.env.agent.${AGENT_ID}"
50
- git -C "$REPO_ROOT" worktree remove "$WORKTREE_PATH" --force 2>/dev/null || rm -rf "$WORKTREE_PATH"
51
- echo "✓ Removed worktree: $WORKTREE_PATH"
52
- fi
53
- git -C "$REPO_ROOT" worktree prune 2>/dev/null || true
54
-
55
- if [ -n "$BRANCH" ]; then
56
- if [ "$DELETE_BRANCH" = true ]; then
57
- git -C "$REPO_ROOT" branch -D "$BRANCH" 2>/dev/null || true
58
- echo "✓ Deleted branch: $BRANCH"
59
- else
60
- echo "✓ Kept branch: $BRANCH (push it or delete with: git branch -D $BRANCH)"
61
- fi
62
- fi
63
-
64
- har_sim_release "$AGENT_ID"
65
-
66
- remove_slot_registry "$AGENT_ID"
67
-
68
- echo "✓ Agent ${AGENT_ID} torn down"