@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,479 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Shared slot helpers: agent-id validation, slot registry, work-dir resolution.
3
- # Source after harness.env (requires $SCRIPT_DIR pointing at the .har directory):
4
- # source "$SCRIPT_DIR/harness.env"
5
- # source "$SCRIPT_DIR/agent-slot.sh"
6
- # validate_agent_id "$AGENT_ID"
7
-
8
- # Canonical slot limits live in stages.json (agentSlots); harness.env is legacy fallback.
9
- har_load_agent_slot_limits() {
10
- local registry="${SCRIPT_DIR}/stages.json"
11
- if [[ -f "$registry" ]]; then
12
- local parsed
13
- parsed="$(node -e '
14
- try {
15
- const slots = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")).agentSlots;
16
- if (slots && Number.isInteger(slots.min) && Number.isInteger(slots.max)) {
17
- process.stdout.write(String(slots.min) + " " + String(slots.max));
18
- }
19
- } catch {}
20
- ' "$registry" 2>/dev/null || true)"
21
- if [[ -n "$parsed" ]]; then
22
- HARNESS_AGENT_SLOT_MIN="${parsed%% *}"
23
- HARNESS_AGENT_SLOT_MAX="${parsed#* }"
24
- export HARNESS_AGENT_SLOT_MIN HARNESS_AGENT_SLOT_MAX
25
- return 0
26
- fi
27
- fi
28
- }
29
-
30
- har_suggest_launch() {
31
- local id="$1"
32
- echo " Launch: har env launch ${id} # or har_launch_environment (MCP)" >&2
33
- echo " Fallback: ./.har/launch.sh ${id} # when har CLI/MCP unavailable" >&2
34
- }
35
-
36
- har_suggest_status() {
37
- local id="${1:-}"
38
- echo " Status: har env status # or har_get_status (MCP)" >&2
39
- if [[ -n "$id" ]]; then
40
- echo " Fallback: ./.har/agent-cli.sh ${id} status" >&2
41
- fi
42
- }
43
-
44
- validate_agent_id() {
45
- local id="${1:-}"
46
- har_load_agent_slot_limits
47
- local min="${HARNESS_AGENT_SLOT_MIN:-1}"
48
- local max="${HARNESS_AGENT_SLOT_MAX:-}"
49
-
50
- if [[ -z "$max" ]]; then
51
- echo "Error: configure agentSlots in .har/stages.json or HARNESS_AGENT_SLOT_MAX in harness.env" >&2
52
- exit 1
53
- fi
54
-
55
- if [[ -z "$id" ]] || ! [[ "$id" =~ ^[0-9]+$ ]]; then
56
- echo "Error: invalid agent slot id: ${id:-"(missing)"}" >&2
57
- echo " Must be an integer between ${min} and ${max} (see .har/stages.json → agentSlots)" >&2
58
- exit 1
59
- fi
60
-
61
- if (( id < min || id > max )); then
62
- echo "Error: invalid agent slot id: ${id}" >&2
63
- echo " Valid slots: ${min}–${max} (configured in .har/stages.json → agentSlots)" >&2
64
- echo " Run: har env status # see which slots are in use" >&2
65
- if (( id > max )); then
66
- echo " To allow slot ${id}, raise agentSlots.max in .har/stages.json." >&2
67
- fi
68
- exit 1
69
- fi
70
- }
71
-
72
- # ── Slot registry ─────────────────────────────────────────────────────────────
73
- # .har/slots/agent-<id>.json is the source of truth for where a session lives
74
- # (worktree path, work dir, branch, base commit). Written by launch.sh, removed
75
- # by teardown.sh. Location resolution must go through it — worktree paths carry
76
- # a random per-session suffix and cannot be derived from the agent id alone.
77
-
78
- slot_registry_file() {
79
- echo "${SCRIPT_DIR}/slots/agent-${1}.json"
80
- }
81
-
82
- # read_slot_field <registry-file> <field> — echoes the scalar value, empty if absent.
83
- read_slot_field() {
84
- [ -f "${1:-}" ] || return 1
85
- node -e '
86
- try {
87
- const v = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"))[process.argv[2]];
88
- if (v != null && typeof v !== "object") process.stdout.write(String(v));
89
- } catch {}
90
- ' "$1" "$2"
91
- }
92
-
93
- # Writes the registry entry from SLOT_* variables:
94
- # required: SLOT_AGENT_ID, SLOT_MODE (worktree|root), SLOT_WORK_DIR
95
- # optional: SLOT_SUFFIX, SLOT_WORKTREE_PATH, SLOT_BRANCH, SLOT_BASE_BRANCH,
96
- # SLOT_BASE_COMMIT, SLOT_PORTS_JSON, SLOT_PREVIEW_URLS_JSON,
97
- # SLOT_STATUS, SLOT_LAST_ERROR, SLOT_WORK_UNIT_ID, SLOT_ATTEMPT_ID
98
- # Mission Control re-reads slot state only when something syncs, and a sync takes
99
- # about twenty seconds — too long to make a slot change wait for it. Detached, and
100
- # coalesced so a run of slot changes leaves one sync going, then one more if needed.
101
- har_notify_control() {
102
- local root bin lock
103
- root="$(cd "${SCRIPT_DIR}/.." && pwd)"
104
- bin="$(command -v har 2>/dev/null || echo "${root}/node_modules/.bin/har")"
105
- [ -x "$bin" ] || return 0
106
-
107
- lock="${TMPDIR:-/tmp}/har-control-sync.$(id -u)"
108
- : > "${lock}.pending"
109
- mkdir "$lock" 2>/dev/null || return 0
110
- (
111
- trap "" HUP
112
- trap 'rmdir "$lock" 2>/dev/null || true' EXIT
113
- cd "$root" || exit 0
114
- while [ -f "${lock}.pending" ]; do
115
- rm -f "${lock}.pending"
116
- if command -v timeout >/dev/null 2>&1; then
117
- timeout 120 "$bin" control sync || true
118
- else
119
- "$bin" control sync || true
120
- fi
121
- done
122
- # Redirected as a whole, not per command: a background job that keeps the inherited
123
- # stdout open makes any caller reading that pipe wait for it.
124
- ) >/dev/null 2>&1 &
125
- }
126
-
127
- write_slot_registry() {
128
- local file
129
- file="$(slot_registry_file "$SLOT_AGENT_ID")"
130
- mkdir -p "$(dirname "$file")"
131
- node -e '
132
- const fs = require("fs");
133
- const e = process.env;
134
- const entry = {
135
- version: 1,
136
- agentId: Number(e.SLOT_AGENT_ID),
137
- projectName: e.HARNESS_PROJECT_NAME || "",
138
- mode: e.SLOT_MODE,
139
- workDir: e.SLOT_WORK_DIR,
140
- createdAt: new Date().toISOString(),
141
- status: e.SLOT_STATUS || "active",
142
- };
143
- if (e.SLOT_SUFFIX) entry.suffix = e.SLOT_SUFFIX;
144
- if (e.SLOT_WORKTREE_PATH) entry.worktreePath = e.SLOT_WORKTREE_PATH;
145
- if (e.SLOT_BRANCH) entry.branch = e.SLOT_BRANCH;
146
- if (e.SLOT_BASE_BRANCH) entry.baseBranch = e.SLOT_BASE_BRANCH;
147
- if (e.SLOT_BASE_COMMIT) entry.baseCommit = e.SLOT_BASE_COMMIT;
148
- if (e.SLOT_LAST_ERROR) entry.lastError = e.SLOT_LAST_ERROR;
149
- if (e.SLOT_WORK_UNIT_ID) entry.workUnitId = e.SLOT_WORK_UNIT_ID;
150
- if (e.SLOT_ATTEMPT_ID) entry.attemptId = e.SLOT_ATTEMPT_ID;
151
- for (const [key, env] of [["ports", "SLOT_PORTS_JSON"], ["previewUrls", "SLOT_PREVIEW_URLS_JSON"]]) {
152
- if (e[env]) try { entry[key] = JSON.parse(e[env]); } catch {}
153
- }
154
- fs.writeFileSync(process.argv[1], JSON.stringify(entry, null, 2) + "\n");
155
- ' "$file"
156
- har_notify_control
157
- }
158
-
159
- remove_slot_registry() {
160
- rm -f "$(slot_registry_file "$1")"
161
- har_notify_control
162
- }
163
-
164
- # Exit 0 when the worktree has uncommitted or untracked changes.
165
- slot_worktree_dirty() {
166
- [ -d "${1:-}" ] || return 1
167
- [ -n "$(git -C "$1" status --porcelain 2>/dev/null)" ]
168
- }
169
-
170
- # Exit 0 when a slot registry entry or worktree path exists for this agent id.
171
- slot_is_occupied() {
172
- local agent_id="$1"
173
- [ -f "$(slot_registry_file "$agent_id")" ] || [ -n "$(existing_slot_worktree "$agent_id")" ]
174
- }
175
-
176
- # Echo "clean" or "dirty (N changed)" for a worktree path.
177
- slot_dirty_summary() {
178
- local wt="${1:-}"
179
- if [ -z "$wt" ] || [ ! -d "$wt" ]; then
180
- echo "unknown"
181
- return 0
182
- fi
183
- if ! slot_worktree_dirty "$wt"; then
184
- echo "clean"
185
- return 0
186
- fi
187
- local count
188
- count="$(git -C "$wt" status --porcelain 2>/dev/null | wc -l | tr -d ' ')"
189
- echo "dirty (${count} changed)"
190
- }
191
-
192
- # Print details about an occupied slot (stdout — visible to agents).
193
- print_slot_occupied_warning() {
194
- local agent_id="$1"
195
- local reg wt branch work_dir created status last_error dirty_summary head
196
- reg="$(slot_registry_file "$agent_id")"
197
- wt="$(existing_slot_worktree "$agent_id")"
198
- branch="$(read_slot_field "$reg" branch || true)"
199
- work_dir="$(read_slot_field "$reg" workDir || true)"
200
- created="$(read_slot_field "$reg" createdAt || true)"
201
- status="$(read_slot_field "$reg" status || true)"
202
- last_error="$(read_slot_field "$reg" lastError || true)"
203
- dirty_summary="$(slot_dirty_summary "$wt")"
204
- if [ -n "$wt" ] && [ -d "$wt" ]; then
205
- head="$(git -C "$wt" rev-parse --short HEAD 2>/dev/null || true)"
206
- fi
207
-
208
- echo "" >&2
209
- echo "⚠ Slot ${agent_id} is already in use." >&2
210
- [ -n "$wt" ] && echo " Worktree: ${wt}" >&2
211
- [ -n "$work_dir" ] && echo " Work dir: ${work_dir}" >&2
212
- [ -n "$branch" ] && echo " Branch: ${branch}${head:+ @ ${head}}" >&2
213
- [ -n "$status" ] && echo " Status: ${status}" >&2
214
- [ -n "$last_error" ] && echo " Error: ${last_error}" >&2
215
- [ -n "$created" ] && echo " Since: ${created}" >&2
216
- echo " Git: ${dirty_summary}" >&2
217
- echo "" >&2
218
- local base_root="${HARNESS_ROOT:-${REPO_ROOT:-}}"
219
- if [ -n "$base_root" ] && [ -d "$base_root" ]; then
220
- local base_branch base_sha
221
- base_branch="$(git -C "$base_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo detached)"
222
- base_sha="$(git -C "$base_root" rev-parse --short HEAD 2>/dev/null || true)"
223
- echo " New session will be based on: ${base_branch}${base_sha:+ @ ${base_sha}}" >&2
224
- echo " (HEAD of ${base_root})" >&2
225
- echo " Switch that checkout to your intended base before launching a new session." >&2
226
- echo "" >&2
227
- fi
228
- if [ "$dirty_summary" != "clean" ] && [ "$dirty_summary" != "unknown" ]; then
229
- echo " Commit or discard changes in the worktree, then teardown/complete the slot:" >&2
230
- else
231
- echo " Free the slot, then launch again:" >&2
232
- fi
233
- echo " har env complete ${agent_id} # or: har env teardown ${agent_id}" >&2
234
- echo " har env launch ${agent_id}" >&2
235
- echo "" >&2
236
- echo " complete/teardown remove the worktree. The session branch is kept only if" >&2
237
- echo " you committed. Gitignored paths (state/, runs/, local clones, .env.local)" >&2
238
- echo " are NOT preserved." >&2
239
- echo "" >&2
240
- }
241
-
242
- # Untracked (not ignored) paths stay in the main checkout when a session
243
- # worktree is created from HEAD. Advisory — never fails the launch.
244
- har_warn_untracked_worktree() {
245
- case "${USE_WORKTREE:-${HARNESS_USE_WORKTREE:-true}}" in
246
- true|TRUE|yes|YES|1) ;;
247
- *) return 0 ;;
248
- esac
249
- local root="${REPO_ROOT:-}"
250
- if [ -z "$root" ] && [ -n "${SCRIPT_DIR:-}" ]; then
251
- root="$(cd "$SCRIPT_DIR/.." && pwd)"
252
- fi
253
- [ -n "$root" ] || return 0
254
- git -C "$root" rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 0
255
-
256
- local paths=()
257
- local entry
258
- while IFS= read -r -d '' entry; do
259
- paths+=("$entry")
260
- done < <(git -C "$root" --literal-pathspecs ls-files --others --exclude-standard --directory -z 2>/dev/null || true)
261
-
262
- local count="${#paths[@]}"
263
- [ "$count" -gt 0 ] || return 0
264
-
265
- local max=8
266
- local listed=""
267
- local i
268
- for ((i = 0; i < count && i < max; i++)); do
269
- [ -n "$listed" ] && listed+=", "
270
- listed+="${paths[$i]}"
271
- done
272
- if [ "$count" -gt "$max" ]; then
273
- listed+=" (+$((count - max)) more)"
274
- fi
275
-
276
- local noun="paths"
277
- local them="They are"
278
- if [ "$count" -eq 1 ]; then
279
- noun="path"
280
- them="It is"
281
- fi
282
- echo "WARN: ${count} untracked ${noun} will not appear in the session worktree: ${listed}. ${them} only in the main checkout — track them, or launch with --no-worktree." >&2
283
- }
284
-
285
- git_common_dir() {
286
- local cwd="$1"
287
- local out
288
- out="$(git -C "$cwd" rev-parse --git-common-dir 2>/dev/null)" || return 1
289
- case "$out" in
290
- /*) echo "$out" ;;
291
- *) (cd "$cwd" && cd "$out" && pwd) ;;
292
- esac
293
- }
294
-
295
- same_git_checkout() {
296
- local left right
297
- left="$(git_common_dir "$1" || true)"
298
- right="$(git_common_dir "$2" || true)"
299
- [ -n "$left" ] && [ -n "$right" ] && [ "$left" = "$right" ]
300
- }
301
-
302
- # Echo the previous session's worktree path for a slot: registry first, then
303
- # legacy and randomized session worktree fallbacks. Empty output when none exists.
304
- existing_slot_worktree() {
305
- local agent_id="$1"
306
- local reg path candidate repo_root rel_prefix
307
- repo_root="$(cd "$SCRIPT_DIR/.." && pwd)"
308
- rel_prefix="$(git -C "$repo_root" rev-parse --show-prefix 2>/dev/null || true)"
309
- reg="$(slot_registry_file "$agent_id")"
310
- if [ -f "$reg" ]; then
311
- path="$(read_slot_field "$reg" worktreePath || true)"
312
- if [ -n "$path" ] && [ -d "$path" ]; then
313
- echo "$path"
314
- return 0
315
- fi
316
- fi
317
- path="$HOME/worktrees/${HARNESS_PROJECT_NAME}-agent-${agent_id}"
318
- if [ -d "$path" ]; then
319
- echo "$path"
320
- return 0
321
- fi
322
- for candidate in "$HOME"/worktrees/*-har-agent-"${agent_id}"-*; do
323
- [ -d "$candidate" ] || continue
324
- same_git_checkout "$repo_root" "$candidate" || continue
325
- [ -f "$candidate/${rel_prefix}.env.agent.${agent_id}" ] || continue
326
- echo "$candidate"
327
- return 0
328
- done
329
- return 0
330
- }
331
-
332
- # Resolve .env.agent.<id> — registry work dir first, then legacy and
333
- # randomized session worktree fallbacks.
334
- resolve_agent_env_file() {
335
- local agent_id="$1"
336
- local repo_root="$2"
337
- local reg work_dir
338
- reg="$(slot_registry_file "$agent_id")"
339
- if [ -f "$reg" ]; then
340
- work_dir="$(read_slot_field "$reg" workDir || true)"
341
- if [ -n "$work_dir" ] && [ -f "$work_dir/.env.agent.${agent_id}" ]; then
342
- echo "$work_dir/.env.agent.${agent_id}"
343
- return 0
344
- fi
345
- fi
346
- # Worktrees are repo-rooted — if the project lives in a subdirectory (monorepo),
347
- # the env file sits under that prefix inside the worktree.
348
- local rel_prefix
349
- rel_prefix="$(git -C "$repo_root" rev-parse --show-prefix 2>/dev/null || true)"
350
- local candidate
351
- for candidate in \
352
- "$repo_root/.env.agent.${agent_id}" \
353
- "$HOME/worktrees/${HARNESS_PROJECT_NAME}-agent-${agent_id}/${rel_prefix}.env.agent.${agent_id}"; do
354
- if [ -f "$candidate" ]; then
355
- echo "$candidate"
356
- return 0
357
- fi
358
- done
359
- local candidate_dir
360
- for candidate in "$HOME"/worktrees/*-har-agent-"${agent_id}"-*/${rel_prefix}.env.agent."${agent_id}"; do
361
- if [ -f "$candidate" ]; then
362
- candidate_dir="$(cd "$(dirname "$candidate")" && pwd)"
363
- same_git_checkout "$repo_root" "$candidate_dir" || continue
364
- echo "$candidate"
365
- return 0
366
- fi
367
- done
368
- return 1
369
- }
370
-
371
- # Resolve work dir for verify/e2e — registry first, else the agent env file's
372
- # variables (call after sourcing the env file). Optional 2nd arg: agent id.
373
- resolve_agent_work_dir() {
374
- local env_file="$1"
375
- local agent_id="${2:-${AGENT_ID:-}}"
376
- if [ -n "$agent_id" ]; then
377
- local reg wd
378
- reg="$(slot_registry_file "$agent_id")"
379
- if [ -f "$reg" ]; then
380
- wd="$(read_slot_field "$reg" workDir || true)"
381
- if [ -n "$wd" ] && [ -d "$wd" ]; then
382
- echo "$wd"
383
- return 0
384
- fi
385
- fi
386
- fi
387
- local work_dir="${REPO_ROOT:-}"
388
- if [ -z "$work_dir" ] || [ ! -d "$work_dir" ]; then
389
- if [ -n "${WORKTREE_DIR:-}" ] && [ -d "$WORKTREE_DIR" ]; then
390
- work_dir="$WORKTREE_DIR"
391
- else
392
- work_dir="$(cd "$(dirname "$env_file")" && pwd)"
393
- fi
394
- fi
395
- echo "$work_dir"
396
- }
397
-
398
- # Portable millisecond clock (GNU date %N is unavailable on macOS/BSD).
399
- now_ms() {
400
- node -e 'process.stdout.write(String(Date.now()))' 2>/dev/null || echo 0
401
- }
402
-
403
- # JSON-escape step output; truncate to 50 lines in node (avoids SIGPIPE under pipefail).
404
- escape_step_output() {
405
- printf '%s' "$1" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const s=d.trim().split('\n').slice(0,50).join('\n');process.stdout.write(JSON.stringify(s))})" 2>/dev/null || echo '""'
406
- }
407
-
408
- # Append one verify.sh step to RESULTS_JSON (caller global).
409
- # Passing steps omit output — it already appeared as ✓ on stderr.
410
- record_step_result() {
411
- local name="$1"
412
- local pass_bool="$2"
413
- local elapsed="$3"
414
- local output="${4:-}"
415
- local step_json
416
- if [ "$pass_bool" = "true" ]; then
417
- step_json=$(NAME="$name" MS="$elapsed" node -e 'process.stdout.write(JSON.stringify({name:process.env.NAME,pass:true,ms:Number(process.env.MS)}))' 2>/dev/null) || return 0
418
- else
419
- local escaped
420
- escaped=$(escape_step_output "$output")
421
- step_json=$(NAME="$name" MS="$elapsed" node -e "process.stdout.write(JSON.stringify({name:process.env.NAME,pass:false,ms:Number(process.env.MS),output:$escaped}))" 2>/dev/null) || return 0
422
- fi
423
- RESULTS_JSON=$(echo "$RESULTS_JSON" | node -e "
424
- const fs = require('fs');
425
- const arr = JSON.parse(fs.readFileSync('/dev/stdin','utf8'));
426
- arr.push($step_json);
427
- process.stdout.write(JSON.stringify(arr));
428
- " 2>/dev/null || echo "$RESULTS_JSON")
429
- }
430
-
431
- # Emit "<id>\t<command>" for each registered stage listed in stages.json
432
- # verificationStages (used by verify --full). Ids without a matching registered
433
- # stage are inline verify.sh steps and skipped here; lifecycle stages
434
- # (setup/launch/reset/teardown/inspect) and the verify stage itself never run.
435
- # Authoring contract: .har/STAGES.md
436
- list_registered_verification_stage_commands() {
437
- local script_dir="$1"
438
- local agent_id="$2"
439
- local registry="$script_dir/stages.json"
440
- [ -f "$registry" ] || return 0
441
- HAR_STAGE_REGISTRY="$registry" HAR_SCRIPT_DIR="$script_dir" HAR_AGENT_ID="$agent_id" node <<'NODE' 2>/dev/null || true
442
- const fs = require('fs');
443
- const { HAR_STAGE_REGISTRY, HAR_SCRIPT_DIR, HAR_AGENT_ID } = process.env;
444
- const shq = (s) => "'" + String(s).replace(/'/g, "'\\''") + "'";
445
- let reg;
446
- try { reg = JSON.parse(fs.readFileSync(HAR_STAGE_REGISTRY, 'utf8')); } catch { process.exit(0); }
447
- const ids = Array.isArray(reg.verificationStages) ? reg.verificationStages : [];
448
- const stages = Array.isArray(reg.stages) ? reg.stages : [];
449
- const runnable = new Set(['test', 'custom']);
450
- for (const id of ids) {
451
- const stage = stages.find((s) => s && s.id === id);
452
- if (!stage || stage.id === 'verify' || !runnable.has(stage.kind)) continue;
453
- const needsAgent = stage.requiresAgentId !== false;
454
- let cmd;
455
- if (stage.script) {
456
- cmd = shq(HAR_SCRIPT_DIR + '/' + stage.script) + (needsAgent ? ' ' + shq(HAR_AGENT_ID) : '');
457
- } else if (stage.command) {
458
- cmd = stage.command.split('{agentId}').join(HAR_AGENT_ID);
459
- } else {
460
- continue;
461
- }
462
- if (stage.cwd) cmd = 'cd ' + shq(stage.cwd) + ' && ' + cmd;
463
- const env = stage.env && typeof stage.env === 'object' ? stage.env : {};
464
- const prefix = Object.entries(env).map(([k, v]) => k + '=' + shq(v)).join(' ');
465
- process.stdout.write(id + '\t' + (prefix ? prefix + ' ' : '') + cmd + '\n');
466
- }
467
- NODE
468
- }
469
-
470
- # Optional project-owned "agent usable" smoke beyond health.
471
- run_readiness_if_configured() {
472
- local agent_id="$1"
473
- if [ -z "${HARNESS_READINESS_CMD:-}" ]; then
474
- echo "No HARNESS_READINESS_CMD configured; skipping readiness smoke."
475
- return 0
476
- fi
477
- local cmd="${HARNESS_READINESS_CMD//\{agentId\}/$agent_id}"
478
- eval "$cmd"
479
- }
@@ -1,206 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Launch an agent slot for iOS mobile app repos (git worktree by default).
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
- # Occupied slots always block: run ./.har/teardown.sh (or complete it) first, then relaunch.
6
- #
7
- # Usage: ./.har/launch.sh <agent-id> [--no-worktree]
8
- set -euo pipefail
9
-
10
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
- REPO_ROOT="$(cd "$SCRIPT_DIR/.." && 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
- AGENT_ID="${1:-}"
21
- USE_WORKTREE="${HARNESS_USE_WORKTREE:-true}"
22
- WORK_UNIT_ID="${HAR_WORK_UNIT_ID:-}"
23
- ATTEMPT_ID="${HAR_ATTEMPT_ID:-}"
24
-
25
- for arg in "$@"; do
26
- case "$arg" in
27
- --no-worktree) USE_WORKTREE=false ;;
28
- --worktree) USE_WORKTREE=true ;;
29
- --work-id=*) WORK_UNIT_ID="${arg#*=}" ;;
30
- --attempt-id=*) ATTEMPT_ID="${arg#*=}" ;;
31
- esac
32
- done
33
-
34
- if [[ -n "$WORK_UNIT_ID" && -z "$ATTEMPT_ID" ]]; then
35
- ATTEMPT_ID="$(node -e 'process.stdout.write(require("crypto").randomUUID())')"
36
- fi
37
- export SLOT_WORK_UNIT_ID="$WORK_UNIT_ID"
38
- export SLOT_ATTEMPT_ID="$ATTEMPT_ID"
39
-
40
- if [[ -z "$AGENT_ID" ]]; then
41
- har_load_agent_slot_limits
42
- echo "Usage: $0 <agent-id> [--no-worktree]" >&2
43
- echo " agent-id must be between ${HARNESS_AGENT_SLOT_MIN} and ${HARNESS_AGENT_SLOT_MAX}" >&2
44
- exit 1
45
- fi
46
-
47
- validate_agent_id "$AGENT_ID"
48
-
49
- log() { echo "==> [agent-$AGENT_ID] $*" >&2; }
50
-
51
- # Occupied slots always block a new launch — teardown/complete first, then relaunch.
52
- if slot_is_occupied "$AGENT_ID"; then
53
- print_slot_occupied_warning "$AGENT_ID"
54
- echo "ERROR: slot ${AGENT_ID} is occupied." >&2
55
- echo " Free it first: har env teardown ${AGENT_ID} (or complete ${AGENT_ID}), then har env launch ${AGENT_ID}." >&2
56
- exit 2
57
- fi
58
-
59
- har_warn_untracked_worktree
60
-
61
- "$SCRIPT_DIR/setup-infra.sh"
62
-
63
- # Session worktree (default — use --no-worktree to work in repo root).
64
- # Name encodes what the session is based on: <base-branch>-<sha4>-har-agent-<id>-<rand4>.
65
- WORK_DIR="$REPO_ROOT"
66
- WORKTREE_DIR=""
67
- BRANCH=""
68
- SUFFIX=""
69
- BASE_BRANCH="$(git -C "$REPO_ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "detached")"
70
- BASE_COMMIT="$(git -C "$REPO_ROOT" rev-parse HEAD 2>/dev/null || true)"
71
- if [ "$USE_WORKTREE" = true ]; then
72
- SHORT_SHA="$(git -C "$REPO_ROOT" rev-parse --short=4 HEAD)"
73
- SUFFIX="$(LC_ALL=C tr -dc 'a-z0-9' </dev/urandom 2>/dev/null | head -c 4 || true)"
74
- [ -n "$SUFFIX" ] || SUFFIX="$(printf '%04d' $(( RANDOM % 10000 )))"
75
- SESSION_NAME="${BASE_BRANCH//\//-}-${SHORT_SHA}-har-agent-${AGENT_ID}-${SUFFIX}"
76
- BRANCH="$SESSION_NAME"
77
- WORKTREE_DIR="$HOME/worktrees/${SESSION_NAME}"
78
- log "Creating session worktree at $WORKTREE_DIR (branch $BRANCH)..."
79
- git -C "$REPO_ROOT" worktree add "$WORKTREE_DIR" -b "$BRANCH"
80
- # Worktrees are always rooted at the git repo — if this project lives in a
81
- # subdirectory (monorepo), point WORK_DIR at the project inside the worktree.
82
- REL_PREFIX="$(git -C "$REPO_ROOT" rev-parse --show-prefix 2>/dev/null || true)"
83
- WORK_DIR="${WORKTREE_DIR%/}/${REL_PREFIX}"
84
- WORK_DIR="${WORK_DIR%/}"
85
- else
86
- log "Using repo root (worktree disabled)"
87
- fi
88
-
89
- # Keep harness-generated files out of accidental `git add -A` commits in repos
90
- # whose .gitignore doesn't know about har (applies to all worktrees too).
91
- GIT_EXCLUDE="$(git -C "$REPO_ROOT" rev-parse --git-common-dir 2>/dev/null)/info/exclude"
92
- if [ -n "$GIT_EXCLUDE" ] && [ -d "$(dirname "$GIT_EXCLUDE")" ]; then
93
- # .har/simulators covers harnesses scaffolded before it reached
94
- # gitignore.template, which only lands on init or maintain.
95
- for pattern in '.env.agent.*' 'ecosystem.agent.*.config.cjs' '.har/venv' '.har/simulators'; do
96
- grep -qxF "$pattern" "$GIT_EXCLUDE" 2>/dev/null || echo "$pattern" >> "$GIT_EXCLUDE"
97
- done
98
- fi
99
-
100
- ENV_FILE="$WORK_DIR/.env.agent.${AGENT_ID}"
101
- log "Generating $ENV_FILE..."
102
- cat > "$ENV_FILE" <<EOF
103
- # Agent environment — generated by launch.sh
104
- AGENT_ID=${AGENT_ID}
105
- REPO_ROOT=${WORK_DIR}
106
- WORKTREE_DIR=${WORKTREE_DIR}
107
- NODE_ENV=test
108
- EOF
109
-
110
- REGISTRY_WRITTEN=false
111
- mark_slot_failed() {
112
- local exit_code="$?"
113
- if [ "$exit_code" != "0" ] && [ "$REGISTRY_WRITTEN" = true ]; then
114
- log "Launch failed after creating the session. Recording failed slot state..."
115
- set +e
116
- har_sim_release "$AGENT_ID"
117
- SLOT_AGENT_ID="$AGENT_ID" \
118
- SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
119
- SLOT_WORK_DIR="$WORK_DIR" \
120
- SLOT_SUFFIX="${SUFFIX:-}" \
121
- SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
122
- SLOT_BRANCH="${BRANCH:-}" \
123
- SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
124
- SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
125
- SLOT_STATUS="failed" \
126
- SLOT_LAST_ERROR="launch.sh exited with code ${exit_code}" \
127
- write_slot_registry
128
- log " Work dir: ${WORK_DIR}"
129
- log " Env file: ${ENV_FILE}"
130
- log " Recovery: fix the failure, then: har env teardown ${AGENT_ID} (or complete), then har env launch ${AGENT_ID}."
131
- fi
132
- }
133
- trap mark_slot_failed EXIT
134
-
135
- # Record the session before slow or fragile setup so verify/status/teardown can
136
- # recover partial launches that already created a worktree and env file.
137
- SLOT_AGENT_ID="$AGENT_ID" \
138
- SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
139
- SLOT_WORK_DIR="$WORK_DIR" \
140
- SLOT_SUFFIX="${SUFFIX:-}" \
141
- SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
142
- SLOT_BRANCH="${BRANCH:-}" \
143
- SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
144
- SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
145
- SLOT_STATUS="starting" \
146
- write_slot_registry
147
- REGISTRY_WRITTEN=true
148
-
149
- log "Provisioning toolchain (see harness.env: HARNESS_ECOSYSTEM, HARNESS_INSTALL_CMD)..."
150
- HAR_WORK_DIR="$WORK_DIR" \
151
- HAR_ENV_FILE="$ENV_FILE" \
152
- HAR_WORKTREE_DIR="${WORKTREE_DIR:-}" \
153
- HAR_REL_PREFIX="${REL_PREFIX:-}" \
154
- HAR_AGENT_ID="$AGENT_ID" \
155
- "$SCRIPT_DIR/provision-toolchain.sh"
156
-
157
- # Give this slot its own simulator. Written after provision-toolchain.sh so the
158
- # per-slot device wins over the shared default when verify.sh sources the file.
159
- if har_sim_per_slot_enabled; then
160
- log "Preparing this slot's simulator..."
161
- SIM_RESULT=""
162
- SIM_RESULT="$(har_sim_acquire "$AGENT_ID")" || {
163
- log "Could not prepare a simulator — see the messages above."
164
- exit 1
165
- }
166
- SIM_UDID="${SIM_RESULT%%$'\t'*}"
167
- SIM_NAME="${SIM_RESULT#*$'\t'}"
168
- # The device name goes under its own key: HARNESS_SIMULATOR_NAME means "model
169
- # to create" in harness.env, and overwriting it here would make its meaning
170
- # depend on which file was sourced last.
171
- {
172
- printf 'HARNESS_SIMULATOR_UDID=%q\n' "$SIM_UDID"
173
- printf 'HARNESS_SIMULATOR_DEVICE_NAME=%q\n' "$SIM_NAME"
174
- printf 'HARNESS_IOS_DESTINATION=%q\n' "platform=iOS Simulator,id=${SIM_UDID}"
175
- } >> "$ENV_FILE"
176
- log "Simulator: ${SIM_NAME} (${SIM_UDID})"
177
- else
178
- log "Shared simulator mode (HARNESS_SIMULATOR_SHARED=true) — slots share ${HARNESS_SIMULATOR_NAME:-the configured device}."
179
- fi
180
-
181
- # Record the session in the slot registry — the source of truth for where
182
- # this slot's code lives (status/verify/teardown resolve through it).
183
- SLOT_AGENT_ID="$AGENT_ID" \
184
- SLOT_MODE="$([ "$USE_WORKTREE" = true ] && echo worktree || echo root)" \
185
- SLOT_WORK_DIR="$WORK_DIR" \
186
- SLOT_SUFFIX="${SUFFIX:-}" \
187
- SLOT_WORKTREE_PATH="${WORKTREE_DIR:-}" \
188
- SLOT_BRANCH="${BRANCH:-}" \
189
- SLOT_BASE_BRANCH="${BASE_BRANCH:-}" \
190
- SLOT_BASE_COMMIT="${BASE_COMMIT:-}" \
191
- SLOT_STATUS="active" \
192
- write_slot_registry
193
-
194
- log "Agent $AGENT_ID is ready."
195
- log ""
196
- log " WORK DIR (make ALL file edits under this path — never the main checkout):"
197
- log " ${WORK_DIR}"
198
- if [ "$USE_WORKTREE" = true ]; then
199
- log " Branch: ${BRANCH} (based on ${BASE_BRANCH} @ ${BASE_COMMIT})"
200
- fi
201
- if [ -n "${SIM_NAME:-}" ]; then
202
- log " Simulator: ${SIM_NAME} (${SIM_UDID})"
203
- fi
204
- log ""
205
- log " Verify: ./.har/verify.sh $AGENT_ID"
206
- log " Teardown: ./.har/teardown.sh $AGENT_ID (keeps the branch)"