From fe88c040f2c8fd8aa3eed463f044d3726d326bd6 Mon Sep 17 00:00:00 2001 From: Wayne Sun Date: Wed, 29 Jul 2026 17:03:58 -0400 Subject: [PATCH] fix(#4718)!: remove inline pre-script calls and scaffold script copies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The inline Validate inputs steps ran pre-code.sh/pre-fix.sh on the bare runner while the harness pre_script ran the agents-repo copy inside fullsend run — two executions per run from two independently maintained copies (#4718, #5667). With the pre-script output protocol in fullsend run, the CLI owns the skip decision, so: - reusable-code.yml, reusable-fix.yml, and both inlined stages in reusable-dispatch.yml lose their Validate inputs steps and every steps.validate.outputs.skipped gate, converging toward the generic harness-run job shape (ADR 0061). That job does not yet carry the code/fix pre-script env contract, so the shapes are not yet interchangeable. - COMMENT_BODY moves onto the Run code agent step env so the pre-script inside fullsend run can read it (env-quoted, never interpolated into run: blocks). Its --force override is not end-to-end yet: the route job's own Check for existing PRs step gates stage=code first and does not honor --force. - The scaffold copies of pre-code.sh, pre-fix.sh, and pre-code-test.sh are deleted; fullsend-ai/agents becomes the single source (closes the #5667 scope). Companion scripts (install-precommit-tools.sh, resolve-precommit-tools.py) stay — the agents-repo pre-fix.sh resolves them via the workspace fallback. Fix-side enforcement (input validation, the 10,000-byte HUMAN_INSTRUCTION cap, and the bot-5/human-10 iteration caps) is exit-code-based in the agents-repo pre-fix.sh, so it is version-independent and unaffected by the protocol rollout. It does lose its fail-fast position: those checks now run inside fullsend run, after GCP setup and token mint, rather than before them. BREAKING CHANGE: the scaffold embed no longer ships scripts/pre-code.sh or scripts/pre-fix.sh. Installations referencing these paths directly must use the fullsend-ai/agents copies via harness pre_script. Assisted-by: Claude (fix, review), Grok (review) Signed-off-by: Wayne Sun --- .github/workflows/reusable-code.yml | 22 +- .github/workflows/reusable-dispatch.yml | 32 +- .github/workflows/reusable-fix.yml | 10 - Makefile | 1 - docs/guides/dev/cli-internals.md | 1 - docs/guides/user/customizing-agents.md | 16 + .../fullsend-repo/scripts/pre-code-test.sh | 471 ------------------ .../fullsend-repo/scripts/pre-code.sh | 183 ------- .../scaffold/fullsend-repo/scripts/pre-fix.sh | 166 ------ internal/scaffold/scaffold.go | 1 - 10 files changed, 32 insertions(+), 871 deletions(-) delete mode 100644 internal/scaffold/fullsend-repo/scripts/pre-code-test.sh delete mode 100755 internal/scaffold/fullsend-repo/scripts/pre-code.sh delete mode 100644 internal/scaffold/fullsend-repo/scripts/pre-fix.sh diff --git a/.github/workflows/reusable-code.yml b/.github/workflows/reusable-code.yml index de71e554d9..512827ba7d 100644 --- a/.github/workflows/reusable-code.yml +++ b/.github/workflows/reusable-code.yml @@ -155,18 +155,7 @@ jobs: fetch-depth: 0 persist-credentials: false - - name: Validate inputs - id: validate - env: - ISSUE_NUMBER: ${{ fromJSON(inputs.event_payload).issue.number }} - REPO_FULL_NAME: ${{ inputs.source_repo }} - GITHUB_ISSUE_URL: ${{ fromJSON(inputs.event_payload).issue.html_url }} - COMMENT_BODY: ${{ fromJSON(inputs.event_payload).comment.body }} - FULLSEND_DIR: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }} - run: bash "${FULLSEND_DIR:+$FULLSEND_DIR/}scripts/pre-code.sh" - - name: Setup GCP and prepare credentials - if: steps.validate.outputs.skipped != 'true' uses: ./.defaults/.github/actions/setup-gcp with: gcp_wif_provider: ${{ secrets.FULLSEND_GCP_WIF_PROVIDER }} @@ -174,7 +163,6 @@ jobs: fullsend-dir: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }} - name: Resolve bot identity - if: steps.validate.outputs.skipped != 'true' env: GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | @@ -190,7 +178,6 @@ jobs: git config --global user.name "${BOT_LOGIN}" - name: Setup agent environment - if: steps.validate.outputs.skipped != 'true' env: AGENT_PREFIX: CODE_ CODE_TARGET_REPO_DIR: target-repo @@ -200,11 +187,18 @@ jobs: run: bash .github/scripts/setup-agent-env.sh - name: Run code agent - if: steps.validate.outputs.skipped != 'true' uses: ./.defaults/ env: GITHUB_ISSUE_URL: ${{ fromJSON(inputs.event_payload).issue.html_url }} ISSUE_NUMBER: ${{ fromJSON(inputs.event_payload).issue.number }} + # Reaches the harness pre-script inside fullsend run, which reads it + # for its --force override of the existing-PR check (pre-script + # output protocol, issue #4718). The override is not end-to-end: + # the dispatcher's own "Check for existing PRs" step gates + # stage=code first and does not honor --force, so a /fs-code + # --force comment is stopped before this value is ever read. + # env-quoted, never interpolated into run: blocks. + COMMENT_BODY: ${{ fromJSON(inputs.event_payload).comment.body }} REPO_FULL_NAME: ${{ inputs.source_repo }} CODE_ALLOWED_TARGET_BRANCHES: '' TARGET_BRANCH: main diff --git a/.github/workflows/reusable-dispatch.yml b/.github/workflows/reusable-dispatch.yml index 6c2e88edfe..bbf9d53996 100644 --- a/.github/workflows/reusable-dispatch.yml +++ b/.github/workflows/reusable-dispatch.yml @@ -691,18 +691,7 @@ jobs: fetch-depth: 0 persist-credentials: false - - name: Validate inputs - id: validate - env: - ISSUE_NUMBER: ${{ fromJSON(needs.route.outputs.event_payload).issue.number }} - REPO_FULL_NAME: ${{ github.repository }} - GITHUB_ISSUE_URL: ${{ fromJSON(needs.route.outputs.event_payload).issue.html_url }} - COMMENT_BODY: ${{ fromJSON(needs.route.outputs.event_payload).comment.body }} - FULLSEND_DIR: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }} - run: bash "${FULLSEND_DIR:+$FULLSEND_DIR/}scripts/pre-code.sh" - - name: Setup GCP and prepare credentials - if: steps.validate.outputs.skipped != 'true' uses: ./.defaults/.github/actions/setup-gcp with: gcp_wif_provider: ${{ secrets.FULLSEND_GCP_WIF_PROVIDER }} @@ -710,7 +699,6 @@ jobs: fullsend-dir: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }} - name: Resolve bot identity - if: steps.validate.outputs.skipped != 'true' env: GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | @@ -726,7 +714,6 @@ jobs: git config --global user.name "${BOT_LOGIN}" - name: Setup agent environment - if: steps.validate.outputs.skipped != 'true' env: AGENT_PREFIX: CODE_ CODE_TARGET_REPO_DIR: target-repo @@ -736,11 +723,18 @@ jobs: run: bash .github/scripts/setup-agent-env.sh - name: Run code agent - if: steps.validate.outputs.skipped != 'true' uses: ./.defaults/ env: GITHUB_ISSUE_URL: ${{ fromJSON(needs.route.outputs.event_payload).issue.html_url }} ISSUE_NUMBER: ${{ fromJSON(needs.route.outputs.event_payload).issue.number }} + # Reaches the harness pre-script inside fullsend run, which reads it + # for its --force override of the existing-PR check (pre-script + # output protocol, issue #4718). The override is not end-to-end: + # the route job's own "Check for existing PRs" step above gates + # stage=code first and does not honor --force, so a /fs-code + # --force comment is stopped before this value is ever read. + # env-quoted, never interpolated into run: blocks. + COMMENT_BODY: ${{ fromJSON(needs.route.outputs.event_payload).comment.body }} REPO_FULL_NAME: ${{ github.repository }} CODE_ALLOWED_TARGET_BRANCHES: '' TARGET_BRANCH: main @@ -1108,16 +1102,6 @@ jobs: fi echo "review_file=${REVIEW_FILE}" >> "${GITHUB_OUTPUT}" - - name: Validate inputs - env: - PR_NUMBER: ${{ steps.context.outputs.pr_number }} - REPO_FULL_NAME: ${{ github.repository }} - TRIGGER_SOURCE: ${{ needs.route.outputs.trigger_source }} - FIX_ITERATION: ${{ steps.context.outputs.iteration }} - HUMAN_INSTRUCTION: ${{ steps.context.outputs.instruction }} - FULLSEND_DIR: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }} - run: bash "${FULLSEND_DIR:+$FULLSEND_DIR/}scripts/pre-fix.sh" - - name: Setup GCP and prepare credentials uses: ./.defaults/.github/actions/setup-gcp with: diff --git a/.github/workflows/reusable-fix.yml b/.github/workflows/reusable-fix.yml index c8e27789fd..a1b510b714 100644 --- a/.github/workflows/reusable-fix.yml +++ b/.github/workflows/reusable-fix.yml @@ -368,16 +368,6 @@ jobs: fi echo "review_file=${REVIEW_FILE}" >> "${GITHUB_OUTPUT}" - - name: Validate inputs - env: - PR_NUMBER: ${{ steps.context.outputs.pr_number }} - REPO_FULL_NAME: ${{ inputs.source_repo }} - TRIGGER_SOURCE: ${{ inputs.trigger_source }} - FIX_ITERATION: ${{ steps.context.outputs.iteration }} - HUMAN_INSTRUCTION: ${{ steps.context.outputs.instruction }} - FULLSEND_DIR: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }} - run: bash "${FULLSEND_DIR:+$FULLSEND_DIR/}scripts/pre-fix.sh" - - name: Setup GCP and prepare credentials uses: ./.defaults/.github/actions/setup-gcp with: diff --git a/Makefile b/Makefile index 4f0371a20b..cb2afc7409 100644 --- a/Makefile +++ b/Makefile @@ -181,7 +181,6 @@ script-test: $(call run-timed,bash scripts/check-e2e-authorization-test.sh) $(call run-timed,bash internal/scaffold/fullsend-repo/scripts/reconcile-repos-test.sh) $(call run-timed,bash internal/scaffold/fullsend-repo/scripts/pre-fetch-prior-review-test.sh) - $(call run-timed,bash internal/scaffold/fullsend-repo/scripts/pre-code-test.sh) $(call run-timed,python3 skills/topissues/scripts/topissues_test.py) $(call run-timed,python3 -m pytest gitlint_rules_test.py -v) diff --git a/docs/guides/dev/cli-internals.md b/docs/guides/dev/cli-internals.md index 2cc16c2343..5df57dbf75 100644 --- a/docs/guides/dev/cli-internals.md +++ b/docs/guides/dev/cli-internals.md @@ -622,7 +622,6 @@ Since `embed.FS` doesn't preserve Unix permissions, executable files are tracked var executableFiles = map[string]struct{}{ "scripts/fullsend-check-output": {}, "scripts/install-precommit-tools.sh": {}, - "scripts/pre-code.sh": {}, "scripts/prepare-sandbox-credentials.sh": {}, "scripts/reconcile-repos.sh": {}, "scripts/resolve-precommit-tools.py": {}, diff --git a/docs/guides/user/customizing-agents.md b/docs/guides/user/customizing-agents.md index 0512f26ee8..2e03b2dea9 100644 --- a/docs/guides/user/customizing-agents.md +++ b/docs/guides/user/customizing-agents.md @@ -52,6 +52,22 @@ env: REPO_DIR: "${GITHUB_WORKSPACE}/target-repo" ``` +> **Where relative script paths resolve.** A **local** harness — the case for +> the example above — resolves `pre_script` / `post_script` against your +> workspace root, so `scripts/pre-code.sh` means *your* `scripts/` directory +> and the harness must ship its own copy. A **URL-sourced** harness resolves +> them against the parent of the harness file's directory in the source repo; +> for the stock agents that is [`fullsend-ai/agents`](https://github.com/fullsend-ai/agents), +> which owns `scripts/pre-code.sh` and `scripts/post-code.sh`. +> +> The fullsend scaffold no longer ships `scripts/pre-code.sh` or +> `scripts/pre-fix.sh`, so copying a stock agents harness into a local file +> without also copying its scripts will fail validation. Reference the +> agents-repo harness by URL instead, or supply your own scripts. +> +> A `pre_script` can also stop the run before the sandbox starts — see the +> [pre-script output protocol](../../normative/prescript-output/v1/README.md). + **Optional fields** (all have secure defaults and can be omitted): ```yaml diff --git a/internal/scaffold/fullsend-repo/scripts/pre-code-test.sh b/internal/scaffold/fullsend-repo/scripts/pre-code-test.sh deleted file mode 100644 index 57aecfe990..0000000000 --- a/internal/scaffold/fullsend-repo/scripts/pre-code-test.sh +++ /dev/null @@ -1,471 +0,0 @@ -#!/usr/bin/env bash -# pre-code-test.sh — Test pre-code.sh with mock gh to verify existing-PR check. -# -# Uses a mock gh command to capture calls without hitting GitHub. -# Run from the repo root: bash internal/scaffold/fullsend-repo/scripts/pre-code-test.sh - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PRE_SCRIPT="${SCRIPT_DIR}/pre-code.sh" -FAILURES=0 - -# Create a temp directory for mock state. -TMPDIR="$(mktemp -d)" -trap 'rm -rf "${TMPDIR}"' EXIT - -# --- Helpers --- - -# build_mock creates a mock gh binary that returns preconfigured responses. -# Arguments: -# $1 — JSON array to return for "gh pr list" calls. When the caller -# passes --jq, the mock pipes this JSON through jq so the real -# filter expression is exercised. Pass an empty string for no PRs. -build_mock() { - local pr_list_output="$1" - local mock_bin="${TMPDIR}/bin" - local gh_log="${TMPDIR}/gh-calls.log" - - rm -rf "${mock_bin}" - mkdir -p "${mock_bin}" - : > "${gh_log}" - - # Write the pr list output to a file so the mock can read it. - printf '%s' "${pr_list_output}" > "${TMPDIR}/pr-list-output.txt" - - cat > "${mock_bin}/gh" <<'MOCKEOF' -#!/usr/bin/env bash -CALL_LOG="LOGFILE_PLACEHOLDER" -PR_OUTPUT="OUTPUT_PLACEHOLDER" - -echo "gh $*" >> "${CALL_LOG}" - -# Route by subcommand -if [[ "$1" == "pr" && "$2" == "list" ]]; then - # Parse --jq flag from arguments, just like the real gh CLI. - JQ_EXPR="" - shift 2 - while [[ $# -gt 0 ]]; do - if [[ "$1" == "--jq" ]]; then - JQ_EXPR="$2" - break - fi - shift - done - if [[ -n "${JQ_EXPR}" ]] && [[ -s "${PR_OUTPUT}" ]]; then - jq -r "${JQ_EXPR}" "${PR_OUTPUT}" - else - cat "${PR_OUTPUT}" - fi -elif [[ "$1" == "label" ]]; then - exit 0 -elif [[ "$1" == "api" ]]; then - exit 0 -elif [[ "$1" == "issue" && "$2" == "comment" ]]; then - # Consume stdin (body-file reads from stdin) - cat > /dev/null - exit 0 -fi -MOCKEOF - - # Patch placeholders with actual paths (avoid sed on source files, - # but this is a generated mock — not repo source code). - local escaped_log="${gh_log//\//\\/}" - local escaped_out="${TMPDIR//\//\\/}\/pr-list-output.txt" - perl -pi -e "s/LOGFILE_PLACEHOLDER/${escaped_log}/g" "${mock_bin}/gh" - perl -pi -e "s/OUTPUT_PLACEHOLDER/${escaped_out}/g" "${mock_bin}/gh" - - chmod +x "${mock_bin}/gh" - - echo "${mock_bin}" -} - -run_test() { - local test_name="$1" - local pr_list_output="$2" - local expected_pattern="$3" - local expect_exit="$4" # 0 = success, 1 = failure - local extra_env="${5:-}" # additional env vars (KEY=VAL KEY2=VAL2) - - local mock_bin - mock_bin="$(build_mock "${pr_list_output}")" - local gh_log="${TMPDIR}/gh-calls.log" - local gh_output="${TMPDIR}/github-output.txt" - : > "${gh_output}" - - # Set base env vars for the script. - local env_cmd=( - env - PATH="${mock_bin}:${PATH}" - ISSUE_NUMBER="42" - REPO_FULL_NAME="test-org/test-repo" - GITHUB_ISSUE_URL="https://github.com/test-org/test-repo/issues/42" - GH_TOKEN="fake-token" - GITHUB_OUTPUT="${gh_output}" - ) - - # Add extra env vars if provided (read line-by-line to support values with spaces). - if [[ -n "${extra_env}" ]]; then - while IFS= read -r kv; do - [[ -n "${kv}" ]] && env_cmd+=("${kv}") - done <<< "${extra_env}" - fi - - local exit_code=0 - "${env_cmd[@]}" bash "${PRE_SCRIPT}" > "${TMPDIR}/stdout.log" 2>&1 || exit_code=$? - - # Check exit code. - if [[ ${exit_code} -ne ${expect_exit} ]]; then - echo "FAIL: ${test_name} — expected exit ${expect_exit}, got ${exit_code}" - cat "${TMPDIR}/stdout.log" - FAILURES=$((FAILURES + 1)) - return - fi - - # Check expected pattern in gh calls (if provided). - if [[ -n "${expected_pattern}" ]]; then - if ! grep -qF "${expected_pattern}" "${gh_log}" 2>/dev/null; then - echo "FAIL: ${test_name} — expected gh call pattern '${expected_pattern}' not found" - echo "Actual calls:" - cat "${gh_log}" 2>/dev/null || echo "(no calls)" - FAILURES=$((FAILURES + 1)) - return - fi - fi - - echo "PASS: ${test_name}" -} - -# Check stdout contains a specific string. -run_test_stdout() { - local test_name="$1" - local pr_list_output="$2" - local expected_stdout="$3" - local expect_exit="$4" - local extra_env="${5:-}" - - local mock_bin - mock_bin="$(build_mock "${pr_list_output}")" - local gh_output="${TMPDIR}/github-output.txt" - : > "${gh_output}" - - local env_cmd=( - env - PATH="${mock_bin}:${PATH}" - ISSUE_NUMBER="42" - REPO_FULL_NAME="test-org/test-repo" - GITHUB_ISSUE_URL="https://github.com/test-org/test-repo/issues/42" - GH_TOKEN="fake-token" - GITHUB_OUTPUT="${gh_output}" - ) - - if [[ -n "${extra_env}" ]]; then - while IFS= read -r kv; do - [[ -n "${kv}" ]] && env_cmd+=("${kv}") - done <<< "${extra_env}" - fi - - local exit_code=0 - "${env_cmd[@]}" bash "${PRE_SCRIPT}" > "${TMPDIR}/stdout.log" 2>&1 || exit_code=$? - - if [[ ${exit_code} -ne ${expect_exit} ]]; then - echo "FAIL: ${test_name} — expected exit ${expect_exit}, got ${exit_code}" - cat "${TMPDIR}/stdout.log" - FAILURES=$((FAILURES + 1)) - return - fi - - if ! grep -qF "${expected_stdout}" "${TMPDIR}/stdout.log" 2>/dev/null; then - echo "FAIL: ${test_name} — expected stdout '${expected_stdout}' not found" - echo "Actual stdout:" - cat "${TMPDIR}/stdout.log" - FAILURES=$((FAILURES + 1)) - return - fi - - echo "PASS: ${test_name}" -} - -# Check stdout contains one string and does NOT contain another. -run_test_stdout_excludes() { - local test_name="$1" - local pr_list_output="$2" - local expected_stdout="$3" - local excluded_stdout="$4" - local expect_exit="$5" - local extra_env="${6:-}" - - local mock_bin - mock_bin="$(build_mock "${pr_list_output}")" - local gh_output="${TMPDIR}/github-output.txt" - : > "${gh_output}" - - local env_cmd=( - env - PATH="${mock_bin}:${PATH}" - ISSUE_NUMBER="42" - REPO_FULL_NAME="test-org/test-repo" - GITHUB_ISSUE_URL="https://github.com/test-org/test-repo/issues/42" - GH_TOKEN="fake-token" - GITHUB_OUTPUT="${gh_output}" - ) - - if [[ -n "${extra_env}" ]]; then - while IFS= read -r kv; do - [[ -n "${kv}" ]] && env_cmd+=("${kv}") - done <<< "${extra_env}" - fi - - local exit_code=0 - "${env_cmd[@]}" bash "${PRE_SCRIPT}" > "${TMPDIR}/stdout.log" 2>&1 || exit_code=$? - - if [[ ${exit_code} -ne ${expect_exit} ]]; then - echo "FAIL: ${test_name} — expected exit ${expect_exit}, got ${exit_code}" - cat "${TMPDIR}/stdout.log" - FAILURES=$((FAILURES + 1)) - return - fi - - if ! grep -qF "${expected_stdout}" "${TMPDIR}/stdout.log" 2>/dev/null; then - echo "FAIL: ${test_name} — expected stdout '${expected_stdout}' not found" - echo "Actual stdout:" - cat "${TMPDIR}/stdout.log" - FAILURES=$((FAILURES + 1)) - return - fi - - if grep -qF "${excluded_stdout}" "${TMPDIR}/stdout.log" 2>/dev/null; then - echo "FAIL: ${test_name} — excluded stdout '${excluded_stdout}' was found" - echo "Actual stdout:" - cat "${TMPDIR}/stdout.log" - FAILURES=$((FAILURES + 1)) - return - fi - - echo "PASS: ${test_name}" -} - -# --- Test cases --- - -# JSON helpers — build unfiltered PR JSON that the mock returns to the -# script. The mock pipes this through jq using the real --jq expression -# from pre-code.sh, so the filter is exercised end-to-end. - -# Single human PR. -HUMAN_PR_JSON='[{"number":99,"author":{"login":"human-dev"},"url":"https://github.com/test-org/test-repo/pull/99"}]' - -# Single fullsend-ai[bot] PR. -BOT_PR_JSON='[{"number":10,"author":{"login":"fullsend-ai[bot]"},"url":"https://github.com/test-org/test-repo/pull/10"}]' - -# Single fullsend-ai-coder[bot] PR. -CODER_BOT_PR_JSON='[{"number":11,"author":{"login":"fullsend-ai-coder[bot]"},"url":"https://github.com/test-org/test-repo/pull/11"}]' - -# Both bot PRs plus a human PR. -MIXED_PR_JSON='[{"number":10,"author":{"login":"fullsend-ai[bot]"},"url":"https://github.com/test-org/test-repo/pull/10"},{"number":11,"author":{"login":"fullsend-ai-coder[bot]"},"url":"https://github.com/test-org/test-repo/pull/11"},{"number":99,"author":{"login":"human-dev"},"url":"https://github.com/test-org/test-repo/pull/99"}]' - -# Multiple human PRs. -MULTI_HUMAN_PR_JSON='[{"number":50,"author":{"login":"dev-a"},"url":"https://github.com/test-org/test-repo/pull/50"},{"number":51,"author":{"login":"dev-b"},"url":"https://github.com/test-org/test-repo/pull/51"}]' - -# No existing PRs → agent proceeds (exit 0, no label/comment). -run_test_stdout "no-existing-prs-proceeds" \ - "" \ - "No existing human PRs found" \ - 0 - -# Human PR exists → should apply label and comment, then exit 0. -run_test "human-pr-applies-label" \ - "${HUMAN_PR_JSON}" \ - "gh api repos/test-org/test-repo/issues/42/labels -f labels[]=pr-open --silent" \ - 0 - -run_test "human-pr-posts-comment" \ - "${HUMAN_PR_JSON}" \ - "gh issue comment 42 --repo test-org/test-repo --body-file -" \ - 0 - -run_test_stdout "human-pr-skips-agent" \ - "${HUMAN_PR_JSON}" \ - "Skipping code agent" \ - 0 - -# Bot PR only → jq filter removes it → script sees empty output → proceeds. -run_test_stdout "bot-pr-does-not-block" \ - "${BOT_PR_JSON}" \ - "No existing human PRs found" \ - 0 - -# CODE_FORCE=true → should skip check even with human PR. -run_test_stdout "force-override-code-force" \ - "${HUMAN_PR_JSON}" \ - "Force override" \ - 0 \ - "CODE_FORCE=true" - -# COMMENT_BODY contains --force → should also skip check. -run_test_stdout "force-override-comment-body" \ - "${HUMAN_PR_JSON}" \ - "Force override" \ - 0 \ - "COMMENT_BODY=/fs-code --force" - -# No GH_TOKEN → skips check entirely, exits 0. -run_test_stdout "no-gh-token-skips-check" \ - "" \ - "GH_TOKEN not set" \ - 0 \ - "GH_TOKEN=" - -# Coder bot PR only → jq filter removes it → script proceeds. -run_test_stdout "coder-bot-pr-does-not-block" \ - "${CODER_BOT_PR_JSON}" \ - "No existing human PRs found" \ - 0 - -# Both bots + human PR → jq filter removes bots, human PR blocks. -run_test_stdout "coder-bot-pr-plus-human-pr-blocks" \ - "${MIXED_PR_JSON}" \ - "Skipping code agent" \ - 0 - -# Both bots only → jq filter removes all → script proceeds. -run_test_stdout "both-bots-do-not-block" \ - '[{"number":10,"author":{"login":"fullsend-ai[bot]"},"url":"https://github.com/test-org/test-repo/pull/10"},{"number":11,"author":{"login":"fullsend-ai-coder[bot]"},"url":"https://github.com/test-org/test-repo/pull/11"}]' \ - "No existing human PRs found" \ - 0 - -# Multiple human PRs → should block and apply label. -run_test "multiple-human-prs-block" \ - "${MULTI_HUMAN_PR_JSON}" \ - "gh api repos/test-org/test-repo/issues/42/labels -f labels[]=pr-open --silent" \ - 0 - -run_test_stdout "multiple-human-prs-notice" \ - "${MULTI_HUMAN_PR_JSON}" \ - "Found existing human PR #50 by @dev-a" \ - 0 - -# PR label gets created. -run_test "pr-label-created" \ - "${HUMAN_PR_JSON}" \ - "gh label create pr-open --repo test-org/test-repo" \ - 0 - -# --- Regression tests: --force bypasses PR search (issue #1697) --- -TAB=$'\t' - -# COMMENT_BODY with --force must exit before PR search is reached. -run_test_stdout_excludes "force-comment-body-no-pr-search" \ - "99${TAB}human-dev${TAB}https://github.com/test-org/test-repo/pull/99" \ - "Force override" \ - "Checking for existing open PRs" \ - 0 \ - "COMMENT_BODY=/fs-code --force" - -# CODE_FORCE=true must exit before PR search is reached. -run_test_stdout_excludes "force-code-force-no-pr-search" \ - "99${TAB}human-dev${TAB}https://github.com/test-org/test-repo/pull/99" \ - "Force override" \ - "Checking for existing open PRs" \ - 0 \ - "CODE_FORCE=true" - -# Force check logs COMMENT_BODY value for debuggability. -run_test_stdout "force-check-logs-comment-body" \ - "" \ - "Evaluating force override:" \ - 0 \ - "COMMENT_BODY=/fs-code --force" - -# Without --force, PR search IS reached (no false bypass). -run_test_stdout "no-force-reaches-pr-search" \ - "" \ - "Checking for existing open PRs" \ - 0 \ - "COMMENT_BODY=/fs-code" - -# --- GITHUB_OUTPUT skip signal tests (issue #1312) --- - -# Helper: run pre-code.sh and check GITHUB_OUTPUT contains expected key=value. -run_test_github_output() { - local test_name="$1" - local pr_list_output="$2" - local expected_output="$3" # e.g. "skipped=true" - local expect_exit="$4" - local extra_env="${5:-}" - - local mock_bin - mock_bin="$(build_mock "${pr_list_output}")" - local gh_output="${TMPDIR}/github-output.txt" - : > "${gh_output}" - - local env_cmd=( - env - PATH="${mock_bin}:${PATH}" - ISSUE_NUMBER="42" - REPO_FULL_NAME="test-org/test-repo" - GITHUB_ISSUE_URL="https://github.com/test-org/test-repo/issues/42" - GH_TOKEN="fake-token" - GITHUB_OUTPUT="${gh_output}" - ) - - if [[ -n "${extra_env}" ]]; then - while IFS= read -r kv; do - [[ -n "${kv}" ]] && env_cmd+=("${kv}") - done <<< "${extra_env}" - fi - - local exit_code=0 - "${env_cmd[@]}" bash "${PRE_SCRIPT}" > "${TMPDIR}/stdout.log" 2>&1 || exit_code=$? - - if [[ ${exit_code} -ne ${expect_exit} ]]; then - echo "FAIL: ${test_name} — expected exit ${expect_exit}, got ${exit_code}" - cat "${TMPDIR}/stdout.log" - FAILURES=$((FAILURES + 1)) - return - fi - - if ! grep -qF "${expected_output}" "${gh_output}" 2>/dev/null; then - echo "FAIL: ${test_name} — expected GITHUB_OUTPUT to contain '${expected_output}'" - echo "Actual GITHUB_OUTPUT:" - cat "${gh_output}" 2>/dev/null || echo "(empty)" - FAILURES=$((FAILURES + 1)) - return - fi - - echo "PASS: ${test_name}" -} - -# Existing human PR → GITHUB_OUTPUT must contain skip=true. -run_test_github_output "skip-output-set-on-existing-pr" \ - "${HUMAN_PR_JSON}" \ - "skipped=true" \ - 0 - -# No existing PRs → GITHUB_OUTPUT must contain skip=false. -run_test_github_output "skip-output-false-on-no-prs" \ - "" \ - "skipped=false" \ - 0 - -# Force override → GITHUB_OUTPUT must NOT contain skip=true (force exits before PR check). -run_test_github_output "skip-output-not-set-on-force" \ - "${HUMAN_PR_JSON}" \ - "skipped=false" \ - 0 \ - "CODE_FORCE=true" - -# No GH_TOKEN → GITHUB_OUTPUT must contain skip=false (proceeds without PR check). -run_test_github_output "skip-output-false-on-no-token" \ - "" \ - "skipped=false" \ - 0 \ - "GH_TOKEN=" - -# --- Summary --- - -echo "" -if [[ ${FAILURES} -gt 0 ]]; then - echo "${FAILURES} test(s) failed" - exit 1 -fi -echo "All tests passed" diff --git a/internal/scaffold/fullsend-repo/scripts/pre-code.sh b/internal/scaffold/fullsend-repo/scripts/pre-code.sh deleted file mode 100755 index d801e80796..0000000000 --- a/internal/scaffold/fullsend-repo/scripts/pre-code.sh +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env bash -# Pre-script: validate workflow_dispatch inputs before the agent runs. -# -# Prevents malformed or malicious event_payload from reaching the sandbox. -# Runs on the GitHub Actions runner BEFORE sandbox creation. -# -# Required environment variables (set by the workflow): -# ISSUE_NUMBER — must be a positive integer -# REPO_FULL_NAME — must be owner/repo format -# GITHUB_ISSUE_URL — must be a valid GitHub issue URL -set -euo pipefail - -echo "::notice::🔗 Code target: ${GITHUB_ISSUE_URL:-}" - -errors=0 - -if [[ ! "${ISSUE_NUMBER:-}" =~ ^[1-9][0-9]*$ ]]; then - echo "::error::ISSUE_NUMBER must be a positive integer, got: '${ISSUE_NUMBER:-}'" - errors=$((errors + 1)) -fi - -if [[ ! "${REPO_FULL_NAME:-}" =~ ^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$ ]]; then - echo "::error::REPO_FULL_NAME must be owner/repo format, got: '${REPO_FULL_NAME:-}'" - errors=$((errors + 1)) -fi - -if [[ ! "${GITHUB_ISSUE_URL:-}" =~ ^https://github\.com/[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+/issues/[0-9]+$ ]]; then - echo "::error::GITHUB_ISSUE_URL format invalid, got: '${GITHUB_ISSUE_URL:-}'" - errors=$((errors + 1)) -fi - -URL_REPO="$(echo "${GITHUB_ISSUE_URL:-}" | sed -E 's|https://github.com/([^/]+/[^/]+)/issues/.*|\1|')" -URL_ISSUE="$(echo "${GITHUB_ISSUE_URL:-}" | sed -E 's|.*/issues/([0-9]+)$|\1|')" - -if [[ -n "${URL_REPO}" && "${URL_REPO}" != "${REPO_FULL_NAME:-}" ]]; then - echo "::error::REPO_FULL_NAME does not match issue URL repo ('${REPO_FULL_NAME:-}' vs '${URL_REPO}')" - errors=$((errors + 1)) -fi -if [[ -n "${URL_ISSUE}" && "${URL_ISSUE}" != "${ISSUE_NUMBER:-}" ]]; then - echo "::error::ISSUE_NUMBER does not match issue URL number ('${ISSUE_NUMBER:-}' vs '${URL_ISSUE}')" - errors=$((errors + 1)) -fi - -if [[ "${errors}" -gt 0 ]]; then - echo "::error::Input validation failed with ${errors} error(s). Aborting." - exit 1 -fi - -echo "Input validation passed:" -echo " ISSUE_NUMBER=${ISSUE_NUMBER}" -echo " REPO_FULL_NAME=${REPO_FULL_NAME}" -echo " GITHUB_ISSUE_URL=${GITHUB_ISSUE_URL}" - -# --------------------------------------------------------------------------- -# Check for existing human PRs linked to this issue -# --------------------------------------------------------------------------- -# Skip if GH_TOKEN is not available (best-effort check). -if [[ -z "${GH_TOKEN:-}" ]]; then - echo "GH_TOKEN not set — skipping existing-PR check" - echo "skipped=false" >> "${GITHUB_OUTPUT:-/dev/null}" - exit 0 -fi - -# Allow override when --force is in the trigger comment or CODE_FORCE is set. -echo "Evaluating force override: CODE_FORCE='${CODE_FORCE:-}' COMMENT_BODY='${COMMENT_BODY:-}'" -if [[ "${CODE_FORCE:-}" == "true" ]] || [[ "${COMMENT_BODY:-}" == *--force* ]]; then - echo "Force override — skipping existing-PR check" - echo "skipped=false" >> "${GITHUB_OUTPUT:-/dev/null}" - exit 0 -fi - -BOT_LOGIN="fullsend-ai[bot]" -CODER_BOT_LOGIN="fullsend-ai-coder[bot]" - -echo "Checking for existing open PRs linked to issue #${ISSUE_NUMBER}..." - -# Search for open PRs in the repo that mention the issue number. -# This catches PRs with "Closes #N", "Fixes #N", or "#N" in the body/title. -# Use gh's built-in --jq to filter out bot-authored PRs in one call. -HUMAN_PR_LINES="$(gh pr list --repo "${REPO_FULL_NAME}" --state open \ - --search "${ISSUE_NUMBER} in:body,title" \ - --json number,url,author \ - --jq "[.[] | select(.author.login != \"${BOT_LOGIN}\" and .author.login != \"${CODER_BOT_LOGIN}\")] | .[] | \"\(.number)\t\(.author.login)\t\(.url)\"" \ - 2>/dev/null || true)" - -if [[ -n "${HUMAN_PR_LINES}" ]]; then - # Parse the first PR for the notice. - FIRST_PR_NUM="$(echo "${HUMAN_PR_LINES}" | head -1 | cut -f1)" - FIRST_PR_AUTHOR="$(echo "${HUMAN_PR_LINES}" | head -1 | cut -f2)" - - echo "::notice::Found existing human PR #${FIRST_PR_NUM} by @${FIRST_PR_AUTHOR}" - - # Apply pr-open label to signal work is already underway. - gh label create "pr-open" --repo "${REPO_FULL_NAME}" \ - --description "An open PR already addresses this issue" --color "D4C5F9" \ - --force 2>/dev/null || true - gh api "repos/${REPO_FULL_NAME}/issues/${ISSUE_NUMBER}/labels" \ - -f "labels[]=pr-open" --silent 2>/dev/null || true - - # Build a markdown list of existing PRs. - PR_LIST_MD="" - while IFS=$'\t' read -r pr_num pr_author _pr_url; do - PR_LIST_MD="${PR_LIST_MD} -- #${pr_num} by @${pr_author}" - done <<< "${HUMAN_PR_LINES}" - - SKIP_COMMENT="An open PR already addresses this issue — skipping automated implementation. -${PR_LIST_MD} - -To override, comment \`/fs-code --force\` on this issue. - -Posted by fullsend pre-code check" - - printf '%s' "${SKIP_COMMENT}" | gh issue comment "${ISSUE_NUMBER}" \ - --repo "${REPO_FULL_NAME}" --body-file - 2>/dev/null || true - - echo "Skipping code agent — existing PR(s) found for issue #${ISSUE_NUMBER}" - echo "skipped=true" >> "${GITHUB_OUTPUT:-/dev/null}" - exit 0 -fi - -echo "No existing human PRs found — proceeding with code agent" -echo "skipped=false" >> "${GITHUB_OUTPUT:-/dev/null}" - -# --------------------------------------------------------------------------- -# Auto-detect and install pre-commit tool dependencies -# --------------------------------------------------------------------------- -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -TARGET_REPO="${REPO_DIR:-${GITHUB_WORKSPACE:-}/target-repo}" -RESOLVE_SCRIPT="${SCRIPT_DIR}/resolve-precommit-tools.py" -INSTALL_SCRIPT="${SCRIPT_DIR}/install-precommit-tools.sh" - -# Fallback: when this script is fetched as a single blob from a URL base, -# BASH_SOURCE points to a temp dir with no siblings. The reusable workflow's -# "Prepare workspace" step always materializes the full scripts/ directory -# at ${GITHUB_WORKSPACE}/scripts/ (per-org) or ${GITHUB_WORKSPACE}/.fullsend/scripts/ -# (per-repo). Try those paths when the BASH_SOURCE-relative lookup misses. -if [ ! -f "${RESOLVE_SCRIPT}" ] || [ ! -f "${INSTALL_SCRIPT}" ]; then - for _ws_candidate in "${GITHUB_WORKSPACE:-}/scripts" "${GITHUB_WORKSPACE:-}/.fullsend/scripts"; do - if [ -f "${_ws_candidate}/resolve-precommit-tools.py" ] \ - && [ -f "${_ws_candidate}/install-precommit-tools.sh" ]; then - RESOLVE_SCRIPT="${_ws_candidate}/resolve-precommit-tools.py" - INSTALL_SCRIPT="${_ws_candidate}/install-precommit-tools.sh" - break - fi - done -fi - -# Warn instead of silently skipping when the repo needs the auto-install but -# the companions are missing everywhere (issue #3070) — a silent skip here -# surfaces later as a confusing "Executable X not found" pre-commit failure. -if [ -f "${TARGET_REPO}/.pre-commit-config.yaml" ] \ - && { [ ! -f "${RESOLVE_SCRIPT}" ] || [ ! -f "${INSTALL_SCRIPT}" ]; }; then - echo "::warning::Pre-commit tool auto-install skipped: companion scripts not found" - echo "::warning::Expected ${RESOLVE_SCRIPT} and ${INSTALL_SCRIPT}" - echo "::warning::Pre-commit hooks requiring system tools (e.g. lychee) may fail" -fi - -if [ -f "${TARGET_REPO}/.pre-commit-config.yaml" ] \ - && [ -f "${RESOLVE_SCRIPT}" ] \ - && [ -f "${INSTALL_SCRIPT}" ]; then - echo "Resolving pre-commit tool dependencies..." - MANIFEST="$(mktemp)" - LOCAL_REG="$(mktemp)" - RESOLVE_ARGS=("${TARGET_REPO}") - DEFAULT_BR="$(git -C "${TARGET_REPO}" symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||')" || DEFAULT_BR="" - if [ -n "${DEFAULT_BR}" ] \ - && git -C "${TARGET_REPO}" show "origin/${DEFAULT_BR}:.pre-commit-tools.yaml" > "${LOCAL_REG}" 2>/dev/null; then - RESOLVE_ARGS+=("--local-registry" "${LOCAL_REG}") - fi - if python3 "${RESOLVE_SCRIPT}" "${RESOLVE_ARGS[@]}" > "${MANIFEST}"; then - if [ -s "${MANIFEST}" ] && jq -e '.tools | length > 0' "${MANIFEST}" >/dev/null 2>&1; then - bash "${INSTALL_SCRIPT}" "${MANIFEST}" - else - echo "No additional pre-commit tools needed" - fi - else - echo "::warning::Pre-commit tool resolution failed — continuing without auto-install" - fi - rm -f "${MANIFEST}" "${LOCAL_REG}" -fi -export PATH="${HOME}/.local/bin:${PATH}" -echo "${HOME}/.local/bin" >> "${GITHUB_PATH:-/dev/null}" diff --git a/internal/scaffold/fullsend-repo/scripts/pre-fix.sh b/internal/scaffold/fullsend-repo/scripts/pre-fix.sh deleted file mode 100644 index 8264cedfba..0000000000 --- a/internal/scaffold/fullsend-repo/scripts/pre-fix.sh +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env bash -# Pre-script: validate workflow_dispatch inputs before the fix agent runs. -# -# Prevents malformed or malicious event_payload from reaching the sandbox. -# Also enforces the iteration cap — blocks the run if too many fix cycles -# have already occurred on this PR. -# -# Required environment variables (set by the workflow): -# PR_NUMBER — must be a positive integer -# REPO_FULL_NAME — must be owner/repo format -# TRIGGER_SOURCE — GitHub username that triggered the fix (usernames ending in [bot] are bot triggers) -# -# Optional environment variables: -# FIX_ITERATION — current iteration count (default: 1) -# ITERATION_CAP — max bot-triggered iterations (default: 5) -# ITERATION_CAP_HUMAN — max human-triggered iterations (default: 10) -# HUMAN_INSTRUCTION — instruction text (only when TRIGGER_SOURCE doesn't end in [bot]) -set -euo pipefail - -# --------------------------------------------------------------------------- -# Helper: Bot user detection -# --------------------------------------------------------------------------- -is_bot_user() { - [[ "${1:-}" =~ \[bot\]$ ]] -} - -errors=0 - -# --------------------------------------------------------------------------- -# Input validation -# --------------------------------------------------------------------------- -if [[ ! "${PR_NUMBER:-}" =~ ^[1-9][0-9]*$ ]]; then - echo "::error::PR_NUMBER must be a positive integer, got: '${PR_NUMBER:-}'" - errors=$((errors + 1)) -fi - -if [[ ! "${REPO_FULL_NAME:-}" =~ ^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$ ]]; then - echo "::error::REPO_FULL_NAME must be owner/repo format, got: '${REPO_FULL_NAME:-}'" - errors=$((errors + 1)) -fi - -if [[ -z "${TRIGGER_SOURCE:-}" ]]; then - echo "::error::TRIGGER_SOURCE is required (GitHub username that triggered the fix)" - errors=$((errors + 1)) -fi - -if [[ "${errors}" -gt 0 ]]; then - echo "::error::Input validation failed with ${errors} error(s). Aborting." - exit 1 -fi - -# --------------------------------------------------------------------------- -# Human instruction length cap (defense against DoS via oversized input) -# --------------------------------------------------------------------------- -MAX_INSTRUCTION_BYTES=10000 -if ! is_bot_user "${TRIGGER_SOURCE}" && [[ -n "${HUMAN_INSTRUCTION:-}" ]]; then - INSTRUCTION_LEN="${#HUMAN_INSTRUCTION}" - if [[ "${INSTRUCTION_LEN}" -gt "${MAX_INSTRUCTION_BYTES}" ]]; then - echo "::error::HUMAN_INSTRUCTION is ${INSTRUCTION_LEN} bytes (max: ${MAX_INSTRUCTION_BYTES}). Truncate the instruction." - exit 1 - fi -fi - -# --------------------------------------------------------------------------- -# Iteration cap check -# --------------------------------------------------------------------------- -ITERATION="${FIX_ITERATION:-1}" -BOT_CAP="${ITERATION_CAP:-5}" -HUMAN_CAP="${ITERATION_CAP_HUMAN:-10}" - -if is_bot_user "${TRIGGER_SOURCE}"; then - CAP="${BOT_CAP}" -else - CAP="${HUMAN_CAP}" -fi - -if [[ "${ITERATION}" -gt "${CAP}" ]]; then - if is_bot_user "${TRIGGER_SOURCE}"; then - echo "::error::Fix iteration ${ITERATION} exceeds bot cap of ${CAP}. Escalating to human." - echo "::error::The review→fix loop has run ${ITERATION} times without converging." - echo "::error::A human can still direct the agent with /fs-fix (up to ${HUMAN_CAP} total iterations)." - else - echo "::error::Fix iteration ${ITERATION} exceeds human cap of ${CAP}." - echo "::error::The /fs-fix loop has run ${ITERATION} times. Further attempts are blocked." - fi - exit 1 -fi - -# --------------------------------------------------------------------------- -# Summary -# --------------------------------------------------------------------------- -echo "Input validation passed:" -echo " PR_NUMBER=${PR_NUMBER}" -echo " REPO_FULL_NAME=${REPO_FULL_NAME}" -echo " TRIGGER_SOURCE=${TRIGGER_SOURCE}" -echo " FIX_ITERATION=${ITERATION} of ${CAP}" -if ! is_bot_user "${TRIGGER_SOURCE}" && [[ -n "${HUMAN_INSTRUCTION:-}" ]]; then - # Truncate instruction in logs to avoid leaking long user input. - INSTR_PREVIEW="${HUMAN_INSTRUCTION:0:200}" - echo " HUMAN_INSTRUCTION=${INSTR_PREVIEW}..." -fi - -# --------------------------------------------------------------------------- -# Auto-detect and install pre-commit tool dependencies -# --------------------------------------------------------------------------- -# Ensures tools required by the target repo's pre-commit hooks are -# available on the runner for the authoritative post-script check. -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -TARGET_REPO="${REPO_DIR:-${GITHUB_WORKSPACE:-}/target-repo}" -RESOLVE_SCRIPT="${SCRIPT_DIR}/resolve-precommit-tools.py" -INSTALL_SCRIPT="${SCRIPT_DIR}/install-precommit-tools.sh" - -# Fallback: when this script is fetched as a single blob from a URL base, -# BASH_SOURCE points to a temp dir with no siblings. The reusable workflow's -# "Prepare workspace" step always materializes the full scripts/ directory -# at ${GITHUB_WORKSPACE}/scripts/ (per-org) or ${GITHUB_WORKSPACE}/.fullsend/scripts/ -# (per-repo). Try those paths when the BASH_SOURCE-relative lookup misses. -if [ ! -f "${RESOLVE_SCRIPT}" ] || [ ! -f "${INSTALL_SCRIPT}" ]; then - for _ws_candidate in "${GITHUB_WORKSPACE:-}/scripts" "${GITHUB_WORKSPACE:-}/.fullsend/scripts"; do - if [ -f "${_ws_candidate}/resolve-precommit-tools.py" ] \ - && [ -f "${_ws_candidate}/install-precommit-tools.sh" ]; then - RESOLVE_SCRIPT="${_ws_candidate}/resolve-precommit-tools.py" - INSTALL_SCRIPT="${_ws_candidate}/install-precommit-tools.sh" - break - fi - done -fi - -# Warn instead of silently skipping when the repo needs the auto-install but -# the companions are missing everywhere (issue #3070) — a silent skip here -# surfaces later as a confusing "Executable X not found" pre-commit failure. -if [ -f "${TARGET_REPO}/.pre-commit-config.yaml" ] \ - && { [ ! -f "${RESOLVE_SCRIPT}" ] || [ ! -f "${INSTALL_SCRIPT}" ]; }; then - echo "::warning::Pre-commit tool auto-install skipped: companion scripts not found" - echo "::warning::Expected ${RESOLVE_SCRIPT} and ${INSTALL_SCRIPT}" - echo "::warning::Pre-commit hooks requiring system tools (e.g. lychee) may fail" -fi - -if [ -f "${TARGET_REPO}/.pre-commit-config.yaml" ] \ - && [ -f "${RESOLVE_SCRIPT}" ] \ - && [ -f "${INSTALL_SCRIPT}" ]; then - echo "Resolving pre-commit tool dependencies..." - MANIFEST="$(mktemp)" - LOCAL_REG="$(mktemp)" - RESOLVE_ARGS=("${TARGET_REPO}") - _BASE_BR="${TARGET_BRANCH:-}" - if [ -z "${_BASE_BR}" ]; then - _BASE_BR="$(git -C "${TARGET_REPO}" symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||')" || _BASE_BR="" - fi - if [ -n "${_BASE_BR}" ] \ - && git -C "${TARGET_REPO}" show "origin/${_BASE_BR}:.pre-commit-tools.yaml" > "${LOCAL_REG}" 2>/dev/null; then - RESOLVE_ARGS+=("--local-registry" "${LOCAL_REG}") - fi - if python3 "${RESOLVE_SCRIPT}" "${RESOLVE_ARGS[@]}" > "${MANIFEST}"; then - if [ -s "${MANIFEST}" ] && jq -e '.tools | length > 0' "${MANIFEST}" >/dev/null 2>&1; then - bash "${INSTALL_SCRIPT}" "${MANIFEST}" - else - echo "No additional pre-commit tools needed" - fi - else - echo "::warning::Pre-commit tool resolution failed — continuing without auto-install" - fi - rm -f "${MANIFEST}" "${LOCAL_REG}" -fi -export PATH="${HOME}/.local/bin:${PATH}" -echo "${HOME}/.local/bin" >> "${GITHUB_PATH:-/dev/null}" diff --git a/internal/scaffold/scaffold.go b/internal/scaffold/scaffold.go index fcf0ea42bf..7092227658 100644 --- a/internal/scaffold/scaffold.go +++ b/internal/scaffold/scaffold.go @@ -26,7 +26,6 @@ func FullsendRepoFile(path string) ([]byte, error) { var executableFiles = map[string]struct{}{ "scripts/fullsend-check-output": {}, "scripts/install-precommit-tools.sh": {}, - "scripts/pre-code.sh": {}, "scripts/prepare-sandbox-credentials.sh": {}, "scripts/reconcile-repos.sh": {}, "scripts/resolve-precommit-tools.py": {},