feat: Created the step-runner graph agent for more deterministic coding workflows to produce even more reliable and higher-quality results
This commit is contained in:
+54
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
|
||||
if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then
|
||||
state=$(cat "$GRAPH_STATE_FILE")
|
||||
elif [[ -n "${GRAPH_STATE:-}" ]]; then
|
||||
state="$GRAPH_STATE"
|
||||
else
|
||||
state='{}'
|
||||
fi
|
||||
|
||||
handoff_path=$(echo "$state" | jq -r '.handoff_path // ""')
|
||||
step_plan_path=$(echo "$state" | jq -r '.step_plan_path // ""')
|
||||
handoff_attempts=$(echo "$state" | jq -r '.handoff_attempts // 0')
|
||||
|
||||
problems=""
|
||||
|
||||
if [[ ! -f "$handoff_path" ]]; then
|
||||
problems="- handoff file does not exist at $handoff_path"$'\n'
|
||||
else
|
||||
content=$(cat "$handoff_path")
|
||||
grep -qE '^result:[[:space:]]*(complete|partial|blocked)' <<< "$content" \
|
||||
|| problems+="- frontmatter is missing 'result: complete|partial|blocked'"$'\n'
|
||||
for section in "Summary" "Completed" "Not completed" "Deviations" "Downstream plan updates" "Edge cases discovered" "Evidence" "Notes for next step"; do
|
||||
grep -qE "^##[[:space:]]+${section}" <<< "$content" \
|
||||
|| problems+="- missing required section: ## ${section}"$'\n'
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -z "$problems" ]]; then
|
||||
if [[ -f "$step_plan_path" ]]; then
|
||||
tmp=$(mktemp)
|
||||
awk 'BEGIN{n=0} /^---[[:space:]]*$/{n++; print; next} n==1 && /^status:/{print "status: complete"; next} {print}' "$step_plan_path" > "$tmp" && mv "$tmp" "$step_plan_path"
|
||||
fi
|
||||
jq -nc '{"handoff_fix": "", "_next": "gate_user_review"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if (( handoff_attempts >= 1 )); then
|
||||
jq -nc \
|
||||
--arg br "Handoff failed validation twice. Problems:
|
||||
$problems" \
|
||||
'{"blocking_reason": $br, "_next": "end_failure"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
jq -nc \
|
||||
--arg hf "The previous handoff attempt failed validation. Fix exactly these problems:
|
||||
$problems" \
|
||||
'{
|
||||
"handoff_attempts": 1,
|
||||
"handoff_fix": $hf,
|
||||
"_next": "write_handoff"
|
||||
}'
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then
|
||||
state=$(cat "$GRAPH_STATE_FILE")
|
||||
elif [[ -n "${GRAPH_STATE:-}" ]]; then
|
||||
state="$GRAPH_STATE"
|
||||
else
|
||||
state='{}'
|
||||
fi
|
||||
|
||||
fix_attempts=$(echo "$state" | jq -r '.fix_attempts // 0')
|
||||
max_fix_attempts=$(echo "$state" | jq -r '.max_fix_attempts // 2')
|
||||
lint_ok=$(echo "$state" | jq -r '.lint_ok | if . == null then "true" else (. | tostring) end')
|
||||
build_ok=$(echo "$state" | jq -r '.build_ok | if . == null then "true" else (. | tostring) end')
|
||||
tests_ok=$(echo "$state" | jq -r '.tests_ok | if . == null then "true" else (. | tostring) end')
|
||||
lint_output=$(echo "$state" | jq -r '.lint_output // ""')
|
||||
build_output=$(echo "$state" | jq -r '.build_output // ""')
|
||||
tests_output=$(echo "$state" | jq -r '.tests_output // ""')
|
||||
|
||||
if (( fix_attempts >= max_fix_attempts )); then
|
||||
jq -nc \
|
||||
--argjson n "$fix_attempts" \
|
||||
'{
|
||||
"fix_attempts": $n,
|
||||
"_next": "end_failure"
|
||||
}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
next_attempts=$((fix_attempts + 1))
|
||||
|
||||
if [[ "$lint_ok" != "true" ]]; then
|
||||
stage="lint"
|
||||
output="$lint_output"
|
||||
elif [[ "$build_ok" != "true" ]]; then
|
||||
stage="build"
|
||||
output="$build_output"
|
||||
elif [[ "$tests_ok" != "true" ]]; then
|
||||
stage="full test suite"
|
||||
output="$tests_output"
|
||||
else
|
||||
stage="verification"
|
||||
output="fix_loop_gate was reached but no failing stage was recorded. Re-run verification."
|
||||
fi
|
||||
|
||||
fix_instructions=$(printf '## Fix loop status (step-level attempt %d of %d)\n\nThe implementation passed the coder'"'"'s internal checks but failed step-level verification at the %s stage.\n\nOutput:\n```\n%s\n```\n\nIdentify the minimal fix and apply it. Do not refactor. Regressions in untouched code caused by this change are in scope.' \
|
||||
"$next_attempts" "$max_fix_attempts" "$stage" "$output")
|
||||
|
||||
jq -nc \
|
||||
--argjson n "$next_attempts" \
|
||||
--arg 'fi' "$fix_instructions" \
|
||||
'{
|
||||
"fix_attempts": $n,
|
||||
"fix_instructions": $fi,
|
||||
"lint_ok": true,
|
||||
"build_ok": true,
|
||||
"tests_ok": true,
|
||||
"_next": "implement"
|
||||
}'
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
|
||||
if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then
|
||||
state=$(cat "$GRAPH_STATE_FILE")
|
||||
elif [[ -n "${GRAPH_STATE:-}" ]]; then
|
||||
state="$GRAPH_STATE"
|
||||
else
|
||||
state='{}'
|
||||
fi
|
||||
|
||||
fail() {
|
||||
jq -nc --arg r "$1" '{"blocking_reason": $r, "_next": "end_failure"}'
|
||||
exit 0
|
||||
}
|
||||
|
||||
project_dir="${LLM_AGENT_VAR_PROJECT_DIR:-.}"
|
||||
project_dir=$(cd "$project_dir" 2>/dev/null && pwd) || fail "project_dir does not exist: $project_dir"
|
||||
|
||||
plans_dir="${LLM_AGENT_VAR_PLANS_DIR:-plans}"
|
||||
[[ "$plans_dir" != /* ]] && plans_dir="$project_dir/$plans_dir"
|
||||
steps_dir="$plans_dir/steps"
|
||||
handoffs_dir="$plans_dir/handoffs"
|
||||
notes_path="$plans_dir/NOTES.md"
|
||||
|
||||
[[ -d "$steps_dir" ]] || fail "No step plans directory at $steps_dir (expected <plans_dir>/steps/NN-<slug>.md)"
|
||||
|
||||
frontmatter() {
|
||||
awk '/^---[[:space:]]*$/{n++; next} n==1{print} n>=2{exit}' "$1"
|
||||
}
|
||||
|
||||
fm_value() {
|
||||
echo "$1" | grep -E "^$2:" | head -1 | sed -E "s/^$2:[[:space:]]*//" | sed -E 's/^["'"'"']|["'"'"']$//g'
|
||||
}
|
||||
|
||||
step="${LLM_AGENT_VAR_STEP:-next}"
|
||||
if [[ "$step" == "next" ]]; then
|
||||
prompt_step=$(echo "$state" | jq -r '.initial_prompt // ""' | grep -oiE 'step[[:space:]#:]*[0-9]+' | head -1 | grep -oE '[0-9]+' || true)
|
||||
[[ -n "$prompt_step" ]] && step="$prompt_step"
|
||||
fi
|
||||
|
||||
plan_file=""
|
||||
if [[ "$step" == "next" ]]; then
|
||||
first_pending=""
|
||||
while IFS= read -r f; do
|
||||
st=$(fm_value "$(frontmatter "$f")" "status")
|
||||
if [[ "$st" == "in-progress" ]]; then
|
||||
plan_file="$f"
|
||||
break
|
||||
fi
|
||||
[[ -z "$first_pending" && ( "$st" == "pending" || -z "$st" ) ]] && first_pending="$f"
|
||||
done < <(find "$steps_dir" -maxdepth 1 -name '*.md' | sort)
|
||||
[[ -z "$plan_file" ]] && plan_file="$first_pending"
|
||||
[[ -z "$plan_file" ]] && fail "No in-progress or pending step plans in $steps_dir"
|
||||
else
|
||||
[[ "$step" =~ ^[0-9]+$ ]] || fail "step must be a number or 'next'; got: $step"
|
||||
padded=$(printf '%02d' "$((10#$step))")
|
||||
plan_file=$(find "$steps_dir" -maxdepth 1 \( -name "${padded}-*.md" -o -name "${step}-*.md" \) | sort | head -1)
|
||||
[[ -n "$plan_file" ]] || fail "No step plan matching step $step in $steps_dir"
|
||||
fi
|
||||
|
||||
bn=$(basename "$plan_file" .md)
|
||||
num_part="${bn%%-*}"
|
||||
[[ "$num_part" =~ ^[0-9]+$ ]] || fail "Step plan filename must start with a number: $bn"
|
||||
step_number=$((10#$num_part))
|
||||
step_slug="${bn#*-}"
|
||||
|
||||
fm=$(frontmatter "$plan_file")
|
||||
step_title=$(fm_value "$fm" "title")
|
||||
[[ -z "$step_title" ]] && step_title="$step_slug"
|
||||
|
||||
deps=$(echo "$fm" | awk '/^depends_on:/{f=1; print; next} f && /^[[:space:]]*-/{print; next} f{exit}' | grep -oE '[0-9]+' || true)
|
||||
unsatisfied=""
|
||||
for dep in $deps; do
|
||||
dep_padded=$(printf '%02d' "$((10#$dep))")
|
||||
dep_handoff=$(find "$handoffs_dir" -maxdepth 1 \( -name "${dep_padded}-*.md" -o -name "${dep}-*.md" \) 2>/dev/null | sort | head -1)
|
||||
if [[ -z "$dep_handoff" ]]; then
|
||||
unsatisfied+="- step $dep: no handoff found (step not executed?)"$'\n'
|
||||
continue
|
||||
fi
|
||||
dep_result=$(fm_value "$(frontmatter "$dep_handoff")" "result")
|
||||
if [[ "$dep_result" != "complete" ]]; then
|
||||
unsatisfied+="- step $dep: handoff result is '$dep_result' (not complete): $dep_handoff"$'\n'
|
||||
fi
|
||||
done
|
||||
|
||||
prev_handoff_path="(none)"
|
||||
prev_handoff="(none - this is the first step)"
|
||||
prev_file=""
|
||||
prev_num=0
|
||||
while IFS= read -r h; do
|
||||
hn="${h##*/}"
|
||||
hn="${hn%%-*}"
|
||||
[[ "$hn" =~ ^[0-9]+$ ]] || continue
|
||||
n=$((10#$hn))
|
||||
if (( n < step_number && n >= prev_num )); then
|
||||
prev_num=$n
|
||||
prev_file="$h"
|
||||
fi
|
||||
done < <(find "$handoffs_dir" -maxdepth 1 -name '*.md' 2>/dev/null | sort)
|
||||
if [[ -n "$prev_file" ]]; then
|
||||
prev_handoff_path="$prev_file"
|
||||
prev_handoff=$(head -c 16000 "$prev_file")
|
||||
fi
|
||||
|
||||
notes="(none)"
|
||||
[[ -f "$notes_path" ]] && notes=$(head -c 8000 "$notes_path")
|
||||
|
||||
step_plan=$(head -c 24000 "$plan_file")
|
||||
handoff_path="$handoffs_dir/$(basename "$plan_file")"
|
||||
|
||||
tmp=$(mktemp)
|
||||
awk 'BEGIN{n=0} /^---[[:space:]]*$/{n++; print; next} n==1 && /^status:/{print "status: in-progress"; next} {print}' "$plan_file" > "$tmp" && mv "$tmp" "$plan_file"
|
||||
|
||||
next_node="orient"
|
||||
blocking_reason=""
|
||||
if [[ -n "$unsatisfied" ]]; then
|
||||
next_node="gate_blocked"
|
||||
blocking_reason="Unsatisfied dependencies:"$'\n'"$unsatisfied"
|
||||
fi
|
||||
|
||||
jq -nc \
|
||||
--arg pd "$project_dir" \
|
||||
--arg pl "$plans_dir" \
|
||||
--argjson sn "$step_number" \
|
||||
--arg ss "$step_slug" \
|
||||
--arg st "$step_title" \
|
||||
--arg spp "$plan_file" \
|
||||
--arg sp "$step_plan" \
|
||||
--arg php "$prev_handoff_path" \
|
||||
--arg ph "$prev_handoff" \
|
||||
--arg np "$notes_path" \
|
||||
--arg no "$notes" \
|
||||
--arg hp "$handoff_path" \
|
||||
--arg br "$blocking_reason" \
|
||||
--arg nx "$next_node" \
|
||||
'{
|
||||
"project_dir": $pd,
|
||||
"plans_dir": $pl,
|
||||
"step_number": $sn,
|
||||
"step_slug": $ss,
|
||||
"step_title": $st,
|
||||
"step_plan_path": $spp,
|
||||
"step_plan": $sp,
|
||||
"prev_handoff_path": $php,
|
||||
"prev_handoff": $ph,
|
||||
"notes_path": $np,
|
||||
"notes": $no,
|
||||
"handoff_path": $hp,
|
||||
"blocking_reason": $br,
|
||||
"_next": $nx
|
||||
}'
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then
|
||||
state=$(cat "$GRAPH_STATE_FILE")
|
||||
elif [[ -n "${GRAPH_STATE:-}" ]]; then
|
||||
state="$GRAPH_STATE"
|
||||
else
|
||||
state='{}'
|
||||
fi
|
||||
|
||||
feedback=$(echo "$state" | jq -r '.user_feedback // ""')
|
||||
|
||||
if [[ -z "$feedback" ]]; then
|
||||
jq -nc '{"_next": "get_revision"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
fix_instructions=$(printf '## Revision requested by the user at the step approval gate\n\nAddress these comments with minimal edits, then the step re-verifies and the handoff is rewritten:\n\n%s' \
|
||||
"$feedback")
|
||||
|
||||
jq -nc \
|
||||
--arg 'fi' "$fix_instructions" \
|
||||
'{
|
||||
"fix_instructions": $fi,
|
||||
"_next": "implement"
|
||||
}'
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then
|
||||
state=$(cat "$GRAPH_STATE_FILE")
|
||||
elif [[ -n "${GRAPH_STATE:-}" ]]; then
|
||||
state="$GRAPH_STATE"
|
||||
else
|
||||
state='{}'
|
||||
fi
|
||||
|
||||
coder_result=$(echo "$state" | jq -r '.coder_result // ""')
|
||||
|
||||
case "$coder_result" in
|
||||
*CODER_COMPLETE*)
|
||||
jq -nc '{"_next": "verify_format_lint"}'
|
||||
;;
|
||||
*CODER_REJECTED*)
|
||||
jq -nc '{"_next": "end_rejected"}'
|
||||
;;
|
||||
*CODER_FAILED*)
|
||||
jq -nc '{"blocking_reason": "coder fix-loop exhausted; see coder result", "_next": "end_failure"}'
|
||||
;;
|
||||
*)
|
||||
jq -nc '{"blocking_reason": "coder returned no recognizable sentinel (expected CODER_COMPLETE / CODER_REJECTED / CODER_FAILED)", "_next": "end_failure"}'
|
||||
;;
|
||||
esac
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then
|
||||
state=$(cat "$GRAPH_STATE_FILE")
|
||||
elif [[ -n "${GRAPH_STATE:-}" ]]; then
|
||||
state="$GRAPH_STATE"
|
||||
else
|
||||
state='{}'
|
||||
fi
|
||||
|
||||
review_report=$(echo "$state" | jq -r '.review_report // ""')
|
||||
review_attempts=$(echo "$state" | jq -r '.review_attempts // 0')
|
||||
max_review_attempts=$(echo "$state" | jq -r '.max_review_attempts // 1')
|
||||
|
||||
if ! grep -qF "🔴" <<< "$review_report"; then
|
||||
jq -nc '{"_next": "write_handoff"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if (( review_attempts >= max_review_attempts )); then
|
||||
jq -nc '{"_next": "write_handoff"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
next_review=$((review_attempts + 1))
|
||||
fix_instructions=$(printf '## Independent review findings (attempt %d of %d)\n\nAn independent reviewer flagged CRITICAL (🔴) findings. Address ONLY the 🔴 findings with minimal edits. Do not refactor unrelated code.\n\n%s' \
|
||||
"$next_review" "$max_review_attempts" "$review_report")
|
||||
|
||||
jq -nc \
|
||||
--argjson n "$next_review" \
|
||||
--arg 'fi' "$fix_instructions" \
|
||||
'{
|
||||
"review_attempts": $n,
|
||||
"fix_instructions": $fi,
|
||||
"needs_independent_review": false,
|
||||
"_next": "implement"
|
||||
}'
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then
|
||||
state=$(cat "$GRAPH_STATE_FILE")
|
||||
elif [[ -n "${GRAPH_STATE:-}" ]]; then
|
||||
state="$GRAPH_STATE"
|
||||
else
|
||||
state='{}'
|
||||
fi
|
||||
|
||||
has_major=$(echo "$state" | jq -r '.has_major_deviation // false')
|
||||
|
||||
if [[ "${STEP_AUTOAPPROVE:-0}" == "1" ]]; then
|
||||
jq -nc '{"_next": "implement"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$has_major" == "true" ]]; then
|
||||
jq -nc '{"_next": "gate_deviation"}'
|
||||
else
|
||||
jq -nc '{"_next": "implement"}'
|
||||
fi
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then
|
||||
state=$(cat "$GRAPH_STATE_FILE")
|
||||
elif [[ -n "${GRAPH_STATE:-}" ]]; then
|
||||
state="$GRAPH_STATE"
|
||||
else
|
||||
state='{}'
|
||||
fi
|
||||
|
||||
needs_review=$(echo "$state" | jq -r '.needs_independent_review // false')
|
||||
|
||||
if [[ "${STEP_SKIP_REVIEW:-0}" == "1" ]]; then
|
||||
jq -nc '{"_next": "write_handoff"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$needs_review" == "true" ]]; then
|
||||
jq -nc '{"_next": "independent_review"}'
|
||||
else
|
||||
jq -nc '{"_next": "write_handoff"}'
|
||||
fi
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source "$(dirname "$0")/../../.shared/utils.sh"
|
||||
|
||||
if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then
|
||||
state=$(cat "$GRAPH_STATE_FILE")
|
||||
elif [[ -n "${GRAPH_STATE:-}" ]]; then
|
||||
state="$GRAPH_STATE"
|
||||
else
|
||||
state='{}'
|
||||
fi
|
||||
|
||||
project_dir=$(echo "$state" | jq -r '.project_dir // "."')
|
||||
|
||||
if [[ -n "${BUILD_CMD:-}" ]]; then
|
||||
cmd="$BUILD_CMD"
|
||||
else
|
||||
project_info=$(detect_project "$project_dir")
|
||||
cmd=$(echo "$project_info" | jq -r '.check // .build // ""')
|
||||
fi
|
||||
|
||||
if [[ -z "$cmd" || "$cmd" == "null" ]]; then
|
||||
jq -nc '{
|
||||
"build_ok": true,
|
||||
"build_output": "(no build/check command available for this project type)",
|
||||
"_next": "verify_tests"
|
||||
}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exit_code=0
|
||||
output=$(cd "$project_dir" && eval "$cmd" 2>&1) || exit_code=$?
|
||||
|
||||
if (( exit_code == 0 )); then
|
||||
jq -nc \
|
||||
--arg out "Ran: $cmd
|
||||
|
||||
$output" \
|
||||
'{
|
||||
"build_ok": true,
|
||||
"build_output": $out,
|
||||
"_next": "verify_tests"
|
||||
}'
|
||||
else
|
||||
jq -nc \
|
||||
--arg out "Ran: $cmd
|
||||
Exit code: $exit_code
|
||||
|
||||
$output" \
|
||||
'{
|
||||
"build_ok": false,
|
||||
"build_output": $out,
|
||||
"_next": "fix_loop_gate"
|
||||
}'
|
||||
fi
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source "$(dirname "$0")/../../.shared/utils.sh"
|
||||
|
||||
if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then
|
||||
state=$(cat "$GRAPH_STATE_FILE")
|
||||
elif [[ -n "${GRAPH_STATE:-}" ]]; then
|
||||
state="$GRAPH_STATE"
|
||||
else
|
||||
state='{}'
|
||||
fi
|
||||
|
||||
project_dir=$(echo "$state" | jq -r '.project_dir // "."')
|
||||
project_type=$(detect_project "$project_dir" | jq -r '.type // "unknown"')
|
||||
|
||||
format_cmd="${FORMAT_CMD:-}"
|
||||
if [[ -z "$format_cmd" ]]; then
|
||||
case "$project_type" in
|
||||
rust) format_cmd="cargo fmt" ;;
|
||||
go) format_cmd="gofmt -w ." ;;
|
||||
python) command -v ruff &>/dev/null && format_cmd="ruff format ." ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [[ -z "$format_cmd" ]]; then
|
||||
format_output="(no format command configured for project type '$project_type'; skipped. Set FORMAT_CMD to enable.)"
|
||||
else
|
||||
fmt_rc=0
|
||||
fmt_out=$(cd "$project_dir" && eval "$format_cmd" 2>&1) || fmt_rc=$?
|
||||
format_output="Ran: $format_cmd
|
||||
Exit code: $fmt_rc
|
||||
|
||||
$fmt_out"
|
||||
fi
|
||||
|
||||
lint_cmd="${LINT_CMD:-}"
|
||||
if [[ -z "$lint_cmd" ]]; then
|
||||
jq -nc \
|
||||
--arg fo "$format_output" \
|
||||
'{
|
||||
"format_output": $fo,
|
||||
"lint_ok": true,
|
||||
"lint_output": "(no LINT_CMD configured; linting is covered by the build/check command)",
|
||||
"_next": "verify_build"
|
||||
}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
lint_rc=0
|
||||
lint_out=$(cd "$project_dir" && eval "$lint_cmd" 2>&1) || lint_rc=$?
|
||||
|
||||
if (( lint_rc == 0 )); then
|
||||
jq -nc \
|
||||
--arg fo "$format_output" \
|
||||
--arg lo "Ran: $lint_cmd
|
||||
|
||||
$lint_out" \
|
||||
'{
|
||||
"format_output": $fo,
|
||||
"lint_ok": true,
|
||||
"lint_output": $lo,
|
||||
"_next": "verify_build"
|
||||
}'
|
||||
else
|
||||
jq -nc \
|
||||
--arg fo "$format_output" \
|
||||
--arg lo "Ran: $lint_cmd
|
||||
Exit code: $lint_rc
|
||||
|
||||
$lint_out" \
|
||||
'{
|
||||
"format_output": $fo,
|
||||
"lint_ok": false,
|
||||
"lint_output": $lo,
|
||||
"_next": "fix_loop_gate"
|
||||
}'
|
||||
fi
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source "$(dirname "$0")/../../.shared/utils.sh"
|
||||
|
||||
if [[ -n "${GRAPH_STATE_FILE:-}" ]]; then
|
||||
state=$(cat "$GRAPH_STATE_FILE")
|
||||
elif [[ -n "${GRAPH_STATE:-}" ]]; then
|
||||
state="$GRAPH_STATE"
|
||||
else
|
||||
state='{}'
|
||||
fi
|
||||
|
||||
project_dir=$(echo "$state" | jq -r '.project_dir // "."')
|
||||
|
||||
if [[ -n "${TEST_CMD:-}" ]]; then
|
||||
cmd="$TEST_CMD"
|
||||
else
|
||||
project_info=$(detect_project "$project_dir")
|
||||
cmd=$(echo "$project_info" | jq -r '.test // ""')
|
||||
fi
|
||||
|
||||
if [[ -z "$cmd" || "$cmd" == "null" ]]; then
|
||||
jq -nc '{
|
||||
"tests_ok": true,
|
||||
"tests_output": "(no test command available for this project type)",
|
||||
"_next": "edge_case_sweep"
|
||||
}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exit_code=0
|
||||
output=$(cd "$project_dir" && eval "$cmd" 2>&1) || exit_code=$?
|
||||
|
||||
if (( exit_code == 0 )); then
|
||||
jq -nc \
|
||||
--arg out "Ran: $cmd
|
||||
|
||||
$output" \
|
||||
'{
|
||||
"tests_ok": true,
|
||||
"tests_output": $out,
|
||||
"_next": "edge_case_sweep"
|
||||
}'
|
||||
else
|
||||
jq -nc \
|
||||
--arg out "Ran: $cmd
|
||||
Exit code: $exit_code
|
||||
|
||||
$output" \
|
||||
'{
|
||||
"tests_ok": false,
|
||||
"tests_output": $out,
|
||||
"_next": "fix_loop_gate"
|
||||
}'
|
||||
fi
|
||||
Reference in New Issue
Block a user