agent-control-plane 0.3.0 → 0.6.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 (106) hide show
  1. package/README.md +141 -28
  2. package/assets/workflow-catalog.json +1 -1
  3. package/bin/pr-risk.sh +22 -7
  4. package/bin/sync-pr-labels.sh +1 -1
  5. package/hooks/heartbeat-hooks.sh +125 -12
  6. package/hooks/issue-reconcile-hooks.sh +1 -1
  7. package/hooks/pr-reconcile-hooks.sh +1 -1
  8. package/npm/bin/agent-control-plane.js +257 -59
  9. package/package.json +39 -32
  10. package/tools/bin/debug-session.sh +106 -0
  11. package/tools/bin/flow-config-lib.sh +1203 -60
  12. package/tools/bin/flow-runtime-doctor-linux.sh +136 -0
  13. package/tools/bin/flow-runtime-doctor.sh +5 -1
  14. package/tools/bin/flow-shell-lib.sh +32 -0
  15. package/tools/bin/github-core-rate-limit-state.sh +77 -0
  16. package/tools/bin/github-write-outbox.sh +470 -0
  17. package/tools/bin/heartbeat-loop-scheduling-lib.sh +7 -7
  18. package/tools/bin/heartbeat-safe-auto.sh +42 -0
  19. package/tools/bin/install-project-launchd.sh +17 -2
  20. package/tools/bin/install-project-systemd.sh +255 -0
  21. package/tools/bin/project-init.sh +21 -1
  22. package/tools/bin/project-launchd-bootstrap.sh +5 -1
  23. package/tools/bin/project-runtimectl.sh +91 -2
  24. package/tools/bin/project-systemd-bootstrap.sh +74 -0
  25. package/tools/bin/scaffold-profile.sh +61 -3
  26. package/tools/bin/uninstall-project-systemd.sh +87 -0
  27. package/tools/dashboard/app.js +228 -6
  28. package/tools/dashboard/dashboard_snapshot.py +55 -0
  29. package/tools/dashboard/issue_queue_state.py +101 -0
  30. package/tools/dashboard/server.py +123 -1
  31. package/tools/dashboard/styles.css +526 -455
  32. package/tools/templates/pr-fix-template.md +3 -1
  33. package/tools/templates/pr-merge-repair-template.md +2 -1
  34. package/references/architecture.md +0 -217
  35. package/references/commands.md +0 -128
  36. package/references/control-plane-map.md +0 -124
  37. package/references/docs-map.md +0 -73
  38. package/references/release-checklist.md +0 -65
  39. package/references/repo-map.md +0 -36
  40. package/tools/bin/agent-cleanup-worktree +0 -247
  41. package/tools/bin/agent-github-update-labels +0 -71
  42. package/tools/bin/agent-init-worktree +0 -216
  43. package/tools/bin/agent-project-archive-run +0 -52
  44. package/tools/bin/agent-project-capture-worker +0 -46
  45. package/tools/bin/agent-project-catch-up-issue-pr-links +0 -118
  46. package/tools/bin/agent-project-catch-up-merged-prs +0 -194
  47. package/tools/bin/agent-project-catch-up-scheduled-issue-retries +0 -123
  48. package/tools/bin/agent-project-cleanup-session +0 -513
  49. package/tools/bin/agent-project-detached-launch +0 -127
  50. package/tools/bin/agent-project-heartbeat-loop +0 -1029
  51. package/tools/bin/agent-project-open-issue-worktree +0 -89
  52. package/tools/bin/agent-project-open-pr-worktree +0 -80
  53. package/tools/bin/agent-project-publish-issue-pr +0 -465
  54. package/tools/bin/agent-project-reconcile-issue-session +0 -1398
  55. package/tools/bin/agent-project-reconcile-pr-session +0 -1230
  56. package/tools/bin/agent-project-retry-state +0 -147
  57. package/tools/bin/agent-project-run-claude-session +0 -805
  58. package/tools/bin/agent-project-run-codex-resilient +0 -955
  59. package/tools/bin/agent-project-run-codex-session +0 -435
  60. package/tools/bin/agent-project-run-kilo-session +0 -369
  61. package/tools/bin/agent-project-run-ollama-session +0 -658
  62. package/tools/bin/agent-project-run-openclaw-session +0 -1309
  63. package/tools/bin/agent-project-run-opencode-session +0 -377
  64. package/tools/bin/agent-project-run-pi-session +0 -479
  65. package/tools/bin/agent-project-sync-anchor-repo +0 -139
  66. package/tools/bin/agent-project-worker-status +0 -188
  67. package/tools/bin/branch-verification-guard.sh +0 -364
  68. package/tools/bin/capture-worker.sh +0 -18
  69. package/tools/bin/cleanup-worktree.sh +0 -52
  70. package/tools/bin/codex-quota +0 -31
  71. package/tools/bin/create-follow-up-issue.sh +0 -114
  72. package/tools/bin/dashboard-launchd-bootstrap.sh +0 -50
  73. package/tools/bin/issue-publish-localization-guard.sh +0 -142
  74. package/tools/bin/issue-publish-scope-guard.sh +0 -242
  75. package/tools/bin/issue-requires-local-workspace-install.sh +0 -31
  76. package/tools/bin/issue-resource-class.sh +0 -12
  77. package/tools/bin/kick-scheduler.sh +0 -75
  78. package/tools/bin/label-follow-up-issues.sh +0 -14
  79. package/tools/bin/new-pr-worktree.sh +0 -50
  80. package/tools/bin/new-worktree.sh +0 -49
  81. package/tools/bin/pr-risk.sh +0 -12
  82. package/tools/bin/prepare-worktree.sh +0 -142
  83. package/tools/bin/provider-cooldown-state.sh +0 -204
  84. package/tools/bin/publish-issue-worker.sh +0 -31
  85. package/tools/bin/reconcile-bootstrap-lib.sh +0 -113
  86. package/tools/bin/reconcile-issue-worker.sh +0 -34
  87. package/tools/bin/reconcile-pr-worker.sh +0 -34
  88. package/tools/bin/record-verification.sh +0 -71
  89. package/tools/bin/render-flow-config.sh +0 -98
  90. package/tools/bin/resident-issue-controller-lib.sh +0 -448
  91. package/tools/bin/resident-issue-queue-status.py +0 -35
  92. package/tools/bin/retry-state.sh +0 -31
  93. package/tools/bin/reuse-issue-worktree.sh +0 -121
  94. package/tools/bin/run-codex-bypass.sh +0 -3
  95. package/tools/bin/run-codex-safe.sh +0 -3
  96. package/tools/bin/run-codex-task.sh +0 -280
  97. package/tools/bin/serve-dashboard.sh +0 -5
  98. package/tools/bin/split-retained-slice.sh +0 -124
  99. package/tools/bin/start-issue-worker.sh +0 -943
  100. package/tools/bin/start-pr-fix-worker.sh +0 -491
  101. package/tools/bin/start-pr-merge-repair-worker.sh +0 -8
  102. package/tools/bin/start-pr-review-worker.sh +0 -261
  103. package/tools/bin/start-resident-issue-loop.sh +0 -499
  104. package/tools/bin/update-github-labels.sh +0 -14
  105. package/tools/bin/worker-status.sh +0 -19
  106. package/tools/bin/workflow-catalog.sh +0 -77
@@ -1,491 +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}")"
173
- PR_HEAD_SHA="$(jq -r '.head.sha' <<<"$PR_PULL_JSON")"
174
- PR_MERGEABLE_STATUS="$(jq -r 'if .mergeable == null then "UNKNOWN" else (.mergeable | tostring | ascii_upcase) end' <<<"$PR_PULL_JSON")"
175
- PR_REVIEW_FINDINGS_TEXT="$(
176
- flow_github_api_repo "${REPO_SLUG}" "pulls/${PR_NUMBER}/comments" \
177
- | jq -r --arg head_sha "$PR_HEAD_SHA" '
178
- map(select(
179
- (.user.login == "chatgpt-codex-connector[bot]")
180
- and (.body | length > 0)
181
- and ((.commit_id // "") == $head_sha)
182
- ))
183
- | if length == 0 then
184
- "- none"
185
- else
186
- map(
187
- "- " +
188
- (.path // "unknown-path") +
189
- (if .line then ":" + (.line | tostring) else "" end) +
190
- " | " +
191
- ((.body // "") | gsub("\\s+"; " ") | sub("^\\*\\*<sub><sub>!\\[[^\\]]+\\]\\([^)]*\\)</sub></sub>\\s*"; "") | .)
192
- )
193
- | join("\n")
194
- end
195
- '
196
- )"
197
- PR_BLOCKER_SUMMARY_TEXT="$(
198
- flow_github_api_repo "${REPO_SLUG}" "issues/${PR_NUMBER}/comments" \
199
- | jq -r '
200
- map(select((.body // "") | startswith("## PR final review blocker")))
201
- | if length == 0 then
202
- "- none"
203
- else
204
- (.[-1].body // "")
205
- end
206
- '
207
- )"
208
-
209
- latest_history_artifact_content() {
210
- local artifact_name="${1:?artifact name required}"
211
- local artifact_path=""
212
-
213
- while IFS= read -r artifact_path; do
214
- [[ -n "$artifact_path" ]] || continue
215
- if [[ -f "$artifact_path" ]]; then
216
- cat "$artifact_path"
217
- return 0
218
- fi
219
- done < <(find "$HISTORY_ROOT" -maxdepth 2 -type f -path "${HISTORY_ROOT}/${SESSION}-*/${artifact_name}" | sort -r)
220
-
221
- printf '%s\n' "- none"
222
- }
223
-
224
- PR_LOCAL_HOST_BLOCKER_SUMMARY_TEXT="$(latest_history_artifact_content "host-blocker.md")"
225
-
226
- WORKTREE_OUT="$("${WORKSPACE_DIR}/bin/new-pr-worktree.sh" "$PR_NUMBER" "$PR_HEAD_REF")"
227
- WORKTREE="$(awk -F= '/^WORKTREE=/{print $2}' <<<"$WORKTREE_OUT")"
228
- PR_HOST_MERGE_STATUS="not-applicable"
229
- PR_HOST_MERGE_SUMMARY_TEXT="- not-applicable"
230
-
231
- materialize_host_merge_repair() {
232
- local merge_output=""
233
- if merge_output="$(git -C "$WORKTREE" merge --no-commit --no-ff "origin/${PR_BASE_REF}" 2>&1)"; then
234
- PR_HOST_MERGE_STATUS="clean"
235
- if [[ -n "$merge_output" ]]; then
236
- PR_HOST_MERGE_SUMMARY_TEXT="$(printf '%s\n' "$merge_output")"
237
- else
238
- PR_HOST_MERGE_SUMMARY_TEXT="- host prepared merge state cleanly with no unresolved conflicts"
239
- fi
240
- return 0
241
- fi
242
-
243
- if git -C "$WORKTREE" ls-files -u | grep -q .; then
244
- PR_HOST_MERGE_STATUS="conflicted"
245
- if [[ -n "$merge_output" ]]; then
246
- PR_HOST_MERGE_SUMMARY_TEXT="$(printf '%s\n' "$merge_output")"
247
- else
248
- PR_HOST_MERGE_SUMMARY_TEXT="- host prepared merge state with unresolved conflicts"
249
- fi
250
- return 0
251
- fi
252
-
253
- if git -C "$WORKTREE" rev-parse -q --verify MERGE_HEAD >/dev/null 2>&1; then
254
- git -C "$WORKTREE" merge --abort >/dev/null 2>&1 || true
255
- fi
256
- printf '%s\n' "$merge_output" >&2
257
- return 1
258
- }
259
-
260
- if [[ "$WORKER_KIND" == "merge-repair" ]]; then
261
- materialize_host_merge_repair
262
- PR_CONFLICT_PATHS_TEXT="$(
263
- unresolved_paths="$(git -C "$WORKTREE" diff --name-only --diff-filter=U 2>/dev/null || true)"
264
- if [[ -n "$unresolved_paths" ]]; then
265
- printf '%s\n' "$unresolved_paths" | sed 's/^/- /'
266
- else
267
- printf '%s\n' "- none detected after host merge preparation"
268
- fi
269
- )"
270
- else
271
- PR_CONFLICT_PATHS_TEXT="$(
272
- (
273
- cd "$WORKTREE"
274
- base_sha="$(git merge-base HEAD "origin/${PR_BASE_REF}" 2>/dev/null || true)"
275
- if [[ -z "$base_sha" ]]; then
276
- printf '%s\n' "- unable to compute merge-base"
277
- exit 0
278
- fi
279
-
280
- conflict_paths="$(
281
- git merge-tree "$base_sha" HEAD "origin/${PR_BASE_REF}" \
282
- | awk '
283
- /^changed in both$/ { capture=1; next }
284
- capture && /^( base| our| their) / {
285
- path=$NF
286
- if (!(path in seen)) {
287
- seen[path]=1
288
- print path
289
- }
290
- }
291
- capture && /^@@ / { capture=0 }
292
- '
293
- )"
294
-
295
- if [[ -n "$conflict_paths" ]]; then
296
- printf '%s\n' "$conflict_paths" | sed 's/^/- /'
297
- else
298
- printf '%s\n' "- none detected"
299
- fi
300
- ) 2>/dev/null
301
- )"
302
- fi
303
-
304
- if [[ -z "$PR_CONFLICT_PATHS_TEXT" ]]; then
305
- PR_CONFLICT_PATHS_TEXT="- none detected"
306
- fi
307
-
308
- mkdir -p "$RUN_DIR"
309
- PROMPT_FILE="${RUN_DIR}/prompt.md"
310
-
311
- PR_NUMBER="$PR_NUMBER" \
312
- PR_TITLE="$PR_TITLE" \
313
- PR_URL="$PR_URL" \
314
- PR_HEAD_REF="$PR_HEAD_REF" \
315
- PR_BASE_REF="$PR_BASE_REF" \
316
- PR_BODY="$PR_BODY" \
317
- PR_RISK="$PR_RISK" \
318
- PR_RISK_REASON="$PR_RISK_REASON" \
319
- PR_LINKED_ISSUE_ID="$PR_LINKED_ISSUE_ID" \
320
- PR_MERGE_STATE_STATUS="$PR_MERGE_STATE_STATUS" \
321
- PR_MERGEABLE_STATUS="$PR_MERGEABLE_STATUS" \
322
- PR_CHECKS_TEXT="$PR_CHECKS_TEXT" \
323
- PR_FILES_TEXT="$PR_FILES_TEXT" \
324
- PR_CONTEXT_READS_TEXT="$PR_CONTEXT_READS_TEXT" \
325
- PR_CHECK_FAILURES_TEXT="$PR_CHECK_FAILURES_TEXT" \
326
- PR_MISSING_REASONS_TEXT="$PR_MISSING_REASONS_TEXT" \
327
- PR_REVIEW_FINDINGS_TEXT="$PR_REVIEW_FINDINGS_TEXT" \
328
- PR_BLOCKER_SUMMARY_TEXT="$PR_BLOCKER_SUMMARY_TEXT" \
329
- PR_LOCAL_HOST_BLOCKER_SUMMARY_TEXT="$PR_LOCAL_HOST_BLOCKER_SUMMARY_TEXT" \
330
- PR_CONFLICT_PATHS_TEXT="$PR_CONFLICT_PATHS_TEXT" \
331
- PR_HOST_MERGE_STATUS="$PR_HOST_MERGE_STATUS" \
332
- PR_HOST_MERGE_SUMMARY_TEXT="$PR_HOST_MERGE_SUMMARY_TEXT" \
333
- PR_REPO_ROOT="$PR_REPO_ROOT" \
334
- PR_DEPENDENCY_SOURCE_ROOT="$PR_DEPENDENCY_SOURCE_ROOT" \
335
- PR_WORKTREE="$WORKTREE" \
336
- PR_WEB_PLAYWRIGHT_COMMAND="$WEB_PLAYWRIGHT_COMMAND" \
337
- REPO_SLUG="$REPO_SLUG" \
338
- TEMPLATE_FILE="$TEMPLATE_FILE" \
339
- node <<'EOF' >"$PROMPT_FILE"
340
- const fs = require('fs');
341
- const path = require('path');
342
- const { execFileSync } = require('child_process');
343
-
344
- const template = fs.readFileSync(process.env.TEMPLATE_FILE, 'utf8');
345
- const normalizePath = (value) => String(value || '').replace(/\\/g, '/');
346
- const stripCodeExtension = (value) => normalizePath(value).replace(/\.[cm]?[jt]sx?$/i, '');
347
- const stripTestSuffix = (value) => stripCodeExtension(value).replace(/\.(spec|test)$/i, '');
348
- const lastPathSegments = (value, count = 2) => {
349
- const parts = normalizePath(value).split('/').filter(Boolean);
350
- return parts.slice(-count).join('/');
351
- };
352
- const isTestFile = (value) =>
353
- /(?:^|\/)__tests__\//.test(value) ||
354
- /(?:^|\/)e2e\//.test(value) ||
355
- /\.(?:spec|test)\.[cm]?[jt]sx?$/i.test(value);
356
- const unique = (values) => [...new Set(values.filter(Boolean))];
357
-
358
- let requiredTargetedVerificationText = '- none';
359
- let preApprovedVerificationFallbacksText = '- none';
360
- try {
361
- const worktree = process.env.PR_WORKTREE || '';
362
- const baseRef = process.env.PR_BASE_REF || 'main';
363
- if (worktree) {
364
- const changedFiles = execFileSync(
365
- 'git',
366
- ['-C', worktree, 'diff', '--name-only', '--diff-filter=ACMR', `origin/${baseRef}...HEAD`],
367
- { encoding: 'utf8' },
368
- )
369
- .split('\n')
370
- .map((file) => normalizePath(file).trim())
371
- .filter(Boolean);
372
-
373
- const changedTestFiles = changedFiles.filter(isTestFile);
374
- if (changedTestFiles.length > 0) {
375
- requiredTargetedVerificationText = changedTestFiles
376
- .map((file) => {
377
- const anchors = unique([
378
- stripCodeExtension(lastPathSegments(file, 2)),
379
- stripCodeExtension(path.basename(file)),
380
- path.basename(stripTestSuffix(file)),
381
- ]);
382
- const hints = anchors.map((anchor) => `\`${anchor}\``);
383
- if (/(?:^|\/)e2e\//.test(file)) {
384
- hints.push('scoped `playwright` command');
385
- } else if (/^apps\/mobile\//.test(file)) {
386
- hints.push('scoped `detox` or `maestro` command');
387
- }
388
- return `- ${file} | accepted command anchors: ${hints.join(', ')}`;
389
- })
390
- .join('\n');
391
- }
392
-
393
- const changedWebE2EFiles = changedFiles.filter((file) =>
394
- /^apps\/web\/e2e\/.+\.(?:spec|test)\.[cm]?[jt]sx?$/i.test(file),
395
- );
396
- if (changedWebE2EFiles.length > 0) {
397
- preApprovedVerificationFallbacksText = changedWebE2EFiles
398
- .map((file) => {
399
- const relativeSpecPath = normalizePath(file).replace(/^apps\/web\//, '');
400
- const loopbackCommand = [
401
- 'E2E_WEB_SERVER_COMMAND="pnpm exec next dev --hostname 127.0.0.1 --port 3001"',
402
- 'E2E_BASE_URL="http://127.0.0.1:3001"',
403
- 'bash scripts/with-test-namespace.sh',
404
- process.env.PR_WEB_PLAYWRIGHT_COMMAND || 'pnpm exec playwright test',
405
- relativeSpecPath,
406
- '--project=chromium',
407
- ].join(' ');
408
- return `- ${file} | loopback retry command: \`${loopbackCommand}\``;
409
- })
410
- .join('\n');
411
- }
412
- }
413
- } catch (error) {
414
- requiredTargetedVerificationText =
415
- '- unable to derive targeted verification coverage automatically; inspect changed test files manually';
416
- preApprovedVerificationFallbacksText =
417
- '- unable to derive pre-approved verification fallbacks automatically; inspect changed e2e files manually';
418
- }
419
-
420
- const replacements = {
421
- '{PR_NUMBER}': process.env.PR_NUMBER || '',
422
- '{PR_TITLE}': process.env.PR_TITLE || '',
423
- '{PR_URL}': process.env.PR_URL || '',
424
- '{PR_HEAD_REF}': process.env.PR_HEAD_REF || '',
425
- '{PR_BASE_REF}': process.env.PR_BASE_REF || '',
426
- '{PR_BODY}': process.env.PR_BODY || '',
427
- '{REPO_SLUG}': process.env.REPO_SLUG || '',
428
- '{PR_RISK}': process.env.PR_RISK || '',
429
- '{PR_RISK_REASON}': process.env.PR_RISK_REASON || '',
430
- '{PR_LINKED_ISSUE_ID}': process.env.PR_LINKED_ISSUE_ID || '',
431
- '{PR_MERGE_STATE_STATUS}': process.env.PR_MERGE_STATE_STATUS || '',
432
- '{PR_MERGEABLE_STATUS}': process.env.PR_MERGEABLE_STATUS || '',
433
- '{PR_CHECKS_TEXT}': process.env.PR_CHECKS_TEXT || '',
434
- '{PR_FILES_TEXT}': process.env.PR_FILES_TEXT || '',
435
- '{PR_CONTEXT_READS_TEXT}': process.env.PR_CONTEXT_READS_TEXT || '',
436
- '{PR_CHECK_FAILURES_TEXT}': process.env.PR_CHECK_FAILURES_TEXT || '',
437
- '{PR_MISSING_REASONS_TEXT}': process.env.PR_MISSING_REASONS_TEXT || '',
438
- '{PR_REVIEW_FINDINGS_TEXT}': process.env.PR_REVIEW_FINDINGS_TEXT || '',
439
- '{PR_BLOCKER_SUMMARY_TEXT}': process.env.PR_BLOCKER_SUMMARY_TEXT || '',
440
- '{PR_LOCAL_HOST_BLOCKER_SUMMARY_TEXT}': process.env.PR_LOCAL_HOST_BLOCKER_SUMMARY_TEXT || '',
441
- '{PR_CONFLICT_PATHS_TEXT}': process.env.PR_CONFLICT_PATHS_TEXT || '',
442
- '{PR_HOST_MERGE_STATUS}': process.env.PR_HOST_MERGE_STATUS || '',
443
- '{PR_HOST_MERGE_SUMMARY_TEXT}': process.env.PR_HOST_MERGE_SUMMARY_TEXT || '',
444
- '{REPO_ROOT}': process.env.PR_REPO_ROOT || '',
445
- '{DEPENDENCY_SOURCE_ROOT}': process.env.PR_DEPENDENCY_SOURCE_ROOT || '',
446
- '{PR_REQUIRED_TARGETED_VERIFICATION_TEXT}': requiredTargetedVerificationText,
447
- '{PR_PREAPPROVED_VERIFICATION_FALLBACKS_TEXT}': preApprovedVerificationFallbacksText,
448
- };
449
-
450
- let rendered = template;
451
- for (const [key, value] of Object.entries(replacements)) {
452
- rendered = rendered.split(key).join(value);
453
- }
454
- process.stdout.write(rendered);
455
- EOF
456
-
457
- case "$MODE" in
458
- safe)
459
- F_LOSNING_PR_NUMBER="$PR_NUMBER" \
460
- F_LOSNING_PR_URL="$PR_URL" \
461
- F_LOSNING_PR_HEAD_REF="$PR_HEAD_REF" \
462
- F_LOSNING_ISSUE_ID="$PR_LINKED_ISSUE_ID" \
463
- "${WORKSPACE_DIR}/bin/run-codex-safe.sh" "$SESSION" "$WORKTREE" "$PROMPT_FILE"
464
- ;;
465
- bypass)
466
- F_LOSNING_PR_NUMBER="$PR_NUMBER" \
467
- F_LOSNING_PR_URL="$PR_URL" \
468
- F_LOSNING_PR_HEAD_REF="$PR_HEAD_REF" \
469
- F_LOSNING_ISSUE_ID="$PR_LINKED_ISSUE_ID" \
470
- "${WORKSPACE_DIR}/bin/run-codex-bypass.sh" "$SESSION" "$WORKTREE" "$PROMPT_FILE"
471
- ;;
472
- *)
473
- echo "unknown mode: $MODE" >&2
474
- exit 1
475
- ;;
476
- esac
477
-
478
- launch_success="yes"
479
-
480
- printf 'PR_NUMBER=%s\n' "$PR_NUMBER"
481
- printf 'TITLE=%s\n' "$PR_TITLE"
482
- printf 'URL=%s\n' "$PR_URL"
483
- printf 'HEAD_REF=%s\n' "$PR_HEAD_REF"
484
- printf 'BASE_REF=%s\n' "$PR_BASE_REF"
485
- printf 'LINKED_ISSUE_ID=%s\n' "$PR_LINKED_ISSUE_ID"
486
- printf 'RISK=%s\n' "$PR_RISK"
487
- printf 'RISK_REASON=%s\n' "$PR_RISK_REASON"
488
- printf 'WORKER_KIND=%s\n' "$WORKER_KIND"
489
- printf 'SESSION=%s\n' "$SESSION"
490
- printf 'WORKTREE=%s\n' "$WORKTREE"
491
- 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