@osfactory/har 0.64.2 → 1.0.0-rc.2

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 (142) hide show
  1. package/dist/index.js +15751 -6548
  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/adaptation-prompt-plugin.md +40 -0
  6. package/dist/templates/agent-skills/har-maintain.md +2 -2
  7. package/dist/templates/agent-skills/setup-har.md +6 -4
  8. package/dist/templates/cursor-rule.mdc.template +3 -3
  9. package/dist/templates/har-boilerplate/docker-compose.agent.yml +1 -1
  10. package/dist/templates/har-boilerplate/harness.env +11 -155
  11. package/dist/templates/har-boilerplate/stages.json +51 -2
  12. package/dist/templates/har-boilerplate-cli/docker-compose.agent.yml +1 -1
  13. package/dist/templates/har-boilerplate-cli/harness.env +12 -138
  14. package/dist/templates/har-boilerplate-cli/stages.json +42 -2
  15. package/dist/templates/har-boilerplate-ios/harness.env +10 -126
  16. package/dist/templates/har-boilerplate-ios/stages.json +42 -2
  17. package/dist/templates/migration-prompt.md +54 -0
  18. package/dist/templates/plugins/custom-stage-skeleton.sh +11 -20
  19. package/dist/templates/plugins/gitleaks/.github/workflows/gitleaks.yml +1 -1
  20. package/dist/templates/plugins/gitleaks/.har/stages/GITLEAKS.md +1 -1
  21. package/dist/templates/plugins/gitleaks/.har/stages/secrets-scan.sh +8 -23
  22. package/dist/templates/plugins/gitleaks/README.md +1 -1
  23. package/dist/templates/plugins/gitleaks/template.manifest.json +20 -5
  24. package/dist/templates/plugins/kerno/.github/workflows/kerno.yml +1 -1
  25. package/dist/templates/plugins/kerno/.har/stages/backend-validation.sh +8 -23
  26. package/dist/templates/plugins/kerno/README.md +1 -1
  27. package/dist/templates/plugins/kerno/template.manifest.json +20 -9
  28. package/dist/templates/plugins/local-plugin-skeleton/README.md +46 -0
  29. package/dist/templates/plugins/playwright/.har/stages/browser-e2e.sh +8 -23
  30. package/dist/templates/plugins/playwright/template.manifest.json +36 -13
  31. package/dist/templates/plugins/rocketsim/.har/stages/rocketsim-flows.sh +8 -23
  32. package/dist/templates/plugins/rocketsim/template.manifest.json +20 -8
  33. package/dist/templates/plugins/semgrep/.har/stages/sast.sh +10 -26
  34. package/dist/templates/plugins/semgrep/README.md +1 -1
  35. package/dist/templates/plugins/semgrep/template.manifest.json +16 -7
  36. package/dist/templates/plugins/trivy/.har/stages/vuln-scan.sh +8 -23
  37. package/dist/templates/plugins/trivy/README.md +1 -1
  38. package/dist/templates/plugins/trivy/template.manifest.json +20 -8
  39. package/dist/templates/{har-boilerplate-cli/CLAUDE.agent.md → profiles/cli/docs/claude-agent/definition-of-done.md} +0 -42
  40. package/dist/templates/profiles/cli/docs/claude-agent/do-not.md +6 -0
  41. package/dist/templates/profiles/cli/docs/claude-agent/environment.md +12 -0
  42. package/dist/templates/profiles/cli/docs/claude-agent/project-commands.md +10 -0
  43. package/dist/templates/profiles/cli/docs/claude-agent/readiness.md +6 -0
  44. package/dist/templates/profiles/cli/docs/readme/for-coding-agents.md +9 -0
  45. package/dist/templates/profiles/cli/docs/readme/intro.md +7 -0
  46. package/dist/templates/profiles/cli/docs/readme/port-shared-services-cli-profile.md +25 -0
  47. package/dist/templates/profiles/cli/docs/readme/quick-start.md +24 -0
  48. package/dist/templates/profiles/cli/docs/readme/verification-contract.md +22 -0
  49. package/dist/templates/profiles/cli/docs/readme/what-s-in-here.md +27 -0
  50. package/dist/templates/profiles/cli/profile.manifest.json +56 -1
  51. package/dist/templates/profiles/default/docs/claude-agent/definition-of-done.md +24 -0
  52. package/dist/templates/profiles/default/docs/claude-agent/do-not.md +10 -0
  53. package/dist/templates/profiles/default/docs/claude-agent/environment.md +19 -0
  54. package/dist/templates/profiles/default/docs/claude-agent/project-commands.md +10 -0
  55. package/dist/templates/profiles/default/docs/claude-agent/readiness.md +12 -0
  56. package/dist/templates/profiles/default/docs/readme/architecture.md +11 -0
  57. package/dist/templates/profiles/default/docs/readme/for-coding-agents.md +9 -0
  58. package/dist/templates/profiles/default/docs/readme/intro.md +7 -0
  59. package/dist/templates/profiles/default/docs/readme/port-shared-services.md +41 -0
  60. package/dist/templates/profiles/default/docs/readme/quick-start.md +24 -0
  61. package/dist/templates/profiles/default/docs/readme/readiness-layers.md +16 -0
  62. package/dist/templates/profiles/default/docs/readme/verification-contract.md +17 -0
  63. package/dist/templates/profiles/default/docs/readme/what-s-in-here.md +28 -0
  64. package/dist/templates/profiles/default/profile.manifest.json +62 -1
  65. package/dist/templates/profiles/ios/docs/claude-agent/definition-of-done.md +23 -0
  66. package/dist/templates/profiles/ios/docs/claude-agent/do-not.md +6 -0
  67. package/dist/templates/profiles/ios/docs/claude-agent/environment.md +14 -0
  68. package/dist/templates/profiles/ios/docs/claude-agent/intro.md +3 -0
  69. package/dist/templates/profiles/ios/docs/claude-agent/project-commands.md +21 -0
  70. package/dist/templates/profiles/ios/docs/claude-agent/readiness.md +6 -0
  71. package/dist/templates/profiles/ios/docs/claude-agent/user-flow-validation-rocketsim.md +11 -0
  72. package/dist/templates/profiles/ios/docs/readme/configuration.md +7 -0
  73. package/dist/templates/profiles/ios/docs/readme/intro.md +7 -0
  74. package/dist/templates/profiles/ios/docs/readme/port-shared-services-ios-profile.md +25 -0
  75. package/dist/templates/profiles/ios/docs/readme/quick-start.md +22 -0
  76. package/dist/templates/profiles/ios/docs/readme/simulators.md +45 -0
  77. package/dist/templates/profiles/ios/docs/readme/user-flow-validation.md +9 -0
  78. package/dist/templates/profiles/ios/docs/readme/verification-contract.md +13 -0
  79. package/dist/templates/profiles/ios/docs/readme/what-s-in-here.md +27 -0
  80. package/dist/templates/profiles/ios/profile.manifest.json +59 -3
  81. package/dist/templates/runtime-bundles/pm2-runtime/attach.sh +8 -15
  82. package/dist/templates/runtime-bundles/pm2-runtime/env.template +1 -1
  83. package/dist/templates/runtime-bundles/pm2-runtime/stages/api-health.sh +9 -0
  84. package/dist/templates/runtime-bundles/shared-kernel/STAGES.md +32 -18
  85. package/dist/templates/runtime-bundles/shared-kernel/agent-cli.sh +35 -0
  86. package/dist/templates/runtime-bundles/shared-kernel/launch.sh +35 -0
  87. package/dist/templates/runtime-bundles/shared-kernel/lib/verify-runner.mjs +129 -0
  88. package/dist/templates/runtime-bundles/shared-kernel/preflight.sh +35 -0
  89. package/dist/templates/runtime-bundles/shared-kernel/setup-infra.sh +35 -0
  90. package/dist/templates/runtime-bundles/shared-kernel/stages/README.sh +1 -1
  91. package/dist/templates/runtime-bundles/shared-kernel/stages/readiness.sh +14 -0
  92. package/dist/templates/runtime-bundles/shared-kernel/teardown.sh +35 -0
  93. package/dist/templates/runtime-bundles/shared-kernel/verify.sh +35 -0
  94. package/dist/templates/shared-docs/claude-agent/intro.md +3 -0
  95. package/dist/templates/shared-docs/readme/maintaining-this-harness.md +14 -0
  96. package/dist/templates/shared-docs/readme/run-history.md +5 -0
  97. package/dist/templates/shared-docs/readme/session-lifecycle.md +20 -0
  98. package/package.json +1 -1
  99. package/dist/templates/har-boilerplate/CLAUDE.agent.md +0 -83
  100. package/dist/templates/har-boilerplate/README.md +0 -199
  101. package/dist/templates/har-boilerplate/STAGES.md +0 -99
  102. package/dist/templates/har-boilerplate/agent-cli.sh +0 -225
  103. package/dist/templates/har-boilerplate/agent-slot.sh +0 -850
  104. package/dist/templates/har-boilerplate/attach.sh +0 -22
  105. package/dist/templates/har-boilerplate/ecosystem.agent.template.cjs +0 -52
  106. package/dist/templates/har-boilerplate/env.template +0 -40
  107. package/dist/templates/har-boilerplate/launch.sh +0 -323
  108. package/dist/templates/har-boilerplate/preflight.sh +0 -32
  109. package/dist/templates/har-boilerplate/provision-toolchain.sh +0 -766
  110. package/dist/templates/har-boilerplate/setup-infra.sh +0 -178
  111. package/dist/templates/har-boilerplate/stages/README.sh +0 -11
  112. package/dist/templates/har-boilerplate/teardown.sh +0 -87
  113. package/dist/templates/har-boilerplate/verify.sh +0 -249
  114. package/dist/templates/har-boilerplate-cli/README.md +0 -157
  115. package/dist/templates/har-boilerplate-cli/STAGES.md +0 -99
  116. package/dist/templates/har-boilerplate-cli/agent-cli.sh +0 -160
  117. package/dist/templates/har-boilerplate-cli/agent-slot.sh +0 -832
  118. package/dist/templates/har-boilerplate-cli/launch.sh +0 -216
  119. package/dist/templates/har-boilerplate-cli/preflight.sh +0 -28
  120. package/dist/templates/har-boilerplate-cli/provision-toolchain.sh +0 -766
  121. package/dist/templates/har-boilerplate-cli/setup-infra.sh +0 -178
  122. package/dist/templates/har-boilerplate-cli/stages/README.sh +0 -11
  123. package/dist/templates/har-boilerplate-cli/teardown.sh +0 -66
  124. package/dist/templates/har-boilerplate-cli/verify.sh +0 -210
  125. package/dist/templates/har-boilerplate-ios/ADAPT-PROMPT.md +0 -63
  126. package/dist/templates/har-boilerplate-ios/CLAUDE.agent.md +0 -90
  127. package/dist/templates/har-boilerplate-ios/README.md +0 -173
  128. package/dist/templates/har-boilerplate-ios/STAGES.md +0 -99
  129. package/dist/templates/har-boilerplate-ios/agent-cli.sh +0 -149
  130. package/dist/templates/har-boilerplate-ios/agent-slot.sh +0 -479
  131. package/dist/templates/har-boilerplate-ios/launch.sh +0 -206
  132. package/dist/templates/har-boilerplate-ios/provision-toolchain.sh +0 -766
  133. package/dist/templates/har-boilerplate-ios/setup-infra.sh +0 -112
  134. package/dist/templates/har-boilerplate-ios/simulator.sh +0 -492
  135. package/dist/templates/har-boilerplate-ios/stages/README.sh +0 -11
  136. package/dist/templates/har-boilerplate-ios/teardown.sh +0 -68
  137. package/dist/templates/har-boilerplate-ios/verify.sh +0 -185
  138. package/dist/templates/runtime-bundles/pm2-runtime/README.md +0 -4
  139. package/dist/templates/runtime-bundles/shared-kernel/README.md +0 -3
  140. package/dist/templates/runtime-bundles/shared-kernel/provision-toolchain.sh +0 -766
  141. package/dist/templates/runtime-bundles/xcode-sim/README.md +0 -4
  142. package/dist/templates/runtime-bundles/xcode-sim/simulator.sh +0 -492
@@ -1,766 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Provision the project toolchain and append resolved paths to .env.agent.<id>.
3
- # Called from launch.sh after the agent env file is created.
4
- #
5
- # Configure in harness.env:
6
- # HARNESS_ECOSYSTEM — auto (default) | node | python | go | rust | java | ruby | ios | none
7
- # HARNESS_INSTALL_CMD — optional override (eval in HAR_WORK_DIR)
8
- # Ecosystem-specific optional overrides:
9
- # HARNESS_PYTHON_VENV_DIR — Python venv path relative to HAR_WORK_DIR (default: .har/venv)
10
- #
11
- # Required env from caller: HAR_WORK_DIR, HAR_ENV_FILE
12
- # Optional: HAR_WORKTREE_DIR, HAR_REL_PREFIX, HAR_AGENT_ID (for logging)
13
- set -euo pipefail
14
-
15
- : "${HAR_WORK_DIR:?HAR_WORK_DIR is required}"
16
- : "${HAR_ENV_FILE:?HAR_ENV_FILE is required}"
17
-
18
- HAR_WORKTREE_DIR="${HAR_WORKTREE_DIR:-}"
19
- HAR_REL_PREFIX="${HAR_REL_PREFIX:-}"
20
- HAR_AGENT_ID="${HAR_AGENT_ID:-}"
21
-
22
- pt_log() {
23
- if [ -n "$HAR_AGENT_ID" ]; then
24
- echo "==> [agent-${HAR_AGENT_ID}] toolchain: $*" >&2
25
- else
26
- echo "==> [provision-toolchain] $*" >&2
27
- fi
28
- }
29
-
30
- # ── Node package manager helpers ──────────────────────────────────────────────
31
- # Mirrored verbatim in harness.env and provision-toolchain.sh so a sourcing
32
- # script and the provisioning subprocess resolve the same tool.
33
-
34
- # Package managers HAR can drive, in fallback preference order.
35
- HAR_NODE_PACKAGE_MANAGERS="npm bun pnpm yarn"
36
-
37
- # Package manager the repo declares: an explicit HARNESS_NODE_PACKAGE_MANAGER
38
- # wins, then package.json "packageManager", then the lockfile. Empty when the
39
- # repo says nothing.
40
- har_node_declared_package_manager() {
41
- local dir="${1:-.}"
42
-
43
- if [ -n "${HARNESS_NODE_PACKAGE_MANAGER:-}" ]; then
44
- echo "$HARNESS_NODE_PACKAGE_MANAGER"
45
- return
46
- fi
47
- if [ -f "$dir/package.json" ]; then
48
- local field
49
- field="$(sed -n 's/.*"packageManager"[[:space:]]*:[[:space:]]*"\([a-z]*\)@.*/\1/p' "$dir/package.json" | head -1)"
50
- if [ -n "$field" ]; then
51
- echo "$field"
52
- return
53
- fi
54
- fi
55
- if [ -f "$dir/bun.lock" ] || [ -f "$dir/bun.lockb" ]; then echo bun; return; fi
56
- if [ -f "$dir/pnpm-lock.yaml" ]; then echo pnpm; return; fi
57
- if [ -f "$dir/yarn.lock" ]; then echo yarn; return; fi
58
- if [ -f "$dir/package-lock.json" ]; then echo npm; return; fi
59
- echo ""
60
- }
61
-
62
- # Package manager to actually run. A manager the repo declares but this machine
63
- # lacks falls back to one that is installed, so a repo pinned to npm still
64
- # provisions on a bun-only machine (and the reverse).
65
- har_node_package_manager() {
66
- local dir="${1:-.}"
67
- local declared
68
- declared="$(har_node_declared_package_manager "$dir")"
69
-
70
- if [ -n "$declared" ] && command -v "$declared" >/dev/null 2>&1; then
71
- echo "$declared"
72
- return
73
- fi
74
-
75
- local candidate
76
- for candidate in $HAR_NODE_PACKAGE_MANAGERS; do
77
- if command -v "$candidate" >/dev/null 2>&1; then
78
- echo "$candidate"
79
- return
80
- fi
81
- done
82
-
83
- echo "${declared:-npm}"
84
- }
85
-
86
- # Lockfile a manager writes, so a substitute install can clean up after itself.
87
- har_node_lockfile() {
88
- case "$1" in
89
- bun) echo "bun.lock" ;;
90
- npm) echo "package-lock.json" ;;
91
- pnpm) echo "pnpm-lock.yaml" ;;
92
- yarn) echo "yarn.lock" ;;
93
- *) echo "" ;;
94
- esac
95
- }
96
-
97
- # Install dependencies in a directory. When a substitute manager stands in for
98
- # the one the repo declares, any lockfile it creates is removed afterwards:
99
- # provisioning must not migrate the repo to a different package manager.
100
- # (bun writes bun.lock even under --no-save, so the flags alone are not enough.)
101
- har_node_install() {
102
- local dir="$1"
103
- local manager="$2"
104
- local declared="${3:-}"
105
- local substituting=false
106
- local lockfile=""
107
- local had_lockfile=false
108
-
109
- if [ -n "$declared" ] && [ "$declared" != "$manager" ]; then
110
- substituting=true
111
- lockfile="$(har_node_lockfile "$manager")"
112
- if [ -n "$lockfile" ] && [ -e "$dir/$lockfile" ]; then
113
- had_lockfile=true
114
- fi
115
- fi
116
-
117
- local code=0
118
- (cd "$dir" && "$manager" install --silent) || code=$?
119
-
120
- if [ "$substituting" = true ] && [ "$had_lockfile" = false ] && [ -n "$lockfile" ]; then
121
- rm -f "$dir/$lockfile"
122
- [ "$manager" = "bun" ] && rm -f "$dir/bun.lockb"
123
- fi
124
- return $code
125
- }
126
-
127
- # Package runner (npx equivalent) for one-off CLIs such as pm2, including the
128
- # flag that keeps it non-interactive. Takes a manager name, or none to resolve
129
- # from the environment and PATH.
130
- har_pkg_exec() {
131
- case "${1:-${HARNESS_NODE_PACKAGE_MANAGER:-}}" in
132
- bun) echo "bunx"; return ;;
133
- pnpm) echo "pnpm dlx"; return ;;
134
- yarn) echo "yarn dlx"; return ;;
135
- npm) echo "npx --yes"; return ;;
136
- esac
137
- if command -v npx >/dev/null 2>&1; then echo "npx --yes"; return; fi
138
- if command -v bunx >/dev/null 2>&1; then echo "bunx"; return; fi
139
- if command -v pnpm >/dev/null 2>&1; then echo "pnpm dlx"; return; fi
140
- echo "npx --yes"
141
- }
142
-
143
- append_env() {
144
- local key="$1"
145
- local value="$2"
146
- # %q, not a raw value: an unquoted value with spaces (an Xcode scheme, a
147
- # simulator name, a path under /Applications) breaks `source .env.agent.<id>`.
148
- printf '%s=%q\n' "$key" "$value" >> "$HAR_ENV_FILE"
149
- }
150
-
151
- append_path_prefix() {
152
- local prefix="$1"
153
- [ -n "$prefix" ] && [ -d "$prefix" ] || return 0
154
- append_env "PATH" "${prefix}:${PATH:-$PATH}"
155
- }
156
-
157
- detect_ecosystem() {
158
- local dir="$1"
159
- local configured="${HARNESS_ECOSYSTEM:-auto}"
160
- if [ -n "$configured" ] && [ "$configured" != "auto" ]; then
161
- echo "$configured"
162
- return
163
- fi
164
- if [ -f "$dir/package.json" ]; then echo node; return; fi
165
- if [ -f "$dir/pyproject.toml" ] || [ -f "$dir/setup.py" ] || [ -f "$dir/setup.cfg" ] \
166
- || [ -f "$dir/requirements.txt" ] || [ -f "$dir/Pipfile" ]; then
167
- echo python
168
- return
169
- fi
170
- if [ -f "$dir/go.mod" ]; then echo go; return; fi
171
- if [ -f "$dir/Cargo.toml" ]; then echo rust; return; fi
172
- if [ -f "$dir/pom.xml" ] || [ -f "$dir/build.gradle" ] || [ -f "$dir/build.gradle.kts" ]; then
173
- echo java
174
- return
175
- fi
176
- if [ -f "$dir/Gemfile" ]; then echo ruby; return; fi
177
- if [ -n "${HARNESS_XCODE_SCHEME:-}" ] || [ -n "${HARNESS_XCODE_PROJECT:-}" ] \
178
- || [ -n "${HARNESS_XCODE_WORKSPACE:-}" ]; then
179
- echo ios
180
- return
181
- fi
182
- echo none
183
- }
184
-
185
- run_install_cmd() {
186
- local dir="$1"
187
- if [ -n "${HARNESS_INSTALL_CMD:-}" ]; then
188
- pt_log "Running HARNESS_INSTALL_CMD..."
189
- (cd "$dir" && eval "$HARNESS_INSTALL_CMD")
190
- return
191
- fi
192
- return 1
193
- }
194
-
195
- provision_node() {
196
- local dir="$1"
197
- local node_bin="node"
198
- local pkg_manager declared_manager
199
- declared_manager="$(har_node_declared_package_manager "$dir")"
200
- pkg_manager="$(har_node_package_manager "$dir")"
201
-
202
- if [ -n "$declared_manager" ] && [ "$declared_manager" != "$pkg_manager" ]; then
203
- pt_log "Repo declares ${declared_manager}, which is not on PATH — installing with ${pkg_manager} and leaving the lockfile untouched."
204
- fi
205
-
206
- if ! run_install_cmd "$dir"; then
207
- pt_log "Installing Node dependencies with ${pkg_manager}..."
208
- har_node_install "$dir" "$pkg_manager" "$declared_manager"
209
- fi
210
-
211
- if command -v node >/dev/null 2>&1; then
212
- node_bin="$(command -v node)"
213
- elif [ "$pkg_manager" = "bun" ]; then
214
- # bun-only machine: bun runs the `node -e` snippets verify.sh relies on.
215
- node_bin="$(command -v bun)"
216
- fi
217
-
218
- local npm_bin="$pkg_manager"
219
- if command -v "$pkg_manager" >/dev/null 2>&1; then
220
- npm_bin="$(command -v "$pkg_manager")"
221
- fi
222
-
223
- append_env "HARNESS_ECOSYSTEM" "node"
224
- append_env "NODE_BIN" "$node_bin"
225
- append_env "NPM_BIN" "$npm_bin"
226
- append_env "HARNESS_NODE_PACKAGE_MANAGER" "$pkg_manager"
227
- append_env "HARNESS_PKG_EXEC" "$(har_pkg_exec "$pkg_manager")"
228
- append_path_prefix "$dir/node_modules/.bin"
229
- }
230
-
231
- # ── Python interpreter helpers ────────────────────────────────────────────────
232
- # Resolve the project Python (requires-python, .python-version, uv) instead of
233
- # blindly using PATH python3.
234
-
235
- har_python_version_from_bin() {
236
- local bin="$1"
237
- "$bin" -c 'import sys; print(".".join(map(str, sys.version_info[:3])))' 2>/dev/null \
238
- | tr -d '[:space:]'
239
- }
240
-
241
- har_python_version_to_sortable() {
242
- local v="${1#python}"
243
- v="${v#v}"
244
- local major=0 minor=0 patch=0
245
- IFS='.' read -r major minor patch _ <<< "$v"
246
- major=${major:-0}
247
- minor=${minor:-0}
248
- patch=${patch:-0}
249
- printf '%03d%03d%03d' "$major" "$minor" "$patch"
250
- }
251
-
252
- har_python_version_ge() {
253
- local a b
254
- a="$(har_python_version_to_sortable "$1")"
255
- b="$(har_python_version_to_sortable "$2")"
256
- [ "$a" -ge "$b" ]
257
- }
258
-
259
- har_python_requires_minimum() {
260
- local spec="$1"
261
- local ver=""
262
-
263
- [ -n "$spec" ] || return 1
264
-
265
- ver="$(printf '%s' "$spec" | sed -n \
266
- -e 's/.*>=\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
267
- -e 's/.*~=\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
268
- -e 's/.*==\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
269
- | head -1)"
270
-
271
- if [ -z "$ver" ]; then
272
- ver="$(printf '%s' "$spec" | sed -n 's/^\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p')"
273
- fi
274
-
275
- [ -n "$ver" ] && echo "$ver"
276
- }
277
-
278
- har_python_read_dot_version() {
279
- local dir="$1"
280
- [ -f "$dir/.python-version" ] || return 1
281
- head -1 "$dir/.python-version" | tr -d '[:space:]'
282
- }
283
-
284
- har_python_read_requires_spec() {
285
- local dir="$1"
286
- local spec=""
287
-
288
- if [ -f "$dir/pyproject.toml" ]; then
289
- spec="$(sed -n 's/^[[:space:]]*requires-python[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
290
- "$dir/pyproject.toml" | head -1)"
291
- fi
292
- if [ -z "$spec" ] && [ -f "$dir/setup.cfg" ]; then
293
- spec="$(sed -n '/^\[options\]/,/^\[/ s/^python_requires[[:space:]]*=[[:space:]]*\(.*\)/\1/p' \
294
- "$dir/setup.cfg" | head -1 | tr -d ' "')"
295
- fi
296
- [ -n "$spec" ] && echo "$spec"
297
- }
298
-
299
- har_python_project_minimum() {
300
- local dir="$1"
301
- local req_min="" dot_ver="" higher=""
302
-
303
- req_min="$(har_python_requires_minimum "$(har_python_read_requires_spec "$dir" || true)" || true)"
304
- dot_ver="$(har_python_read_dot_version "$dir" || true)"
305
- if [ -n "$dot_ver" ]; then
306
- dot_ver="$(printf '%s' "$dot_ver" | sed 's/^v//; s/^\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/')"
307
- fi
308
-
309
- if [ -n "$req_min" ] && [ -n "$dot_ver" ]; then
310
- if har_python_version_ge "$dot_ver" "$req_min"; then
311
- higher="$dot_ver"
312
- else
313
- higher="$req_min"
314
- fi
315
- elif [ -n "$req_min" ]; then
316
- higher="$req_min"
317
- elif [ -n "$dot_ver" ]; then
318
- higher="$dot_ver"
319
- fi
320
-
321
- [ -n "$higher" ] && echo "$higher"
322
- }
323
-
324
- har_python_is_uv_project() {
325
- local dir="$1"
326
- [ -f "$dir/uv.lock" ] && return 0
327
- [ -f "$dir/pyproject.toml" ] && grep -q '^\[tool\.uv\]' "$dir/pyproject.toml" 2>/dev/null
328
- }
329
-
330
- har_python_uv_find() {
331
- local dir="$1"
332
- local request="${2:-}"
333
-
334
- command -v uv >/dev/null 2>&1 || return 1
335
- if [ -n "$request" ]; then
336
- (cd "$dir" && uv python find "$request" 2>/dev/null)
337
- else
338
- (cd "$dir" && uv python find 2>/dev/null)
339
- fi
340
- }
341
-
342
- har_python_resolve_interpreter() {
343
- local dir="$1"
344
- local minimum="${2:-}"
345
- local found=""
346
-
347
- if har_python_is_uv_project "$dir"; then
348
- found="$(har_python_uv_find "$dir" "$minimum" || true)"
349
- if [ -n "$found" ] && [ -x "$found" ]; then
350
- echo "$found"
351
- return 0
352
- fi
353
- fi
354
-
355
- if [ -f "$dir/.python-version" ]; then
356
- found="$(har_python_uv_find "$dir" "$(har_python_read_dot_version "$dir")" || true)"
357
- if [ -n "$found" ] && [ -x "$found" ]; then
358
- echo "$found"
359
- return 0
360
- fi
361
- if command -v pyenv >/dev/null 2>&1; then
362
- found="$(cd "$dir" && pyenv which python 2>/dev/null || true)"
363
- if [ -n "$found" ] && [ -x "$found" ]; then
364
- echo "$found"
365
- return 0
366
- fi
367
- fi
368
- fi
369
-
370
- if [ -n "$minimum" ]; then
371
- found="$(har_python_uv_find "$dir" "$minimum" || true)"
372
- if [ -n "$found" ] && [ -x "$found" ]; then
373
- echo "$found"
374
- return 0
375
- fi
376
- fi
377
-
378
- if command -v python3 >/dev/null 2>&1; then
379
- local sys_py sys_ver=""
380
- sys_py="$(command -v python3)"
381
- if [ -z "$minimum" ]; then
382
- echo "$sys_py"
383
- return 0
384
- fi
385
- sys_ver="$(har_python_version_from_bin "$sys_py")"
386
- if [ -n "$sys_ver" ] && har_python_version_ge "$sys_ver" "$minimum"; then
387
- echo "$sys_py"
388
- return 0
389
- fi
390
- echo "$sys_py"
391
- return 0
392
- fi
393
-
394
- return 1
395
- }
396
-
397
- har_python_warn_if_below_minimum() {
398
- local bin="$1"
399
- local minimum="$2"
400
- local actual=""
401
-
402
- [ -n "$minimum" ] || return 0
403
- actual="$(har_python_version_from_bin "$bin")"
404
- [ -n "$actual" ] || return 0
405
- if ! har_python_version_ge "$actual" "$minimum"; then
406
- pt_log "WARNING: resolved Python ${actual} is older than required ${minimum} (requires-python / .python-version)"
407
- fi
408
- }
409
-
410
- har_python_create_venv() {
411
- local dir="$1"
412
- local venv_dir="$2"
413
- local interpreter="$3"
414
- local venv_rel="${venv_dir#"$dir"/}"
415
-
416
- if har_python_is_uv_project "$dir" && command -v uv >/dev/null 2>&1; then
417
- pt_log "Creating Python venv with uv at ${venv_rel}..."
418
- if (cd "$dir" && uv venv --python "$interpreter" "$venv_dir" 2>/dev/null) \
419
- || (cd "$dir" && uv venv "$venv_dir" 2>/dev/null); then
420
- return 0
421
- fi
422
- pt_log "uv venv failed — falling back to ${interpreter} -m venv"
423
- fi
424
-
425
- pt_log "Creating Python venv at ${venv_rel}..."
426
- "$interpreter" -m venv "$venv_dir"
427
- }
428
-
429
- har_python_install_deps() {
430
- local dir="$1"
431
- local venv_dir="$2"
432
-
433
- if har_python_is_uv_project "$dir" && command -v uv >/dev/null 2>&1; then
434
- pt_log "Syncing Python dependencies with uv..."
435
- if (cd "$dir" && UV_PROJECT_ENVIRONMENT="$venv_dir" uv sync --quiet 2>/dev/null); then
436
- return 0
437
- fi
438
- if (cd "$dir" && uv sync --quiet 2>/dev/null); then
439
- return 0
440
- fi
441
- pt_log "uv sync failed — falling back to pip"
442
- return 1
443
- fi
444
- return 1
445
- }
446
-
447
- provision_python() {
448
- local dir="$1"
449
- local venv_rel="${HARNESS_PYTHON_VENV_DIR:-.har/venv}"
450
- local venv_dir="$dir/$venv_rel"
451
- local python_bin=""
452
- local interpreter=""
453
- local project_min=""
454
-
455
- project_min="$(har_python_project_minimum "$dir" || true)"
456
- interpreter="$(har_python_resolve_interpreter "$dir" "$project_min" || true)"
457
-
458
- if [ -z "$interpreter" ] || ! command -v "$interpreter" >/dev/null 2>&1; then
459
- pt_log "No suitable Python interpreter found — skipping venv provisioning"
460
- append_env "HARNESS_ECOSYSTEM" "python"
461
- append_env "PYTHON_BIN" "${PYTHON_BIN:-python3}"
462
- return
463
- fi
464
-
465
- if [ -n "$project_min" ]; then
466
- har_python_warn_if_below_minimum "$interpreter" "$project_min"
467
- fi
468
-
469
- if [ -d "$venv_dir" ] && [ -x "$venv_dir/bin/python" ] && [ -n "$project_min" ]; then
470
- local venv_ver=""
471
- venv_ver="$(har_python_version_from_bin "$venv_dir/bin/python")"
472
- if [ -n "$venv_ver" ] && ! har_python_version_ge "$venv_ver" "$project_min"; then
473
- pt_log "Existing venv Python ${venv_ver} is older than required ${project_min} — recreating..."
474
- rm -rf "$venv_dir"
475
- fi
476
- fi
477
-
478
- if [ ! -d "$venv_dir" ]; then
479
- if ! har_python_create_venv "$dir" "$venv_dir" "$interpreter"; then
480
- rm -rf "$venv_dir"
481
- pt_log "venv creation failed (on Debian/Ubuntu install python3-venv) — using resolved interpreter"
482
- append_env "HARNESS_ECOSYSTEM" "python"
483
- append_env "PYTHON_BIN" "$interpreter"
484
- return
485
- fi
486
- fi
487
-
488
- if [ ! -x "$venv_dir/bin/python" ]; then
489
- pt_log "Python venv at $venv_rel is missing or broken — using resolved interpreter"
490
- append_env "HARNESS_ECOSYSTEM" "python"
491
- append_env "PYTHON_BIN" "$interpreter"
492
- return
493
- fi
494
-
495
- python_bin="$venv_dir/bin/python"
496
- if [ -n "$project_min" ]; then
497
- har_python_warn_if_below_minimum "$python_bin" "$project_min"
498
- fi
499
-
500
- # shellcheck disable=SC1091
501
- source "$venv_dir/bin/activate"
502
-
503
- if ! run_install_cmd "$dir"; then
504
- if ! har_python_install_deps "$dir" "$venv_dir"; then
505
- pt_log "Installing Python dependencies..."
506
- if [ -f "$dir/pyproject.toml" ]; then
507
- (cd "$dir" && pip install -q -e ".[dev]" 2>/dev/null) || (cd "$dir" && pip install -q -e .)
508
- elif [ -f "$dir/requirements.txt" ]; then
509
- (cd "$dir" && pip install -q -r requirements.txt)
510
- elif [ -f "$dir/setup.py" ] || [ -f "$dir/setup.cfg" ]; then
511
- (cd "$dir" && pip install -q -e .)
512
- elif [ -f "$dir/Pipfile" ] && command -v pipenv >/dev/null 2>&1; then
513
- (cd "$dir" && pipenv install --dev)
514
- python_bin="$(cd "$dir" && pipenv --py)"
515
- fi
516
- fi
517
- fi
518
-
519
- append_env "HARNESS_ECOSYSTEM" "python"
520
- append_env "PYTHON_BIN" "$python_bin"
521
- append_env "VIRTUAL_ENV" "$venv_dir"
522
- append_path_prefix "$venv_dir/bin"
523
- }
524
-
525
- provision_go() {
526
- local dir="$1"
527
- if ! run_install_cmd "$dir"; then
528
- if command -v go >/dev/null 2>&1; then
529
- pt_log "Downloading Go modules..."
530
- (cd "$dir" && go mod download)
531
- else
532
- pt_log "go not found on PATH — record paths only"
533
- fi
534
- fi
535
- append_env "HARNESS_ECOSYSTEM" "go"
536
- append_env "GO_BIN" "$(command -v go 2>/dev/null || echo go)"
537
- if [ -n "${GOPATH:-}" ]; then append_env "GOPATH" "$GOPATH"; fi
538
- if [ -n "${GOROOT:-}" ]; then append_env "GOROOT" "$GOROOT"; fi
539
- }
540
-
541
- provision_rust() {
542
- local dir="$1"
543
- if ! run_install_cmd "$dir"; then
544
- if command -v cargo >/dev/null 2>&1; then
545
- pt_log "Fetching Rust dependencies..."
546
- (cd "$dir" && cargo fetch)
547
- else
548
- pt_log "cargo not found on PATH — record paths only"
549
- fi
550
- fi
551
- append_env "HARNESS_ECOSYSTEM" "rust"
552
- append_env "CARGO_BIN" "$(command -v cargo 2>/dev/null || echo cargo)"
553
- append_env "RUSTC_BIN" "$(command -v rustc 2>/dev/null || echo rustc)"
554
- }
555
-
556
- provision_java() {
557
- local dir="$1"
558
- run_install_cmd "$dir" || true
559
- append_env "HARNESS_ECOSYSTEM" "java"
560
- if [ -n "${JAVA_HOME:-}" ]; then
561
- append_env "JAVA_HOME" "$JAVA_HOME"
562
- append_path_prefix "$JAVA_HOME/bin"
563
- fi
564
- if command -v mvn >/dev/null 2>&1; then
565
- append_env "MVN_BIN" "$(command -v mvn)"
566
- elif [ -f "$dir/gradlew" ]; then
567
- append_env "GRADLE_BIN" "$dir/gradlew"
568
- elif command -v gradle >/dev/null 2>&1; then
569
- append_env "GRADLE_BIN" "$(command -v gradle)"
570
- fi
571
- }
572
-
573
- provision_ruby() {
574
- local dir="$1"
575
- if ! run_install_cmd "$dir"; then
576
- if command -v bundle >/dev/null 2>&1; then
577
- pt_log "Installing Ruby gems..."
578
- (cd "$dir" && bundle install --quiet)
579
- else
580
- pt_log "bundle not found on PATH — record paths only"
581
- fi
582
- fi
583
- append_env "HARNESS_ECOSYSTEM" "ruby"
584
- append_env "RUBY_BIN" "$(command -v ruby 2>/dev/null || echo ruby)"
585
- append_env "BUNDLE_BIN" "$(command -v bundle 2>/dev/null || echo bundle)"
586
- append_path_prefix "$dir/vendor/bundle/bin"
587
- }
588
-
589
- # ── iOS Xcode project helpers ─────────────────────────────────────────────────
590
- # Tuist, XcodeGen and CocoaPods treat the .xcodeproj / .xcworkspace as a build
591
- # product rather than a tracked file, so a fresh worktree has nothing for
592
- # xcodebuild to open. Generate it here — and fail naming the missing generator,
593
- # instead of letting xcodebuild report an opaque "scheme not found" later.
594
-
595
- # First project file of a kind in the work dir. Runs from inside the dir so a dot
596
- # in the worktree path itself cannot trip the dotfile filter, and skips both the
597
- # project.xcworkspace every .xcodeproj carries inside it and whatever CocoaPods
598
- # generates under Pods/.
599
- har_ios_find_project_file() {
600
- local dir="$1"
601
- local pattern="$2"
602
- local match=""
603
- match="$(cd "$dir" 2>/dev/null && find . -maxdepth 2 -name "$pattern" \
604
- ! -path "./.*" ! -path "*.xcodeproj/*" ! -path "*/Pods/*" 2>/dev/null | head -1)" || true
605
- [ -n "$match" ] || return 1
606
- echo "${match#./}"
607
- }
608
-
609
- # True when this worktree already has something xcodebuild can open. Adapt-time
610
- # HARNESS_XCODE_WORKSPACE / HARNESS_XCODE_PROJECT decide it when set: they name
611
- # the generated file, so a missing one means the generator still has to run.
612
- har_ios_has_project() {
613
- local dir="$1"
614
-
615
- if [ -n "${HARNESS_XCODE_WORKSPACE:-}" ] || [ -n "${HARNESS_XCODE_PROJECT:-}" ]; then
616
- if [ -n "${HARNESS_XCODE_WORKSPACE:-}" ] && [ -e "$dir/$HARNESS_XCODE_WORKSPACE" ]; then
617
- return 0
618
- fi
619
- if [ -n "${HARNESS_XCODE_PROJECT:-}" ] && [ -e "$dir/$HARNESS_XCODE_PROJECT" ]; then
620
- return 0
621
- fi
622
- return 1
623
- fi
624
-
625
- if har_ios_find_project_file "$dir" '*.xcworkspace' >/dev/null; then return 0; fi
626
- if har_ios_find_project_file "$dir" '*.xcodeproj' >/dev/null; then return 0; fi
627
- return 1
628
- }
629
-
630
- # Generator this repo declares, or empty. A Podfile counts: `pod install` is what
631
- # writes the .xcworkspace for a CocoaPods project.
632
- har_ios_project_generator() {
633
- local dir="$1"
634
- if [ -f "$dir/Project.swift" ] || [ -f "$dir/Workspace.swift" ]; then echo tuist; return; fi
635
- if [ -f "$dir/project.yml" ] || [ -f "$dir/project.yaml" ]; then echo xcodegen; return; fi
636
- if [ -f "$dir/Podfile" ]; then echo pod; return; fi
637
- echo ""
638
- }
639
-
640
- har_ios_generate_project() {
641
- local dir="$1"
642
- local generator=""
643
-
644
- if har_ios_has_project "$dir"; then
645
- return 0
646
- fi
647
-
648
- generator="$(har_ios_project_generator "$dir")"
649
- if [ -z "$generator" ]; then
650
- pt_log "No .xcodeproj/.xcworkspace found and no generator manifest (Project.swift, project.yml, Podfile) — set HARNESS_XCODE_PROJECT/HARNESS_XCODE_WORKSPACE or HARNESS_INSTALL_CMD in harness.env"
651
- return 0
652
- fi
653
-
654
- if ! command -v "$generator" >/dev/null 2>&1; then
655
- local label="$generator"
656
- [ "$generator" = "pod" ] && label="CocoaPods (pod)"
657
- pt_log "ERROR: this repo generates its Xcode project with ${label}, which is not on PATH."
658
- pt_log " Install ${label}, or set HARNESS_INSTALL_CMD in harness.env to provision the project another way."
659
- return 1
660
- fi
661
-
662
- case "$generator" in
663
- tuist)
664
- pt_log "Generating the Xcode project with tuist..."
665
- if ! (cd "$dir" && tuist generate --no-open); then
666
- pt_log "tuist generate --no-open failed — retrying without the flag"
667
- (cd "$dir" && tuist generate)
668
- fi
669
- ;;
670
- xcodegen)
671
- pt_log "Generating the Xcode project with xcodegen..."
672
- (cd "$dir" && xcodegen generate)
673
- ;;
674
- pod)
675
- # har_ios_pod_install writes the workspace right after this.
676
- :
677
- ;;
678
- esac
679
- }
680
-
681
- # CocoaPods dependencies. Independent of project generation: a tracked
682
- # .xcworkspace still cannot build when Pods/ is missing in a fresh worktree.
683
- har_ios_pod_install() {
684
- local dir="$1"
685
-
686
- [ -f "$dir/Podfile" ] || return 0
687
- [ -d "$dir/Pods" ] && return 0
688
-
689
- if ! command -v pod >/dev/null 2>&1; then
690
- pt_log "ERROR: this repo uses CocoaPods (Podfile, no Pods/) and pod is not on PATH."
691
- pt_log " Install CocoaPods, or set HARNESS_INSTALL_CMD in harness.env to provision the project another way."
692
- return 1
693
- fi
694
-
695
- pt_log "Installing CocoaPods dependencies..."
696
- (cd "$dir" && pod install)
697
- }
698
-
699
- provision_ios() {
700
- local dir="$1"
701
-
702
- # An explicit HARNESS_INSTALL_CMD owns provisioning outright: the default
703
- # generators stay out of the way, and a failing override fails the launch
704
- # rather than being papered over.
705
- if [ -n "${HARNESS_INSTALL_CMD:-}" ]; then
706
- run_install_cmd "$dir"
707
- else
708
- har_ios_generate_project "$dir"
709
- har_ios_pod_install "$dir"
710
- fi
711
-
712
- append_env "HARNESS_ECOSYSTEM" "ios"
713
- append_env "XCODEBUILD_BIN" "$(command -v xcodebuild 2>/dev/null || echo xcodebuild)"
714
- if [ -n "${HARNESS_XCODE_SCHEME:-}" ]; then
715
- append_env "HARNESS_XCODE_SCHEME" "$HARNESS_XCODE_SCHEME"
716
- fi
717
- # The simulator is not recorded here: launch.sh writes the device reserved for
718
- # this slot, and harness.env already carries the shared one.
719
- if [ -n "${HARNESS_BUNDLE_ID:-}" ]; then
720
- append_env "HARNESS_BUNDLE_ID" "$HARNESS_BUNDLE_ID"
721
- fi
722
- if [ -n "${DEVELOPER_DIR:-}" ]; then
723
- append_env "DEVELOPER_DIR" "$DEVELOPER_DIR"
724
- fi
725
- }
726
-
727
- provision_monorepo_root() {
728
- [ -n "$HAR_REL_PREFIX" ] || return 0
729
- [ -n "$HAR_WORKTREE_DIR" ] || return 0
730
- [ -f "$HAR_WORKTREE_DIR/package.json" ] || return 0
731
- [ -d "$HAR_WORKTREE_DIR/node_modules" ] && return 0
732
- local pkg_manager
733
- pkg_manager="$(har_node_package_manager "$HAR_WORKTREE_DIR")"
734
- pt_log "Installing monorepo root dependencies in $HAR_WORKTREE_DIR with ${pkg_manager}..."
735
- har_node_install "$HAR_WORKTREE_DIR" "$pkg_manager" \
736
- "$(har_node_declared_package_manager "$HAR_WORKTREE_DIR")"
737
- }
738
-
739
- provision_ecosystem() {
740
- local dir="$1"
741
- local ecosystem
742
- ecosystem="$(detect_ecosystem "$dir")"
743
- pt_log "Toolchain ecosystem: ${ecosystem} (work dir: ${dir})"
744
-
745
- case "$ecosystem" in
746
- node) provision_node "$dir" ;;
747
- python) provision_python "$dir" ;;
748
- go) provision_go "$dir" ;;
749
- rust) provision_rust "$dir" ;;
750
- java) provision_java "$dir" ;;
751
- ruby) provision_ruby "$dir" ;;
752
- ios) provision_ios "$dir" ;;
753
- none)
754
- if run_install_cmd "$dir"; then
755
- append_env "HARNESS_ECOSYSTEM" "custom"
756
- else
757
- pt_log "No ecosystem manifest detected — set HARNESS_ECOSYSTEM or HARNESS_INSTALL_CMD in harness.env"
758
- append_env "HARNESS_ECOSYSTEM" "none"
759
- fi
760
- ;;
761
- esac
762
- }
763
-
764
- append_env "HARNESS_TOOLCHAIN_PROVISIONED" "true"
765
- provision_ecosystem "$HAR_WORK_DIR"
766
- provision_monorepo_root