agent-control-plane 0.4.9 → 0.7.0

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 (87) hide show
  1. package/README.md +109 -13
  2. package/npm/bin/agent-control-plane.js +1 -1
  3. package/package.json +39 -33
  4. package/tools/bin/debug-session.sh +106 -0
  5. package/tools/bin/flow-config-lib.sh +13 -3508
  6. package/tools/bin/flow-execution-lib.sh +243 -0
  7. package/tools/bin/flow-forge-lib.sh +1770 -0
  8. package/tools/bin/flow-profile-lib.sh +335 -0
  9. package/tools/bin/flow-provider-lib.sh +981 -0
  10. package/tools/bin/flow-runtime-doctor-linux.sh +136 -0
  11. package/tools/bin/flow-runtime-doctor.sh +5 -1
  12. package/tools/bin/flow-session-lib.sh +317 -0
  13. package/tools/bin/install-project-systemd.sh +255 -0
  14. package/tools/bin/project-runtimectl.sh +45 -0
  15. package/tools/bin/project-systemd-bootstrap.sh +74 -0
  16. package/tools/bin/uninstall-project-systemd.sh +87 -0
  17. package/tools/dashboard/app.js +238 -8
  18. package/tools/dashboard/issue_queue_state.py +101 -0
  19. package/tools/dashboard/requirements.txt +3 -0
  20. package/tools/dashboard/server.py +250 -30
  21. package/tools/dashboard/styles.css +526 -455
  22. package/tools/bin/agent-cleanup-worktree +0 -247
  23. package/tools/bin/agent-github-update-labels +0 -105
  24. package/tools/bin/agent-init-worktree +0 -216
  25. package/tools/bin/agent-project-archive-run +0 -52
  26. package/tools/bin/agent-project-capture-worker +0 -46
  27. package/tools/bin/agent-project-catch-up-issue-pr-links +0 -118
  28. package/tools/bin/agent-project-catch-up-merged-prs +0 -195
  29. package/tools/bin/agent-project-catch-up-scheduled-issue-retries +0 -123
  30. package/tools/bin/agent-project-cleanup-session +0 -513
  31. package/tools/bin/agent-project-detached-launch +0 -127
  32. package/tools/bin/agent-project-heartbeat-loop +0 -1029
  33. package/tools/bin/agent-project-open-issue-worktree +0 -89
  34. package/tools/bin/agent-project-open-pr-worktree +0 -80
  35. package/tools/bin/agent-project-publish-issue-pr +0 -468
  36. package/tools/bin/agent-project-reconcile-issue-session +0 -1409
  37. package/tools/bin/agent-project-reconcile-pr-session +0 -1288
  38. package/tools/bin/agent-project-retry-state +0 -158
  39. package/tools/bin/agent-project-run-claude-session +0 -805
  40. package/tools/bin/agent-project-run-codex-resilient +0 -963
  41. package/tools/bin/agent-project-run-codex-session +0 -435
  42. package/tools/bin/agent-project-run-kilo-session +0 -369
  43. package/tools/bin/agent-project-run-ollama-session +0 -658
  44. package/tools/bin/agent-project-run-openclaw-session +0 -1309
  45. package/tools/bin/agent-project-run-opencode-session +0 -377
  46. package/tools/bin/agent-project-run-pi-session +0 -479
  47. package/tools/bin/agent-project-sync-anchor-repo +0 -139
  48. package/tools/bin/agent-project-sync-source-repo-main +0 -163
  49. package/tools/bin/agent-project-worker-status +0 -188
  50. package/tools/bin/branch-verification-guard.sh +0 -364
  51. package/tools/bin/capture-worker.sh +0 -18
  52. package/tools/bin/cleanup-worktree.sh +0 -52
  53. package/tools/bin/codex-quota +0 -31
  54. package/tools/bin/create-follow-up-issue.sh +0 -114
  55. package/tools/bin/dashboard-launchd-bootstrap.sh +0 -50
  56. package/tools/bin/issue-publish-localization-guard.sh +0 -142
  57. package/tools/bin/issue-publish-scope-guard.sh +0 -242
  58. package/tools/bin/issue-requires-local-workspace-install.sh +0 -31
  59. package/tools/bin/issue-resource-class.sh +0 -12
  60. package/tools/bin/kick-scheduler.sh +0 -75
  61. package/tools/bin/label-follow-up-issues.sh +0 -14
  62. package/tools/bin/new-pr-worktree.sh +0 -50
  63. package/tools/bin/new-worktree.sh +0 -49
  64. package/tools/bin/pr-risk.sh +0 -12
  65. package/tools/bin/prepare-worktree.sh +0 -142
  66. package/tools/bin/provider-cooldown-state.sh +0 -204
  67. package/tools/bin/publish-issue-worker.sh +0 -31
  68. package/tools/bin/reconcile-bootstrap-lib.sh +0 -113
  69. package/tools/bin/reconcile-issue-worker.sh +0 -34
  70. package/tools/bin/reconcile-pr-worker.sh +0 -34
  71. package/tools/bin/record-verification.sh +0 -71
  72. package/tools/bin/render-flow-config.sh +0 -98
  73. package/tools/bin/resident-issue-controller-lib.sh +0 -448
  74. package/tools/bin/retry-state.sh +0 -31
  75. package/tools/bin/reuse-issue-worktree.sh +0 -121
  76. package/tools/bin/run-codex-bypass.sh +0 -3
  77. package/tools/bin/run-codex-safe.sh +0 -3
  78. package/tools/bin/run-codex-task.sh +0 -280
  79. package/tools/bin/serve-dashboard.sh +0 -5
  80. package/tools/bin/start-issue-worker.sh +0 -943
  81. package/tools/bin/start-pr-fix-worker.sh +0 -528
  82. package/tools/bin/start-pr-merge-repair-worker.sh +0 -8
  83. package/tools/bin/start-pr-review-worker.sh +0 -261
  84. package/tools/bin/start-resident-issue-loop.sh +0 -499
  85. package/tools/bin/update-github-labels.sh +0 -14
  86. package/tools/bin/worker-status.sh +0 -19
  87. package/tools/bin/workflow-catalog.sh +0 -77
@@ -1,528 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
- # shellcheck source=/dev/null
6
- source "${SCRIPT_DIR}/flow-config-lib.sh"
7
-
8
- PR_NUMBER="${1:?usage: start-pr-fix-worker.sh PR_NUMBER [safe|bypass] [fix|merge-repair]}"
9
- MODE="${2:-safe}"
10
- WORKER_KIND="${3:-fix}"
11
- WORKSPACE_DIR="$(cd "$(dirname "$0")/.." && pwd)"
12
- FLOW_SKILL_DIR="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
13
- if ! flow_require_explicit_profile_selection "${FLOW_SKILL_DIR}" "start-pr-fix-worker.sh"; then
14
- exit 64
15
- fi
16
- CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
17
- flow_export_execution_env "${CONFIG_YAML}"
18
- flow_export_project_env_aliases
19
- AGENT_ROOT="$(flow_resolve_agent_root "${CONFIG_YAML}")"
20
- PR_SESSION_PREFIX="$(flow_resolve_pr_session_prefix "${CONFIG_YAML}")"
21
- MANAGED_PR_BRANCH_GLOBS="$(flow_resolve_managed_pr_branch_globs "${CONFIG_YAML}")"
22
- RUNS_ROOT="$(flow_resolve_runs_root "${CONFIG_YAML}")"
23
- HISTORY_ROOT="$(flow_resolve_history_root "${CONFIG_YAML}")"
24
- REPO_SLUG="$(flow_resolve_repo_slug "${CONFIG_YAML}")"
25
- WEB_PLAYWRIGHT_COMMAND="$(flow_resolve_web_playwright_command "${CONFIG_YAML}")"
26
- SESSION="${PR_SESSION_PREFIX}${PR_NUMBER}"
27
- RUN_DIR="${RUNS_ROOT}/${SESSION}"
28
- UPDATE_LABELS_BIN="${WORKSPACE_DIR}/bin/agent-github-update-labels"
29
- launch_success="no"
30
- label_rollback_armed="no"
31
-
32
- rollback_labels_on_failure() {
33
- if [[ "${label_rollback_armed}" != "yes" || "${launch_success}" == "yes" ]]; then
34
- return 0
35
- fi
36
- if [[ -d "${RUN_DIR}" && ! -f "${RUN_DIR}/run.env" && ! -f "${RUN_DIR}/runner.env" && ! -f "${RUN_DIR}/result.env" ]]; then
37
- rm -rf "${RUN_DIR}" >/dev/null 2>&1 || true
38
- fi
39
- if [[ -x "${UPDATE_LABELS_BIN}" ]]; then
40
- bash "${UPDATE_LABELS_BIN}" --repo-slug "${REPO_SLUG}" --number "${PR_NUMBER}" --remove agent-running >/dev/null 2>&1 || true
41
- fi
42
- }
43
-
44
- reap_stale_run_dir() {
45
- if [[ ! -d "$RUN_DIR" ]]; then
46
- return 0
47
- fi
48
- if [[ -f "$RUN_DIR/run.env" ]]; then
49
- if "${WORKSPACE_DIR}/bin/cleanup-worktree.sh" "" "$SESSION" >/dev/null 2>&1; then
50
- return 0
51
- fi
52
- fi
53
- mkdir -p "$HISTORY_ROOT"
54
- mv "$RUN_DIR" "${HISTORY_ROOT}/${SESSION}-stale-$(date +%Y%m%d-%H%M%S)"
55
- }
56
-
57
- case "$WORKER_KIND" in
58
- fix)
59
- TEMPLATE_FILE="$(flow_resolve_template_file "pr-fix-template.md" "${WORKSPACE_DIR}" "${CONFIG_YAML}")"
60
- ;;
61
- merge-repair)
62
- TEMPLATE_FILE="$(flow_resolve_template_file "pr-merge-repair-template.md" "${WORKSPACE_DIR}" "${CONFIG_YAML}")"
63
- ;;
64
- *)
65
- echo "unknown worker kind: $WORKER_KIND" >&2
66
- exit 1
67
- ;;
68
- esac
69
-
70
- is_managed_agent_pr_branch() {
71
- local head_ref="${1:-}"
72
- local branch_glob=""
73
- for branch_glob in ${MANAGED_PR_BRANCH_GLOBS}; do
74
- case "$head_ref" in
75
- ${branch_glob}) return 0 ;;
76
- esac
77
- done
78
- return 1
79
- }
80
-
81
- if tmux has-session -t "$SESSION" 2>/dev/null; then
82
- echo "worker session already exists: $SESSION" >&2
83
- exit 1
84
- fi
85
-
86
- label_rollback_armed="yes"
87
- trap rollback_labels_on_failure EXIT INT TERM
88
-
89
- if [[ -d "$RUN_DIR" ]]; then
90
- reap_stale_run_dir
91
- fi
92
-
93
- PR_JSON="$(flow_github_pr_view_json "$REPO_SLUG" "$PR_NUMBER")"
94
- PR_TITLE="$(jq -r '.title' <<<"$PR_JSON")"
95
- PR_BODY="$(jq -r '.body // ""' <<<"$PR_JSON")"
96
- PR_URL="$(jq -r '.url' <<<"$PR_JSON")"
97
- PR_HEAD_REF="$(jq -r '.headRefName' <<<"$PR_JSON")"
98
- PR_BASE_REF="$(jq -r '.baseRefName' <<<"$PR_JSON")"
99
- PR_MERGE_STATE_STATUS="$(jq -r '.mergeStateStatus // "UNKNOWN"' <<<"$PR_JSON")"
100
- PR_HAS_HANDOFF_LABEL="$(jq -r 'any(.labels[]?; .name == "agent-handoff")' <<<"$PR_JSON")"
101
- PR_HAS_AGENT_STATUS_COMMENT="$(jq -r 'any(.comments[]?; ((.body // "") | test("^## PR (final review blocker|repair worker summary|repair summary|repair update)"; "i")))' <<<"$PR_JSON")"
102
- PR_CHECKS_TEXT="$(jq -r '
103
- if ((.statusCheckRollup // []) | length) == 0 then
104
- "- none"
105
- else
106
- (.statusCheckRollup // [])
107
- | map(
108
- "- "
109
- + (.name // .context // "unknown-check")
110
- + ": "
111
- + (.status // "UNKNOWN")
112
- + (
113
- if (.conclusion // "") != "" then
114
- " / " + .conclusion
115
- else
116
- ""
117
- end
118
- )
119
- )
120
- | join("\n")
121
- end
122
- ' <<<"$PR_JSON")"
123
-
124
- if ! is_managed_agent_pr_branch "$PR_HEAD_REF" && [[ "$PR_HAS_HANDOFF_LABEL" != "true" ]] && [[ "$PR_HAS_AGENT_STATUS_COMMENT" != "true" ]]; then
125
- echo "PR branch is not an agent branch: $PR_HEAD_REF" >&2
126
- exit 1
127
- fi
128
-
129
- RISK_JSON="$("${WORKSPACE_DIR}/bin/pr-risk.sh" "$PR_NUMBER")"
130
- PR_RISK="$(jq -r '.risk' <<<"$RISK_JSON")"
131
- PR_RISK_REASON="$(jq -r '.riskReason' <<<"$RISK_JSON")"
132
- PR_LINKED_ISSUE_ID="$(jq -r '.linkedIssueId // ""' <<<"$RISK_JSON")"
133
- PR_FILES_TEXT="$(jq -r '.files[] | "- " + .' <<<"$RISK_JSON")"
134
- PR_REPO_ROOT="$(flow_resolve_repo_root "${CONFIG_YAML}")"
135
- PR_DEPENDENCY_SOURCE_ROOT="${ACP_DEPENDENCY_SOURCE_ROOT:-${F_LOSNING_DEPENDENCY_SOURCE_ROOT:-$PR_REPO_ROOT}}"
136
- render_pr_context_reads_text() {
137
- local repo_root="${1:?repo root required}"
138
- local -a candidate_paths=(
139
- "${repo_root}/AGENTS.md"
140
- "${repo_root}/openspec/AGENT_RULES.md"
141
- "${repo_root}/openspec/AGENTS.md"
142
- "${repo_root}/openspec/project.md"
143
- "${repo_root}/openspec/CONVENTIONS.md"
144
- "${repo_root}/docs/TESTING_AND_SEED_POLICY.md"
145
- )
146
- local -a existing_paths=()
147
- local candidate_path=""
148
-
149
- for candidate_path in "${candidate_paths[@]}"; do
150
- if [[ -f "${candidate_path}" ]]; then
151
- existing_paths+=("${candidate_path}")
152
- fi
153
- done
154
-
155
- if [[ "${#existing_paths[@]}" -eq 0 ]]; then
156
- printf '%s\n' '- No repo-specific context files were found under the expected AGENTS/OpenSpec/testing-doc locations; rely on the current diff and nearby source.'
157
- return 0
158
- fi
159
-
160
- printf '%s\n' "${existing_paths[@]}" | sed 's/^/- `/' | sed 's/$/`/'
161
- }
162
-
163
- PR_CONTEXT_READS_TEXT="$(render_pr_context_reads_text "${PR_REPO_ROOT}")"
164
- PR_CHECK_FAILURES_TEXT="$(jq -r '(.checkFailures + .pendingChecks)[]? | "- " + .' <<<"$RISK_JSON")"
165
- if [[ -z "$PR_CHECK_FAILURES_TEXT" ]]; then
166
- PR_CHECK_FAILURES_TEXT="- none reported"
167
- fi
168
- PR_MISSING_REASONS_TEXT="$(jq -r '.missingReasons[]? | "- " + .' <<<"$RISK_JSON")"
169
- if [[ -z "$PR_MISSING_REASONS_TEXT" ]]; then
170
- PR_MISSING_REASONS_TEXT="- none"
171
- fi
172
- PR_PULL_JSON="$(flow_github_api_repo "${REPO_SLUG}" "pulls/${PR_NUMBER}" 2>/dev/null || printf '{}\n')"
173
- PR_HEAD_SHA="$(jq -r '.head.sha // .headRefOid // ""' <<<"$PR_PULL_JSON")"
174
- PR_MERGEABLE_STATUS="$(jq -r 'if .mergeable == null then "UNKNOWN" else (.mergeable | tostring | ascii_upcase) end' <<<"$PR_PULL_JSON" 2>/dev/null || printf 'UNKNOWN\n')"
175
-
176
- pr_comments_json() {
177
- local review_route="pulls/${PR_NUMBER}/comments"
178
- local issue_route="issues/${PR_NUMBER}/comments"
179
- local payload=""
180
-
181
- if flow_using_gitea; then
182
- payload="$(flow_github_api_repo "${REPO_SLUG}" "${issue_route}" 2>/dev/null || true)"
183
- else
184
- payload="$(flow_github_api_repo "${REPO_SLUG}" "${review_route}" 2>/dev/null || true)"
185
- fi
186
-
187
- if jq -e 'type == "array"' >/dev/null 2>&1 <<<"${payload}"; then
188
- printf '%s\n' "${payload}"
189
- return 0
190
- fi
191
-
192
- printf '[]\n'
193
- }
194
-
195
- pr_issue_comments_json() {
196
- local payload=""
197
- payload="$(flow_github_api_repo "${REPO_SLUG}" "issues/${PR_NUMBER}/comments" 2>/dev/null || true)"
198
- if jq -e 'type == "array"' >/dev/null 2>&1 <<<"${payload}"; then
199
- printf '%s\n' "${payload}"
200
- return 0
201
- fi
202
- printf '[]\n'
203
- }
204
-
205
- PR_REVIEW_FINDINGS_TEXT="$(
206
- pr_comments_json \
207
- | jq -r --arg head_sha "$PR_HEAD_SHA" '
208
- map(select(
209
- (.user.login == "chatgpt-codex-connector[bot]")
210
- and (.body | length > 0)
211
- and ((.commit_id // "") == $head_sha)
212
- ))
213
- | if length == 0 then
214
- "- none"
215
- else
216
- map(
217
- "- " +
218
- (.path // "unknown-path") +
219
- (if .line then ":" + (.line | tostring) else "" end) +
220
- " | " +
221
- ((.body // "") | gsub("\\s+"; " ") | sub("^\\*\\*<sub><sub>!\\[[^\\]]+\\]\\([^)]*\\)</sub></sub>\\s*"; "") | .)
222
- )
223
- | join("\n")
224
- end
225
- '
226
- )"
227
- PR_BLOCKER_SUMMARY_TEXT="$(
228
- pr_issue_comments_json \
229
- | jq -r '
230
- map(select((.body // "") | startswith("## PR final review blocker")))
231
- | if length == 0 then
232
- "- none"
233
- else
234
- (.[-1].body // "")
235
- end
236
- '
237
- )"
238
-
239
- latest_history_artifact_content() {
240
- local artifact_name="${1:?artifact name required}"
241
- local artifact_path=""
242
-
243
- while IFS= read -r artifact_path; do
244
- [[ -n "$artifact_path" ]] || continue
245
- if [[ -f "$artifact_path" ]]; then
246
- cat "$artifact_path"
247
- return 0
248
- fi
249
- done < <(find "$HISTORY_ROOT" -maxdepth 2 -type f -path "${HISTORY_ROOT}/${SESSION}-*/${artifact_name}" | sort -r)
250
-
251
- printf '%s\n' "- none"
252
- }
253
-
254
- PR_LOCAL_HOST_BLOCKER_SUMMARY_TEXT="$(latest_history_artifact_content "host-blocker.md")"
255
-
256
- WORKTREE_OUT="$("${WORKSPACE_DIR}/bin/new-pr-worktree.sh" "$PR_NUMBER" "$PR_HEAD_REF")"
257
- WORKTREE="$(awk -F= '/^WORKTREE=/{print $2}' <<<"$WORKTREE_OUT")"
258
- PR_BASE_REMOTE="$(flow_resolve_forge_primary_remote "${WORKTREE}" "${REPO_SLUG}" 2>/dev/null || true)"
259
- if [[ -z "${PR_BASE_REMOTE}" ]]; then
260
- PR_BASE_REMOTE="origin"
261
- fi
262
- PR_BASE_TRACKING_REF="${PR_BASE_REMOTE}/${PR_BASE_REF}"
263
- PR_HOST_MERGE_STATUS="not-applicable"
264
- PR_HOST_MERGE_SUMMARY_TEXT="- not-applicable"
265
-
266
- materialize_host_merge_repair() {
267
- local merge_output=""
268
- if merge_output="$(git -C "$WORKTREE" merge --no-commit --no-ff "${PR_BASE_TRACKING_REF}" 2>&1)"; then
269
- PR_HOST_MERGE_STATUS="clean"
270
- if [[ -n "$merge_output" ]]; then
271
- PR_HOST_MERGE_SUMMARY_TEXT="$(printf '%s\n' "$merge_output")"
272
- else
273
- PR_HOST_MERGE_SUMMARY_TEXT="- host prepared merge state cleanly with no unresolved conflicts"
274
- fi
275
- return 0
276
- fi
277
-
278
- if git -C "$WORKTREE" ls-files -u | grep -q .; then
279
- PR_HOST_MERGE_STATUS="conflicted"
280
- if [[ -n "$merge_output" ]]; then
281
- PR_HOST_MERGE_SUMMARY_TEXT="$(printf '%s\n' "$merge_output")"
282
- else
283
- PR_HOST_MERGE_SUMMARY_TEXT="- host prepared merge state with unresolved conflicts"
284
- fi
285
- return 0
286
- fi
287
-
288
- if git -C "$WORKTREE" rev-parse -q --verify MERGE_HEAD >/dev/null 2>&1; then
289
- git -C "$WORKTREE" merge --abort >/dev/null 2>&1 || true
290
- fi
291
- printf '%s\n' "$merge_output" >&2
292
- return 1
293
- }
294
-
295
- if [[ "$WORKER_KIND" == "merge-repair" ]]; then
296
- materialize_host_merge_repair
297
- PR_CONFLICT_PATHS_TEXT="$(
298
- unresolved_paths="$(git -C "$WORKTREE" diff --name-only --diff-filter=U 2>/dev/null || true)"
299
- if [[ -n "$unresolved_paths" ]]; then
300
- printf '%s\n' "$unresolved_paths" | sed 's/^/- /'
301
- else
302
- printf '%s\n' "- none detected after host merge preparation"
303
- fi
304
- )"
305
- else
306
- PR_CONFLICT_PATHS_TEXT="$(
307
- (
308
- cd "$WORKTREE"
309
- base_sha="$(git merge-base HEAD "${PR_BASE_TRACKING_REF}" 2>/dev/null || true)"
310
- if [[ -z "$base_sha" ]]; then
311
- printf '%s\n' "- unable to compute merge-base"
312
- exit 0
313
- fi
314
-
315
- conflict_paths="$(
316
- git merge-tree "$base_sha" HEAD "${PR_BASE_TRACKING_REF}" \
317
- | awk '
318
- /^changed in both$/ { capture=1; next }
319
- capture && /^( base| our| their) / {
320
- path=$NF
321
- if (!(path in seen)) {
322
- seen[path]=1
323
- print path
324
- }
325
- }
326
- capture && /^@@ / { capture=0 }
327
- '
328
- )"
329
-
330
- if [[ -n "$conflict_paths" ]]; then
331
- printf '%s\n' "$conflict_paths" | sed 's/^/- /'
332
- else
333
- printf '%s\n' "- none detected"
334
- fi
335
- ) 2>/dev/null
336
- )"
337
- fi
338
-
339
- if [[ -z "$PR_CONFLICT_PATHS_TEXT" ]]; then
340
- PR_CONFLICT_PATHS_TEXT="- none detected"
341
- fi
342
-
343
- mkdir -p "$RUN_DIR"
344
- PROMPT_FILE="${RUN_DIR}/prompt.md"
345
-
346
- PR_NUMBER="$PR_NUMBER" \
347
- PR_TITLE="$PR_TITLE" \
348
- PR_URL="$PR_URL" \
349
- PR_HEAD_REF="$PR_HEAD_REF" \
350
- PR_BASE_REF="$PR_BASE_REF" \
351
- PR_BODY="$PR_BODY" \
352
- PR_RISK="$PR_RISK" \
353
- PR_RISK_REASON="$PR_RISK_REASON" \
354
- PR_LINKED_ISSUE_ID="$PR_LINKED_ISSUE_ID" \
355
- PR_MERGE_STATE_STATUS="$PR_MERGE_STATE_STATUS" \
356
- PR_MERGEABLE_STATUS="$PR_MERGEABLE_STATUS" \
357
- PR_CHECKS_TEXT="$PR_CHECKS_TEXT" \
358
- PR_FILES_TEXT="$PR_FILES_TEXT" \
359
- PR_CONTEXT_READS_TEXT="$PR_CONTEXT_READS_TEXT" \
360
- PR_CHECK_FAILURES_TEXT="$PR_CHECK_FAILURES_TEXT" \
361
- PR_MISSING_REASONS_TEXT="$PR_MISSING_REASONS_TEXT" \
362
- PR_REVIEW_FINDINGS_TEXT="$PR_REVIEW_FINDINGS_TEXT" \
363
- PR_BLOCKER_SUMMARY_TEXT="$PR_BLOCKER_SUMMARY_TEXT" \
364
- PR_LOCAL_HOST_BLOCKER_SUMMARY_TEXT="$PR_LOCAL_HOST_BLOCKER_SUMMARY_TEXT" \
365
- PR_CONFLICT_PATHS_TEXT="$PR_CONFLICT_PATHS_TEXT" \
366
- PR_HOST_MERGE_STATUS="$PR_HOST_MERGE_STATUS" \
367
- PR_HOST_MERGE_SUMMARY_TEXT="$PR_HOST_MERGE_SUMMARY_TEXT" \
368
- PR_REPO_ROOT="$PR_REPO_ROOT" \
369
- PR_DEPENDENCY_SOURCE_ROOT="$PR_DEPENDENCY_SOURCE_ROOT" \
370
- PR_WORKTREE="$WORKTREE" \
371
- PR_BASE_TRACKING_REF="$PR_BASE_TRACKING_REF" \
372
- PR_WEB_PLAYWRIGHT_COMMAND="$WEB_PLAYWRIGHT_COMMAND" \
373
- REPO_SLUG="$REPO_SLUG" \
374
- TEMPLATE_FILE="$TEMPLATE_FILE" \
375
- node <<'EOF' >"$PROMPT_FILE"
376
- const fs = require('fs');
377
- const path = require('path');
378
- const { execFileSync } = require('child_process');
379
-
380
- const template = fs.readFileSync(process.env.TEMPLATE_FILE, 'utf8');
381
- const normalizePath = (value) => String(value || '').replace(/\\/g, '/');
382
- const stripCodeExtension = (value) => normalizePath(value).replace(/\.[cm]?[jt]sx?$/i, '');
383
- const stripTestSuffix = (value) => stripCodeExtension(value).replace(/\.(spec|test)$/i, '');
384
- const lastPathSegments = (value, count = 2) => {
385
- const parts = normalizePath(value).split('/').filter(Boolean);
386
- return parts.slice(-count).join('/');
387
- };
388
- const isTestFile = (value) =>
389
- /(?:^|\/)__tests__\//.test(value) ||
390
- /(?:^|\/)e2e\//.test(value) ||
391
- /\.(?:spec|test)\.[cm]?[jt]sx?$/i.test(value);
392
- const unique = (values) => [...new Set(values.filter(Boolean))];
393
-
394
- let requiredTargetedVerificationText = '- none';
395
- let preApprovedVerificationFallbacksText = '- none';
396
- try {
397
- const worktree = process.env.PR_WORKTREE || '';
398
- const baseTrackingRef = process.env.PR_BASE_TRACKING_REF || `origin/${process.env.PR_BASE_REF || 'main'}`;
399
- if (worktree) {
400
- const changedFiles = execFileSync(
401
- 'git',
402
- ['-C', worktree, 'diff', '--name-only', '--diff-filter=ACMR', `${baseTrackingRef}...HEAD`],
403
- { encoding: 'utf8' },
404
- )
405
- .split('\n')
406
- .map((file) => normalizePath(file).trim())
407
- .filter(Boolean);
408
-
409
- const changedTestFiles = changedFiles.filter(isTestFile);
410
- if (changedTestFiles.length > 0) {
411
- requiredTargetedVerificationText = changedTestFiles
412
- .map((file) => {
413
- const anchors = unique([
414
- stripCodeExtension(lastPathSegments(file, 2)),
415
- stripCodeExtension(path.basename(file)),
416
- path.basename(stripTestSuffix(file)),
417
- ]);
418
- const hints = anchors.map((anchor) => `\`${anchor}\``);
419
- if (/(?:^|\/)e2e\//.test(file)) {
420
- hints.push('scoped `playwright` command');
421
- } else if (/^apps\/mobile\//.test(file)) {
422
- hints.push('scoped `detox` or `maestro` command');
423
- }
424
- return `- ${file} | accepted command anchors: ${hints.join(', ')}`;
425
- })
426
- .join('\n');
427
- }
428
-
429
- const changedWebE2EFiles = changedFiles.filter((file) =>
430
- /^apps\/web\/e2e\/.+\.(?:spec|test)\.[cm]?[jt]sx?$/i.test(file),
431
- );
432
- if (changedWebE2EFiles.length > 0) {
433
- preApprovedVerificationFallbacksText = changedWebE2EFiles
434
- .map((file) => {
435
- const relativeSpecPath = normalizePath(file).replace(/^apps\/web\//, '');
436
- const loopbackCommand = [
437
- 'E2E_WEB_SERVER_COMMAND="pnpm exec next dev --hostname 127.0.0.1 --port 3001"',
438
- 'E2E_BASE_URL="http://127.0.0.1:3001"',
439
- 'bash scripts/with-test-namespace.sh',
440
- process.env.PR_WEB_PLAYWRIGHT_COMMAND || 'pnpm exec playwright test',
441
- relativeSpecPath,
442
- '--project=chromium',
443
- ].join(' ');
444
- return `- ${file} | loopback retry command: \`${loopbackCommand}\``;
445
- })
446
- .join('\n');
447
- }
448
- }
449
- } catch (error) {
450
- requiredTargetedVerificationText =
451
- '- unable to derive targeted verification coverage automatically; inspect changed test files manually';
452
- preApprovedVerificationFallbacksText =
453
- '- unable to derive pre-approved verification fallbacks automatically; inspect changed e2e files manually';
454
- }
455
-
456
- const replacements = {
457
- '{PR_NUMBER}': process.env.PR_NUMBER || '',
458
- '{PR_TITLE}': process.env.PR_TITLE || '',
459
- '{PR_URL}': process.env.PR_URL || '',
460
- '{PR_HEAD_REF}': process.env.PR_HEAD_REF || '',
461
- '{PR_BASE_REF}': process.env.PR_BASE_REF || '',
462
- '{PR_BASE_TRACKING_REF}': process.env.PR_BASE_TRACKING_REF || '',
463
- '{PR_BODY}': process.env.PR_BODY || '',
464
- '{REPO_SLUG}': process.env.REPO_SLUG || '',
465
- '{PR_RISK}': process.env.PR_RISK || '',
466
- '{PR_RISK_REASON}': process.env.PR_RISK_REASON || '',
467
- '{PR_LINKED_ISSUE_ID}': process.env.PR_LINKED_ISSUE_ID || '',
468
- '{PR_MERGE_STATE_STATUS}': process.env.PR_MERGE_STATE_STATUS || '',
469
- '{PR_MERGEABLE_STATUS}': process.env.PR_MERGEABLE_STATUS || '',
470
- '{PR_CHECKS_TEXT}': process.env.PR_CHECKS_TEXT || '',
471
- '{PR_FILES_TEXT}': process.env.PR_FILES_TEXT || '',
472
- '{PR_CONTEXT_READS_TEXT}': process.env.PR_CONTEXT_READS_TEXT || '',
473
- '{PR_CHECK_FAILURES_TEXT}': process.env.PR_CHECK_FAILURES_TEXT || '',
474
- '{PR_MISSING_REASONS_TEXT}': process.env.PR_MISSING_REASONS_TEXT || '',
475
- '{PR_REVIEW_FINDINGS_TEXT}': process.env.PR_REVIEW_FINDINGS_TEXT || '',
476
- '{PR_BLOCKER_SUMMARY_TEXT}': process.env.PR_BLOCKER_SUMMARY_TEXT || '',
477
- '{PR_LOCAL_HOST_BLOCKER_SUMMARY_TEXT}': process.env.PR_LOCAL_HOST_BLOCKER_SUMMARY_TEXT || '',
478
- '{PR_CONFLICT_PATHS_TEXT}': process.env.PR_CONFLICT_PATHS_TEXT || '',
479
- '{PR_HOST_MERGE_STATUS}': process.env.PR_HOST_MERGE_STATUS || '',
480
- '{PR_HOST_MERGE_SUMMARY_TEXT}': process.env.PR_HOST_MERGE_SUMMARY_TEXT || '',
481
- '{REPO_ROOT}': process.env.PR_REPO_ROOT || '',
482
- '{DEPENDENCY_SOURCE_ROOT}': process.env.PR_DEPENDENCY_SOURCE_ROOT || '',
483
- '{PR_REQUIRED_TARGETED_VERIFICATION_TEXT}': requiredTargetedVerificationText,
484
- '{PR_PREAPPROVED_VERIFICATION_FALLBACKS_TEXT}': preApprovedVerificationFallbacksText,
485
- };
486
-
487
- let rendered = template;
488
- for (const [key, value] of Object.entries(replacements)) {
489
- rendered = rendered.split(key).join(value);
490
- }
491
- process.stdout.write(rendered);
492
- EOF
493
-
494
- case "$MODE" in
495
- safe)
496
- F_LOSNING_PR_NUMBER="$PR_NUMBER" \
497
- F_LOSNING_PR_URL="$PR_URL" \
498
- F_LOSNING_PR_HEAD_REF="$PR_HEAD_REF" \
499
- F_LOSNING_ISSUE_ID="$PR_LINKED_ISSUE_ID" \
500
- "${WORKSPACE_DIR}/bin/run-codex-safe.sh" "$SESSION" "$WORKTREE" "$PROMPT_FILE"
501
- ;;
502
- bypass)
503
- F_LOSNING_PR_NUMBER="$PR_NUMBER" \
504
- F_LOSNING_PR_URL="$PR_URL" \
505
- F_LOSNING_PR_HEAD_REF="$PR_HEAD_REF" \
506
- F_LOSNING_ISSUE_ID="$PR_LINKED_ISSUE_ID" \
507
- "${WORKSPACE_DIR}/bin/run-codex-bypass.sh" "$SESSION" "$WORKTREE" "$PROMPT_FILE"
508
- ;;
509
- *)
510
- echo "unknown mode: $MODE" >&2
511
- exit 1
512
- ;;
513
- esac
514
-
515
- launch_success="yes"
516
-
517
- printf 'PR_NUMBER=%s\n' "$PR_NUMBER"
518
- printf 'TITLE=%s\n' "$PR_TITLE"
519
- printf 'URL=%s\n' "$PR_URL"
520
- printf 'HEAD_REF=%s\n' "$PR_HEAD_REF"
521
- printf 'BASE_REF=%s\n' "$PR_BASE_REF"
522
- printf 'LINKED_ISSUE_ID=%s\n' "$PR_LINKED_ISSUE_ID"
523
- printf 'RISK=%s\n' "$PR_RISK"
524
- printf 'RISK_REASON=%s\n' "$PR_RISK_REASON"
525
- printf 'WORKER_KIND=%s\n' "$WORKER_KIND"
526
- printf 'SESSION=%s\n' "$SESSION"
527
- printf 'WORKTREE=%s\n' "$WORKTREE"
528
- printf 'PROMPT=%s\n' "$PROMPT_FILE"
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- PR_NUMBER="${1:?usage: start-pr-merge-repair-worker.sh PR_NUMBER [safe|bypass]}"
5
- MODE="${2:-safe}"
6
- WORKSPACE_DIR="$(cd "$(dirname "$0")/.." && pwd)"
7
-
8
- "${WORKSPACE_DIR}/bin/start-pr-fix-worker.sh" "$PR_NUMBER" "$MODE" merge-repair