From d567c838a9d798a4465d8dbeb535aa69881a3d4f Mon Sep 17 00:00:00 2001 From: satyaborg Date: Mon, 29 Jun 2026 14:51:58 +1000 Subject: [PATCH 1/3] feat: glow-terminal-rendering --- README.md | 2 +- devloop | 122 +++++++++++++++++----- scripts/devloop_test.sh | 208 ++++++++++++++++++++++++++++++++++++-- scripts/install.remote.sh | 6 +- scripts/install.sh | 2 +- scripts/skill_helpers.sh | 1 + 6 files changed, 302 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index f2941b8..07dc4b8 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ cd devloop ./scripts/install.sh ``` -> Requires Bash, git, `codex`, `claude`, `gum`, and `fzf`. Run `devloop doctor` to check. +> Requires Bash, git, `codex`, `claude`, `glow`, `gum`, and `fzf`. Run `devloop doctor` to check. Uninstall with `./scripts/uninstall.sh` (`--dry-run` to preview). diff --git a/devloop b/devloop index 0758319..cb2909e 100755 --- a/devloop +++ b/devloop @@ -871,6 +871,10 @@ ui_has_fzf() { [ "$USE_TUI" = true ] && command -v fzf >/dev/null 2>&1 } +ui_has_glow() { + [ "$USE_TUI" = true ] && command -v glow >/dev/null 2>&1 +} + ui_color_code() { local color="$1" case "$color" in @@ -1109,15 +1113,18 @@ ui_numbered_pick() { ui_pick_from_file() { local file="$1" local header="$2" + local preview_cmd if [ ! -s "$file" ]; then return 1; fi if ui_has_fzf; then + # shellcheck disable=SC2016 + preview_cmd='case "{}" in *.md) if command -v glow >/dev/null 2>&1; then glow -w "$FZF_PREVIEW_COLUMNS" "{}"; else sed -n "1,80p" "{}" 2>/dev/null; fi ;; *) sed -n "1,80p" "{}" 2>/dev/null ;; esac' fzf \ --height 70% \ --layout reverse \ --border \ --color "fg:-1,bg:-1,fg+:${UI_OK_COLOR},bg+:-1,hl:${UI_REC_COLOR},hl+:${UI_REC_COLOR},prompt:${UI_DIM_COLOR},pointer:${UI_REC_COLOR},marker:${UI_OK_COLOR},spinner:${UI_REC_COLOR},info:${UI_DIM_COLOR},border:${UI_BORDER_COLOR},header:${UI_ACCENT_COLOR},gutter:-1" \ --prompt "$header > " \ - --preview 'sed -n "1,80p" {} 2>/dev/null' < "$file" + --preview "$preview_cmd" < "$file" return $? fi if ui_has_gum; then @@ -1545,7 +1552,7 @@ interactive_run_setup() { return 0 fi run_header "$spec" "$max" "$strict" "$use_worktree" "$coder" "$reviewer" "$create_pr" "$timeout_minutes" - run_devloop "$spec" "$max" "$strict" "$use_worktree" "$coder" "$reviewer" "$create_pr" "$timeout_minutes" + run_devloop "$spec" "$max" "markdown" "$strict" "$use_worktree" "$coder" "$reviewer" "$create_pr" "$timeout_minutes" code=$? maybe_enter_worktree return "$(final_exit_code "$code")" @@ -1776,13 +1783,38 @@ list_artifact_files() { while IFS= read -r root; do dir="$root/$subdir" if [ -d "$dir" ]; then - find "$dir" -type f -name '*.md' | LC_ALL=C sort + case "$subdir" in + .devloop/reports) find "$dir" -type f \( -name '*.md' -o -name '*.html' -o -name '*.htm' \) | LC_ALL=C sort ;; + *) find "$dir" -type f -name '*.md' | LC_ALL=C sort ;; + esac fi done } view_file() { local file="$1" + case "$file" in + *.html|*.htm) + if command -v open >/dev/null 2>&1 && open "$file" >/dev/null 2>&1; then return 0; fi + if command -v xdg-open >/dev/null 2>&1 && xdg-open "$file" >/dev/null 2>&1; then return 0; fi + ;; + esac + case "$file" in + *.md) + if ui_has_glow; then + if ui_has_gum; then + glow "$file" | gum pager + return $? + fi + if [ "$USE_TUI" = true ] && [ -t 0 ]; then + glow "$file" | "${PAGER:-less}" + return $? + fi + glow "$file" + return $? + fi + ;; + esac if ui_has_gum; then gum pager < "$file" return $? @@ -1856,8 +1888,12 @@ track_report_path() { local slug dir report slug="$(basename "$track" .md)" dir="$(cd "$(dirname "$track")/.." >/dev/null 2>&1 && pwd -P)/reports" - report="$dir/$slug.md" - if [ -f "$report" ]; then printf '%s\n' "$report"; fi + for report in "$dir/$slug.html" "$dir/$slug.md"; do + if [ -f "$report" ]; then + printf '%s\n' "$report" + return + fi + done } print_track_status() { @@ -1938,7 +1974,7 @@ remove_devloop_worktree() { run_from_track() { local track="$1" - local worktree spec max strict coder reviewer create_pr timeout_minutes old_pwd code next_pass + local worktree spec max report_format strict coder reviewer create_pr timeout_minutes old_pwd code next_pass track="$(absolute_existing_file "$track")" || { printf 'track not found: %s\n' "$track" >&2 return 2 @@ -1946,6 +1982,7 @@ run_from_track() { worktree="$(track_value "worktree" "$track")" spec="$(track_value "spec" "$track")" max="$(track_value "max" "$track")" + report_format="$(track_value "report-format" "$track")" strict="$(track_value "strict" "$track")" coder="$(track_value "coder" "$track")" reviewer="$(track_value "reviewer" "$track")" @@ -1955,6 +1992,10 @@ run_from_track() { if [ -z "$worktree" ]; then worktree="$(cd "$(dirname "$track")/../.." >/dev/null 2>&1 && pwd -P)"; fi if [ -z "$spec" ]; then spec="$(track_value "source-spec" "$track")"; fi if [ -z "$max" ]; then max=5; fi + case "$report_format" in + html) ;; + *) report_format="markdown" ;; + esac if [ -z "$strict" ]; then strict=true; fi if [ -z "$coder" ]; then coder="$(devloop_coder)"; fi if [ -z "$reviewer" ]; then reviewer="$(devloop_reviewer)"; fi @@ -1976,7 +2017,7 @@ run_from_track() { cd "$worktree" || return 2 RUN_START_PASS="$next_pass" run_header "$spec" "$max" "$strict" false "$coder" "$reviewer" "$create_pr" "$timeout_minutes" - run_devloop "$spec" "$max" "$strict" false "$coder" "$reviewer" "$create_pr" "$timeout_minutes" + run_devloop "$spec" "$max" "$report_format" "$strict" false "$coder" "$reviewer" "$create_pr" "$timeout_minutes" code=$? RUN_START_PASS=1 maybe_enter_worktree @@ -2025,6 +2066,7 @@ will_enter_worktree_shell() { } run_command() { + local report_format="markdown" local strict=true local use_worktree=true local coder @@ -2043,6 +2085,16 @@ run_command() { arg="$1" shift case "$arg" in + --report-format) + if [ "$#" -eq 0 ]; then usage >&2; return 2; fi + value="$1" + shift + case "$value" in + html) report_format="html" ;; + markdown|md) report_format="markdown" ;; + *) usage >&2; return 2 ;; + esac + ;; --coder) if [ "$#" -eq 0 ]; then printf '%s\n' "coder must be Codex or Claude Code" >&2; usage >&2; return 2; fi value="$(normalize_agent "$1")" @@ -2104,7 +2156,7 @@ run_command() { if [ "$max" -gt 10 ]; then max=10; fi run_header "$spec" "$max" "$strict" "$use_worktree" "$coder" "$reviewer" "$create_pr" "$timeout_minutes" - run_devloop "$spec" "$max" "$strict" "$use_worktree" "$coder" "$reviewer" "$create_pr" "$timeout_minutes" + run_devloop "$spec" "$max" "$report_format" "$strict" "$use_worktree" "$coder" "$reviewer" "$create_pr" "$timeout_minutes" local code=$? maybe_enter_worktree return "$(final_exit_code "$code")" @@ -2113,12 +2165,13 @@ run_command() { run_devloop() { local spec_arg="$1" local max="$2" - local strict="$3" - local use_worktree="$4" - local coder="$5" - local reviewer="$6" - local create_pr="$7" - local timeout_minutes="${8:-$DEFAULT_TIMEOUT_MINUTES}" + local report_format="$3" + local strict="$4" + local use_worktree="$5" + local coder="$6" + local reviewer="$7" + local create_pr="$8" + local timeout_minutes="${9:-$DEFAULT_TIMEOUT_MINUTES}" MAX="$max" CODER="$coder" @@ -2228,11 +2281,15 @@ run_devloop() { run_branch="$(git -C "$repo" branch --show-current)" FINAL_BRANCH="$run_branch" TRACK=".devloop/tracks/$slug.md" - REPORT=".devloop/reports/$slug.md" + if [ "$report_format" = "html" ]; then + REPORT=".devloop/reports/$slug.html" + else + REPORT=".devloop/reports/$slug.md" + fi local coder_session=".devloop/sessions/$slug-coder-$coder.id" local reviewer_session=".devloop/sessions/$slug-reviewer-$reviewer.id" - init_track "$repo/$TRACK" "$run_spec" "$spec" "$PWD" "$SOURCE_REPO" "$repo" "$base" "$source_branch" "$run_branch" "$max" "$strict" "$coder" "$reviewer" "$WORK_TYPE" "$WORK_BREAKING" "$create_pr" "$timeout_minutes" + init_track "$repo/$TRACK" "$run_spec" "$spec" "$PWD" "$SOURCE_REPO" "$repo" "$base" "$source_branch" "$run_branch" "$max" "$report_format" "$strict" "$coder" "$reviewer" "$WORK_TYPE" "$WORK_BREAKING" "$create_pr" "$timeout_minutes" if [ "$create_pr" = true ]; then event_step "pull-request-lookup" "checking for existing PR" @@ -2410,7 +2467,7 @@ run_devloop() { CODER_SESSION_ID="$(read_first_line "$repo/$coder_session")" REVIEWER_SESSION_ID="$(read_first_line "$repo/$reviewer_session")" - synthesize_report "$repo" "$slug" "$reviewer" "$run_spec" "$spec" "$spec_text" "$SOURCE_REPO" "$repo" "$TRACK" "$REPORT" "$STATUS" "$PASSES" "$max" "$base" "$source_branch" "$FINAL_BRANCH" "$FINAL_COMMIT" "$FINAL_COMMIT_MESSAGE" "$PULL_REQUEST" "$PULL_REQUEST_ERROR" "$coder" "$repo/$reviewer_session" "$CODER_SESSION_ID" "$REVIEWER_SESSION_ID" + synthesize_report "$repo" "$slug" "$reviewer" "$run_spec" "$spec" "$spec_text" "$SOURCE_REPO" "$repo" "$TRACK" "$REPORT" "$STATUS" "$PASSES" "$max" "$base" "$source_branch" "$FINAL_BRANCH" "$FINAL_COMMIT" "$FINAL_COMMIT_MESSAGE" "$PULL_REQUEST" "$PULL_REQUEST_ERROR" "$coder" "$repo/$reviewer_session" "$CODER_SESSION_ID" "$REVIEWER_SESSION_ID" "$report_format" if [ "$create_pr" = true ] && [ -n "$PULL_REQUEST" ] && [ "$STATUS" != "pr-error" ]; then event_step "pr-final-report" "posting final report to PR" @@ -3138,17 +3195,19 @@ init_track() { local branch="$8" local worktree_branch="$9" local max="${10}" - local strict="${11}" - local coder="${12}" - local reviewer="${13}" - local type="${14}" - local breaking="${15}" - local create_pr="${16}" - local timeout_minutes="${17}" + local report_format="${11}" + local strict="${12}" + local coder="${13}" + local reviewer="${14}" + local type="${15}" + local breaking="${16}" + local create_pr="${17}" + local timeout_minutes="${18}" local track_name if [ -f "$file" ]; then return; fi track_name="$(basename "$file" .md)" - cat > "$file" < "$file" } read_first_line() { @@ -4063,12 +4128,17 @@ synthesize_report() { local reviewer_session_file="${22}" local coder_session_id="${23}" local reviewer_session_id="${24}" + local format="${25}" local title subtitle reviews metadata body prompt title="$(report_title "$spec_text" "$slug")" subtitle="$(report_subtitle "$spec_text" "$title")" reviews="$(list_reviews "$slug" "$pass" "$max")" metadata="$(report_metadata "$status" "$pass" "$max" "$repo" "$run_spec" "$source_spec" "$source_repo" "$worktree" "$base" "$initial_branch" "$branch" "$commit" "$commit_message" "$pull_request" "$pull_request_error" "$coder" "$reviewer" "$coder_session_id" "$reviewer_session_id" "$track" "$reviews")" - body="Write the report to $report in plain Markdown. Start with the report title as the H1, put the subtitle directly below it, put a topical three-line haiku immediately after the subtitle, then include these headings: Metadata, The shape of the problem, What was built, What the review caught (and why it mattered), What to remember next time, Residual risk, Pointers. Use terminal-friendly Markdown only: no HTML tags, embedded CSS, Mermaid, SVG, images, or external assets. If a diagram clarifies the run, draw it as ASCII inside a plain code fence. Do not optimize away substance: explain the decisions, tradeoffs, evidence, and transferable lessons clearly enough that the reader learns from the run." + if [ "$format" = "html" ]; then + body="Write the report to $report as valid standalone HTML. Use a readable document layout with embedded CSS, set the HTML to the report title, render the report title and subtitle before Metadata, render a topical three-line haiku immediately after the subtitle, use a compact metadata table, and add substantive sections after it. Include these visible section headings: Metadata, The shape of the problem, What was built, What the review caught (and why it mattered), What to remember next time, Residual risk, Pointers. Do not optimize away substance: explain the decisions, tradeoffs, evidence, and transferable lessons clearly enough that the reader learns from the run." + else + body="Write the report to $report in plain Markdown. Start with the report title as the H1, put the subtitle directly below it, put a topical three-line haiku immediately after the subtitle, then include these headings: Metadata, The shape of the problem, What was built, What the review caught (and why it mattered), What to remember next time, Residual risk, Pointers. Use terminal-friendly Markdown only: no HTML tags, embedded CSS, Mermaid, SVG, images, or external assets. If a diagram clarifies the run, draw it as ASCII inside a plain code fence. Do not optimize away substance: explain the decisions, tradeoffs, evidence, and transferable lessons clearly enough that the reader learns from the run." + fi prompt="$(cat <<EOF You are writing a learning-oriented post-mortem for a developer who just ran a devloop. diff --git a/scripts/devloop_test.sh b/scripts/devloop_test.sh index 67be55e..0f4083d 100755 --- a/scripts/devloop_test.sh +++ b/scripts/devloop_test.sh @@ -721,7 +721,7 @@ if ! run_setup_output="$( maybe_enter_worktree() { :; } interactive_run_setup "spec.md" )"; then fail "run setup defaults failed"; fi -equals "$run_setup_output" "spec.md 5 true true codex claude false 60" "run setup launches with defaults" +equals "$run_setup_output" "spec.md 5 markdown true true codex claude false 60" "run setup launches with defaults" configured_agents_home="$work/agents-home" configured_agents_repo="$work/agents-repo" mkdir -p "$configured_agents_home" "$configured_agents_repo/.devloop/specs" @@ -766,6 +766,162 @@ equals "$(ui_pick_from_file "$picker_file" "Pick")" "alpha" "non-tui picker fall equals "$(USE_TUI=true; ui_numbered_pick "$picker_file" "Pick" 2>/dev/null <<<"2")" "beta" "numbered picker" view_file "$picker_file" >/dev/null USE_TUI="$old_use_tui" + +preview_bin="$work/preview-bin" +preview_log="$work/preview.log" +mkdir -p "$preview_bin" +cat > "$preview_bin/glow" <<'GLOW' +#!/usr/bin/env bash +{ + printf 'glow' + for arg in "$@"; do printf ' <%s>' "$arg"; done + printf '\n' +} >> "$DEVLOOP_PREVIEW_LOG" +printf '%s\n' "rendered" +GLOW +cat > "$preview_bin/sed" <<'SED' +#!/usr/bin/env bash +{ + printf 'sed' + for arg in "$@"; do printf ' <%s>' "$arg"; done + printf '\n' +} >> "$DEVLOOP_PREVIEW_LOG" +printf '%s\n' "raw" +SED +chmod +x "$preview_bin/glow" "$preview_bin/sed" +preview_md="$work/preview.md" +preview_log_file="$work/preview-file.log" +preview_list="$work/preview-list.txt" +printf '%s\n' "# Preview" > "$preview_md" +printf '%s\n' "plain log" > "$preview_log_file" +printf '%s\n' "$preview_md" > "$preview_list" +: > "$preview_log" +DEVLOOP_PREVIEW_LOG="$preview_log" +export DEVLOOP_PREVIEW_LOG +old_use_tui="$USE_TUI" +USE_TUI=true +( + ui_has_fzf() { return 0; } + fzf() { + local preview="" arg selection expanded + while [ "$#" -gt 0 ]; do + arg="$1" + shift + if [ "$arg" = "--preview" ]; then + preview="$1" + shift + fi + done + IFS= read -r selection || return 1 + expanded="${preview//\{\}/$selection}" + FZF_PREVIEW_COLUMNS=77 PATH="$preview_bin:/usr/bin:/bin:/usr/sbin:/sbin" sh -c "$expanded" >/dev/null + printf '%s\n' "$selection" + } + ui_pick_from_file "$preview_list" "Pick" >/dev/null +) +contains "$(cat "$preview_log")" "glow <-w> <77> <$preview_md>" "fzf markdown preview" +not_contains "$(cat "$preview_log")" "sed" "fzf markdown preview" +printf '%s\n' "$preview_log_file" > "$preview_list" +: > "$preview_log" +( + ui_has_fzf() { return 0; } + fzf() { + local preview="" arg selection expanded + while [ "$#" -gt 0 ]; do + arg="$1" + shift + if [ "$arg" = "--preview" ]; then + preview="$1" + shift + fi + done + IFS= read -r selection || return 1 + expanded="${preview//\{\}/$selection}" + FZF_PREVIEW_COLUMNS=77 PATH="$preview_bin:/usr/bin:/bin:/usr/sbin:/sbin" sh -c "$expanded" >/dev/null + printf '%s\n' "$selection" + } + ui_pick_from_file "$preview_list" "Pick" >/dev/null +) +not_contains "$(cat "$preview_log")" "glow" "fzf non-markdown preview" +contains "$(cat "$preview_log")" "sed" "fzf non-markdown preview" +unset DEVLOOP_PREVIEW_LOG + +view_bin="$work/view-bin" +view_log="$work/view.log" +mkdir -p "$view_bin" +cat > "$view_bin/glow" <<'GLOW' +#!/usr/bin/env bash +{ + printf 'glow' + for arg in "$@"; do printf ' <%s>' "$arg"; done + printf '\n' +} >> "$DEVLOOP_VIEW_LOG" +printf '%s\n' "rendered markdown" +GLOW +cat > "$view_bin/gum" <<'GUM' +#!/usr/bin/env bash +{ + printf 'gum' + for arg in "$@"; do printf ' <%s>' "$arg"; done + printf '\n' +} >> "$DEVLOOP_VIEW_LOG" +cat >/dev/null +GUM +cat > "$view_bin/open" <<'OPEN' +#!/usr/bin/env bash +{ + printf 'open' + for arg in "$@"; do printf ' <%s>' "$arg"; done + printf '\n' +} >> "$DEVLOOP_VIEW_LOG" +OPEN +chmod +x "$view_bin/glow" "$view_bin/gum" "$view_bin/open" +view_md="$work/view.md" +view_html="$work/view.html" +view_log_file="$work/view.logfile" +printf '%s\n' "# View" > "$view_md" +printf '%s\n' "<html></html>" > "$view_html" +printf '%s\n' "raw log" > "$view_log_file" +old_path="$PATH" +old_use_tui="$USE_TUI" +DEVLOOP_VIEW_LOG="$view_log" +export DEVLOOP_VIEW_LOG +PATH="$view_bin:/usr/bin:/bin:/usr/sbin:/sbin" +USE_TUI=true +ui_has_gum() { [ "$USE_TUI" = true ] && command -v gum >/dev/null 2>&1; } +: > "$view_log" +view_file "$view_md" >/dev/null +contains "$(cat "$view_log")" "glow <$view_md>" "markdown view uses glow" +contains "$(cat "$view_log")" "gum <pager>" "markdown view uses pager" +rm -f "$view_bin/glow" +: > "$view_log" +view_file "$view_md" >/dev/null +not_contains "$(cat "$view_log")" "glow" "markdown view absent glow" +contains "$(cat "$view_log")" "gum <pager>" "markdown view absent glow fallback" +cat > "$view_bin/glow" <<'GLOW' +#!/usr/bin/env bash +{ + printf 'glow' + for arg in "$@"; do printf ' <%s>' "$arg"; done + printf '\n' +} >> "$DEVLOOP_VIEW_LOG" +printf '%s\n' "rendered markdown" +GLOW +chmod +x "$view_bin/glow" +USE_TUI=false +: > "$view_log" +view_file "$view_html" >/dev/null +contains "$(cat "$view_log")" "open <$view_html>" "html view opens browser" +not_contains "$(cat "$view_log")" "glow" "html view skips glow" +rm -f "$view_bin/gum" "$view_bin/open" +: > "$view_log" +equals "$(view_file "$view_log_file")" "raw log" "non-markdown raw view" +not_contains "$(cat "$view_log")" "glow" "non-markdown view skips glow" +PATH="$old_path" +USE_TUI="$old_use_tui" +ui_has_gum() { return 1; } +unset DEVLOOP_VIEW_LOG + equals "$(title_from_slug "chat-retry")" "Chat Retry" "title from slug" RUN_TIMEOUT_MINUTES=7 contains "$(timeout_message)" "7 minutes" "timeout message" @@ -875,8 +1031,8 @@ contains "$remote_dry_output" "verify: $remote_release_base/v$remote_version/dev contains "$remote_dry_output" "install: $remote_custom_root/$remote_version" "remote dry run install dir" contains "$remote_dry_output" "link: $remote_custom_bin/devloop -> $remote_custom_root/$remote_version/devloop" "remote dry run bin dir" contains "$remote_dry_output" "skills: $work/remote-dry-home/.agents/skills, $work/remote-dry-home/.claude/skills" "remote dry run skills" -contains "$remote_dry_output" "missing UI tools: gum fzf" "remote missing UI guidance" -contains "$remote_dry_output" "install with: brew install gum fzf" "remote missing UI guidance" +contains "$remote_dry_output" "missing UI tools: glow gum fzf" "remote missing UI guidance" +contains "$remote_dry_output" "install with: brew install glow gum fzf" "remote missing UI guidance" contains "$remote_dry_output" "missing agent CLIs: codex claude" "remote missing agent guidance" contains "$remote_dry_output" "Devloop does not install codex or claude automatically." "remote missing agent guidance" [[ ! -e "$remote_custom_root" ]] || fail "remote dry run created install root" @@ -927,7 +1083,7 @@ ok "remote installer rejects checksum mismatch" remote_tool_bin="$work/remote-tool-bin" mkdir -p "$remote_tool_bin" -for tool in gum fzf codex claude; do +for tool in glow gum fzf codex claude; do printf '%s\n' '#!/usr/bin/env bash' 'exit 0' > "$remote_tool_bin/$tool" chmod +x "$remote_tool_bin/$tool" done @@ -1091,6 +1247,7 @@ equals "$("$remote_default_bin/devloop" --version)" "devloop $remote_version" "r contains "$remote_install_output" "verified checksum" "remote install checksum" contains "$remote_install_output" "$remote_default_bin is not on PATH" "remote install PATH guidance" contains "$remote_install_output" "export PATH=\"$remote_default_bin:\$PATH\"" "remote install PATH guidance" +contains "$remote_install_output" "[ok] glow:" "remote install UI check" contains "$remote_install_output" "[ok] gum:" "remote install UI check" contains "$remote_install_output" "[ok] codex:" "remote install agent check" contains "$remote_install_output" "devloop $remote_version installed" "remote install banner version" @@ -1181,7 +1338,7 @@ shift tool_dir="$(cd "$(dirname "$0")" >/dev/null 2>&1 && pwd)" for formula in "$@"; do case "$formula" in - gum|fzf) + glow|gum|fzf) printf '%s\n' '#!/usr/bin/env bash' 'exit 0' > "$tool_dir/$formula" chmod +x "$tool_dir/$formula" ;; @@ -1195,6 +1352,7 @@ DEVLOOP_BIN_DIR="$bin_dir" HOME="$install_home" PATH="$install_path" "$SCRIPTS_D [[ -x "$REPO_ROOT/devloop" ]] || fail "devloop is not executable" [[ -L "$bin_dir/devloop" ]] || fail "installer did not create symlink" contains "$(cat /tmp/devloop-install-test.out)" "gh auth login" "installer optional gh auth" +PATH="$install_path" command -v glow >/dev/null 2>&1 || fail "installer did not make glow available" PATH="$install_path" command -v gum >/dev/null 2>&1 || fail "installer did not make gum available" PATH="$install_path" command -v fzf >/dev/null 2>&1 || fail "installer did not make fzf available" [[ -f "$install_home/.agents/skills/devloop-spec/SKILL.md" ]] || fail "installer did not install Codex spec skill" @@ -1225,6 +1383,7 @@ fake_bin="$work/fake-bin" mkdir -p "$fake_bin" printf '#!/usr/bin/env bash\nexit 0\n' > "$fake_bin/codex" printf '#!/usr/bin/env bash\nexit 0\n' > "$fake_bin/claude" +printf '#!/usr/bin/env bash\nexit 0\n' > "$fake_bin/glow" cat > "$fake_bin/gh" <<'GH' #!/usr/bin/env bash set -euo pipefail @@ -1340,7 +1499,7 @@ case "${1:-}" in esac GH chmod +x "$fake_bin/gh" -chmod +x "$fake_bin/codex" "$fake_bin/claude" +chmod +x "$fake_bin/codex" "$fake_bin/claude" "$fake_bin/glow" backlink_repo="$work/spec-backlink-repo" git init -q "$backlink_repo" @@ -1386,6 +1545,7 @@ contains "$doctor_output" "devloop doctor: ready" "doctor" contains "$doctor_output" "Required dependencies" "doctor" contains "$doctor_output" "[ok] codex:" "doctor" contains "$doctor_output" "[ok] claude:" "doctor" +contains "$doctor_output" "[ok] glow:" "doctor" contains "$doctor_output" "[ok] skill devloop-spec" "doctor" contains "$doctor_output" "[ok] gum:" "doctor" contains "$doctor_output" "[ok] fzf:" "doctor" @@ -1403,7 +1563,8 @@ no_gh_bin="$work/no-gh-bin" mkdir -p "$no_gh_bin" printf '#!/usr/bin/env bash\nexit 0\n' > "$no_gh_bin/codex" printf '#!/usr/bin/env bash\nexit 0\n' > "$no_gh_bin/claude" -chmod +x "$no_gh_bin/codex" "$no_gh_bin/claude" +printf '#!/usr/bin/env bash\nexit 0\n' > "$no_gh_bin/glow" +chmod +x "$no_gh_bin/codex" "$no_gh_bin/claude" "$no_gh_bin/glow" # Mirror the system bin dirs without gh so `command -v gh` fails regardless of # where gh is installed on the host (CI runners ship gh in /usr/bin). sys_clean="$work/sys-clean" @@ -1423,6 +1584,21 @@ contains "$doctor_no_gh_output" "[FAIL] gh installed" "doctor no gh" contains "$doctor_no_gh_output" "PR-backed loop readiness unavailable" "doctor no gh" ok "doctor optional GitHub readiness" +no_glow_bin="$work/no-glow-bin" +mkdir -p "$no_glow_bin" +printf '#!/usr/bin/env bash\nexit 0\n' > "$no_glow_bin/codex" +printf '#!/usr/bin/env bash\nexit 0\n' > "$no_glow_bin/claude" +printf '#!/usr/bin/env bash\nexit 0\n' > "$no_glow_bin/gum" +printf '#!/usr/bin/env bash\nexit 0\n' > "$no_glow_bin/fzf" +chmod +x "$no_glow_bin/codex" "$no_glow_bin/claude" "$no_glow_bin/gum" "$no_glow_bin/fzf" +if doctor_no_glow_output="$(HOME="$install_home" PATH="$bin_dir:$no_glow_bin:$sys_clean" "$bin_dir/devloop" doctor 2>&1)"; then + printf '%s\n' "$doctor_no_glow_output" >&2 + fail "doctor passed when glow was unavailable" +fi +contains "$doctor_no_glow_output" "[fail] missing command: glow" "doctor no glow" +contains "$doctor_no_glow_output" "devloop doctor: not ready" "doctor no glow" +ok "doctor requires glow" + agent="$work/spec-agent" cat > "$agent" <<'AGENT' #!/usr/bin/env bash @@ -1676,7 +1852,6 @@ run_loop() { local slug="$2" local mode="$3" local max="${4:-1}" - local extra="${5:-}" local args=() local old_home="$HOME" local old_path="$PATH" @@ -1686,8 +1861,9 @@ run_loop() { local old_enter_worktree="$ENTER_WORKTREE" local old_start_pass="$RUN_START_PASS" local code + shift 4 if [ "${DEVLOOP_FAKE_MODE+x}" = "x" ]; then had_mode=true; fi - if [ -n "$extra" ]; then args+=("$extra"); fi + args+=("$@") args+=(".specs/$slug.md" "$max") HOME="$install_home" PATH="$fake_bin:$bin_dir:$PATH" @@ -1861,6 +2037,20 @@ if grep -Eq '^gh pr (create|comment|list|view)' "$no_pr_gh_log" 2>/dev/null; the unset DEVLOOP_GH_LOG ok "e2e accept and verify" +loop_repo="$work/loop-html" +make_loop_repo "$loop_repo" "e2e-html" "E2E HTML" +if ! html_output="$(run_loop "$loop_repo" "e2e-html" accept 1 --report-format html 2>&1)"; then + printf '%s\n' "$html_output" >&2 + fail "html report loop failed" +fi +html_worktree="$(printf '%s\n' "$html_output" | sed -nE 's/^[[:space:]]*Worktree[[:space:]]+//p')" +[[ -f "$html_worktree/.devloop/reports/e2e-html.html" ]] || fail "html report loop did not write html report" +[[ ! -e "$html_worktree/.devloop/reports/e2e-html.md" ]] || fail "html report loop wrote markdown report" +contains "$(cat "$html_worktree/.devloop/tracks/e2e-html.md")" "- report-format: html" "html report track metadata" +contains "$(run_repo_main "$loop_repo" reports)" ".devloop/reports/e2e-html.html" "reports command includes html" +contains "$(run_repo_main "$loop_repo" status)" ".devloop/reports/e2e-html.html" "status command includes html report" +ok "e2e html report" + loop_repo="$work/loop-retry" make_loop_repo "$loop_repo" "e2e-retry" "E2E Retry" if ! retry_output="$(run_loop "$loop_repo" "e2e-retry" reject-then-accept 2 2>&1)"; then diff --git a/scripts/install.remote.sh b/scripts/install.remote.sh index 1cdb195..d77d7ab 100755 --- a/scripts/install.remote.sh +++ b/scripts/install.remote.sh @@ -205,6 +205,7 @@ check_ui_tools() { local missing_items=() local reply if [ -z "$missing_text" ]; then + info "[ok] glow: $(command -v glow)" info "[ok] gum: $(command -v gum)" info "[ok] fzf: $(command -v fzf)" return 0 @@ -234,10 +235,11 @@ check_ui_tools() { return 0 fi - missing_text="$(missing_commands gum fzf)" + missing_text="$(missing_commands glow gum fzf)" if [ -n "$missing_text" ]; then info "still missing UI tools: $missing_text" else + info "[ok] glow: $(command -v glow)" info "[ok] gum: $(command -v gum)" info "[ok] fzf: $(command -v fzf)" fi @@ -352,7 +354,7 @@ main() { VERSION="$(resolve_latest_version)" fi version="$(normalize_version "$VERSION")" - ui_missing="$(missing_commands gum fzf)" + ui_missing="$(missing_commands glow gum fzf)" agent_missing="$(missing_commands codex claude)" if [ "$DRY_RUN" = true ]; then diff --git a/scripts/install.sh b/scripts/install.sh index 07d2165..6b3214b 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -50,7 +50,7 @@ install_required_ui_tools() { local missing=() local tool - for tool in gum fzf; do + for tool in glow gum fzf; do if ! command -v "$tool" >/dev/null 2>&1; then missing+=("$tool") fi diff --git a/scripts/skill_helpers.sh b/scripts/skill_helpers.sh index 4b110b4..f5fcdaa 100644 --- a/scripts/skill_helpers.sh +++ b/scripts/skill_helpers.sh @@ -350,6 +350,7 @@ devloop_doctor() { devloop_doctor_command git || status=1 devloop_doctor_command codex || status=1 devloop_doctor_command claude || status=1 + devloop_doctor_command glow || status=1 devloop_doctor_command gum || status=1 devloop_doctor_command fzf || status=1 printf '\nSkills\n' From fad20e57816af4802162b12af0c3c2a606c68075 Mon Sep 17 00:00:00 2001 From: satyaborg <satya.borg@gmail.com> Date: Mon, 29 Jun 2026 15:11:04 +1000 Subject: [PATCH 2/3] fix: glow-terminal-rendering --- devloop | 2 +- scripts/devloop_test.sh | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/devloop b/devloop index cb2909e..30802b8 100755 --- a/devloop +++ b/devloop @@ -1117,7 +1117,7 @@ ui_pick_from_file() { if [ ! -s "$file" ]; then return 1; fi if ui_has_fzf; then # shellcheck disable=SC2016 - preview_cmd='case "{}" in *.md) if command -v glow >/dev/null 2>&1; then glow -w "$FZF_PREVIEW_COLUMNS" "{}"; else sed -n "1,80p" "{}" 2>/dev/null; fi ;; *) sed -n "1,80p" "{}" 2>/dev/null ;; esac' + preview_cmd='case {} in *.md) if command -v glow >/dev/null 2>&1; then glow -w "$FZF_PREVIEW_COLUMNS" {}; else sed -n "1,80p" {} 2>/dev/null; fi ;; *) sed -n "1,80p" {} 2>/dev/null ;; esac' fzf \ --height 70% \ --layout reverse \ diff --git a/scripts/devloop_test.sh b/scripts/devloop_test.sh index 0f4083d..ed7cadd 100755 --- a/scripts/devloop_test.sh +++ b/scripts/devloop_test.sh @@ -803,7 +803,7 @@ USE_TUI=true ( ui_has_fzf() { return 0; } fzf() { - local preview="" arg selection expanded + local preview="" arg selection quoted_selection expanded while [ "$#" -gt 0 ]; do arg="$1" shift @@ -813,7 +813,8 @@ USE_TUI=true fi done IFS= read -r selection || return 1 - expanded="${preview//\{\}/$selection}" + quoted_selection="'$selection'" + expanded="${preview//\{\}/$quoted_selection}" FZF_PREVIEW_COLUMNS=77 PATH="$preview_bin:/usr/bin:/bin:/usr/sbin:/sbin" sh -c "$expanded" >/dev/null printf '%s\n' "$selection" } @@ -826,7 +827,7 @@ printf '%s\n' "$preview_log_file" > "$preview_list" ( ui_has_fzf() { return 0; } fzf() { - local preview="" arg selection expanded + local preview="" arg selection quoted_selection expanded while [ "$#" -gt 0 ]; do arg="$1" shift @@ -836,7 +837,8 @@ printf '%s\n' "$preview_log_file" > "$preview_list" fi done IFS= read -r selection || return 1 - expanded="${preview//\{\}/$selection}" + quoted_selection="'$selection'" + expanded="${preview//\{\}/$quoted_selection}" FZF_PREVIEW_COLUMNS=77 PATH="$preview_bin:/usr/bin:/bin:/usr/sbin:/sbin" sh -c "$expanded" >/dev/null printf '%s\n' "$selection" } From 9cbdc63eeec783916f05ffb57174d3e77e64d49b Mon Sep 17 00:00:00 2001 From: satyaborg <satya.borg@gmail.com> Date: Mon, 29 Jun 2026 15:39:36 +1000 Subject: [PATCH 3/3] fix: keep glow rendering markdown-only --- devloop | 97 +++++++++++------------------------------ scripts/devloop_test.sh | 37 ++-------------- 2 files changed, 29 insertions(+), 105 deletions(-) diff --git a/devloop b/devloop index 30802b8..da05bc1 100755 --- a/devloop +++ b/devloop @@ -1552,7 +1552,7 @@ interactive_run_setup() { return 0 fi run_header "$spec" "$max" "$strict" "$use_worktree" "$coder" "$reviewer" "$create_pr" "$timeout_minutes" - run_devloop "$spec" "$max" "markdown" "$strict" "$use_worktree" "$coder" "$reviewer" "$create_pr" "$timeout_minutes" + run_devloop "$spec" "$max" "$strict" "$use_worktree" "$coder" "$reviewer" "$create_pr" "$timeout_minutes" code=$? maybe_enter_worktree return "$(final_exit_code "$code")" @@ -1783,22 +1783,13 @@ list_artifact_files() { while IFS= read -r root; do dir="$root/$subdir" if [ -d "$dir" ]; then - case "$subdir" in - .devloop/reports) find "$dir" -type f \( -name '*.md' -o -name '*.html' -o -name '*.htm' \) | LC_ALL=C sort ;; - *) find "$dir" -type f -name '*.md' | LC_ALL=C sort ;; - esac + find "$dir" -type f -name '*.md' | LC_ALL=C sort fi done } view_file() { local file="$1" - case "$file" in - *.html|*.htm) - if command -v open >/dev/null 2>&1 && open "$file" >/dev/null 2>&1; then return 0; fi - if command -v xdg-open >/dev/null 2>&1 && xdg-open "$file" >/dev/null 2>&1; then return 0; fi - ;; - esac case "$file" in *.md) if ui_has_glow; then @@ -1888,12 +1879,8 @@ track_report_path() { local slug dir report slug="$(basename "$track" .md)" dir="$(cd "$(dirname "$track")/.." >/dev/null 2>&1 && pwd -P)/reports" - for report in "$dir/$slug.html" "$dir/$slug.md"; do - if [ -f "$report" ]; then - printf '%s\n' "$report" - return - fi - done + report="$dir/$slug.md" + if [ -f "$report" ]; then printf '%s\n' "$report"; fi } print_track_status() { @@ -1974,7 +1961,7 @@ remove_devloop_worktree() { run_from_track() { local track="$1" - local worktree spec max report_format strict coder reviewer create_pr timeout_minutes old_pwd code next_pass + local worktree spec max strict coder reviewer create_pr timeout_minutes old_pwd code next_pass track="$(absolute_existing_file "$track")" || { printf 'track not found: %s\n' "$track" >&2 return 2 @@ -1982,7 +1969,6 @@ run_from_track() { worktree="$(track_value "worktree" "$track")" spec="$(track_value "spec" "$track")" max="$(track_value "max" "$track")" - report_format="$(track_value "report-format" "$track")" strict="$(track_value "strict" "$track")" coder="$(track_value "coder" "$track")" reviewer="$(track_value "reviewer" "$track")" @@ -1992,10 +1978,6 @@ run_from_track() { if [ -z "$worktree" ]; then worktree="$(cd "$(dirname "$track")/../.." >/dev/null 2>&1 && pwd -P)"; fi if [ -z "$spec" ]; then spec="$(track_value "source-spec" "$track")"; fi if [ -z "$max" ]; then max=5; fi - case "$report_format" in - html) ;; - *) report_format="markdown" ;; - esac if [ -z "$strict" ]; then strict=true; fi if [ -z "$coder" ]; then coder="$(devloop_coder)"; fi if [ -z "$reviewer" ]; then reviewer="$(devloop_reviewer)"; fi @@ -2017,7 +1999,7 @@ run_from_track() { cd "$worktree" || return 2 RUN_START_PASS="$next_pass" run_header "$spec" "$max" "$strict" false "$coder" "$reviewer" "$create_pr" "$timeout_minutes" - run_devloop "$spec" "$max" "$report_format" "$strict" false "$coder" "$reviewer" "$create_pr" "$timeout_minutes" + run_devloop "$spec" "$max" "$strict" false "$coder" "$reviewer" "$create_pr" "$timeout_minutes" code=$? RUN_START_PASS=1 maybe_enter_worktree @@ -2066,7 +2048,6 @@ will_enter_worktree_shell() { } run_command() { - local report_format="markdown" local strict=true local use_worktree=true local coder @@ -2085,16 +2066,6 @@ run_command() { arg="$1" shift case "$arg" in - --report-format) - if [ "$#" -eq 0 ]; then usage >&2; return 2; fi - value="$1" - shift - case "$value" in - html) report_format="html" ;; - markdown|md) report_format="markdown" ;; - *) usage >&2; return 2 ;; - esac - ;; --coder) if [ "$#" -eq 0 ]; then printf '%s\n' "coder must be Codex or Claude Code" >&2; usage >&2; return 2; fi value="$(normalize_agent "$1")" @@ -2156,7 +2127,7 @@ run_command() { if [ "$max" -gt 10 ]; then max=10; fi run_header "$spec" "$max" "$strict" "$use_worktree" "$coder" "$reviewer" "$create_pr" "$timeout_minutes" - run_devloop "$spec" "$max" "$report_format" "$strict" "$use_worktree" "$coder" "$reviewer" "$create_pr" "$timeout_minutes" + run_devloop "$spec" "$max" "$strict" "$use_worktree" "$coder" "$reviewer" "$create_pr" "$timeout_minutes" local code=$? maybe_enter_worktree return "$(final_exit_code "$code")" @@ -2165,13 +2136,12 @@ run_command() { run_devloop() { local spec_arg="$1" local max="$2" - local report_format="$3" - local strict="$4" - local use_worktree="$5" - local coder="$6" - local reviewer="$7" - local create_pr="$8" - local timeout_minutes="${9:-$DEFAULT_TIMEOUT_MINUTES}" + local strict="$3" + local use_worktree="$4" + local coder="$5" + local reviewer="$6" + local create_pr="$7" + local timeout_minutes="${8:-$DEFAULT_TIMEOUT_MINUTES}" MAX="$max" CODER="$coder" @@ -2281,15 +2251,11 @@ run_devloop() { run_branch="$(git -C "$repo" branch --show-current)" FINAL_BRANCH="$run_branch" TRACK=".devloop/tracks/$slug.md" - if [ "$report_format" = "html" ]; then - REPORT=".devloop/reports/$slug.html" - else - REPORT=".devloop/reports/$slug.md" - fi + REPORT=".devloop/reports/$slug.md" local coder_session=".devloop/sessions/$slug-coder-$coder.id" local reviewer_session=".devloop/sessions/$slug-reviewer-$reviewer.id" - init_track "$repo/$TRACK" "$run_spec" "$spec" "$PWD" "$SOURCE_REPO" "$repo" "$base" "$source_branch" "$run_branch" "$max" "$report_format" "$strict" "$coder" "$reviewer" "$WORK_TYPE" "$WORK_BREAKING" "$create_pr" "$timeout_minutes" + init_track "$repo/$TRACK" "$run_spec" "$spec" "$PWD" "$SOURCE_REPO" "$repo" "$base" "$source_branch" "$run_branch" "$max" "$strict" "$coder" "$reviewer" "$WORK_TYPE" "$WORK_BREAKING" "$create_pr" "$timeout_minutes" if [ "$create_pr" = true ]; then event_step "pull-request-lookup" "checking for existing PR" @@ -2467,7 +2433,7 @@ run_devloop() { CODER_SESSION_ID="$(read_first_line "$repo/$coder_session")" REVIEWER_SESSION_ID="$(read_first_line "$repo/$reviewer_session")" - synthesize_report "$repo" "$slug" "$reviewer" "$run_spec" "$spec" "$spec_text" "$SOURCE_REPO" "$repo" "$TRACK" "$REPORT" "$STATUS" "$PASSES" "$max" "$base" "$source_branch" "$FINAL_BRANCH" "$FINAL_COMMIT" "$FINAL_COMMIT_MESSAGE" "$PULL_REQUEST" "$PULL_REQUEST_ERROR" "$coder" "$repo/$reviewer_session" "$CODER_SESSION_ID" "$REVIEWER_SESSION_ID" "$report_format" + synthesize_report "$repo" "$slug" "$reviewer" "$run_spec" "$spec" "$spec_text" "$SOURCE_REPO" "$repo" "$TRACK" "$REPORT" "$STATUS" "$PASSES" "$max" "$base" "$source_branch" "$FINAL_BRANCH" "$FINAL_COMMIT" "$FINAL_COMMIT_MESSAGE" "$PULL_REQUEST" "$PULL_REQUEST_ERROR" "$coder" "$repo/$reviewer_session" "$CODER_SESSION_ID" "$REVIEWER_SESSION_ID" if [ "$create_pr" = true ] && [ -n "$PULL_REQUEST" ] && [ "$STATUS" != "pr-error" ]; then event_step "pr-final-report" "posting final report to PR" @@ -3195,19 +3161,17 @@ init_track() { local branch="$8" local worktree_branch="$9" local max="${10}" - local report_format="${11}" - local strict="${12}" - local coder="${13}" - local reviewer="${14}" - local type="${15}" - local breaking="${16}" - local create_pr="${17}" - local timeout_minutes="${18}" + local strict="${11}" + local coder="${12}" + local reviewer="${13}" + local type="${14}" + local breaking="${15}" + local create_pr="${16}" + local timeout_minutes="${17}" local track_name if [ -f "$file" ]; then return; fi track_name="$(basename "$file" .md)" - { - cat <<EOF + cat > "$file" <<EOF # Track: $track_name - spec: $spec @@ -3223,18 +3187,12 @@ init_track() { - type: $type - breaking: $breaking - max: $max -EOF - if [ "$report_format" = "html" ]; then - printf '%s\n' "- report-format: html" - fi - cat <<EOF - strict: $strict - create-pr: $create_pr - timeout-minutes: $timeout_minutes - started: $(date -u +"%Y-%m-%dT%H:%M:%SZ") EOF - } > "$file" } read_first_line() { @@ -4128,17 +4086,12 @@ synthesize_report() { local reviewer_session_file="${22}" local coder_session_id="${23}" local reviewer_session_id="${24}" - local format="${25}" local title subtitle reviews metadata body prompt title="$(report_title "$spec_text" "$slug")" subtitle="$(report_subtitle "$spec_text" "$title")" reviews="$(list_reviews "$slug" "$pass" "$max")" metadata="$(report_metadata "$status" "$pass" "$max" "$repo" "$run_spec" "$source_spec" "$source_repo" "$worktree" "$base" "$initial_branch" "$branch" "$commit" "$commit_message" "$pull_request" "$pull_request_error" "$coder" "$reviewer" "$coder_session_id" "$reviewer_session_id" "$track" "$reviews")" - if [ "$format" = "html" ]; then - body="Write the report to $report as valid standalone HTML. Use a readable document layout with embedded CSS, set the HTML <title> to the report title, render the report title and subtitle before Metadata, render a topical three-line haiku immediately after the subtitle, use a compact metadata table, and add substantive sections after it. Include these visible section headings: Metadata, The shape of the problem, What was built, What the review caught (and why it mattered), What to remember next time, Residual risk, Pointers. Do not optimize away substance: explain the decisions, tradeoffs, evidence, and transferable lessons clearly enough that the reader learns from the run." - else - body="Write the report to $report in plain Markdown. Start with the report title as the H1, put the subtitle directly below it, put a topical three-line haiku immediately after the subtitle, then include these headings: Metadata, The shape of the problem, What was built, What the review caught (and why it mattered), What to remember next time, Residual risk, Pointers. Use terminal-friendly Markdown only: no HTML tags, embedded CSS, Mermaid, SVG, images, or external assets. If a diagram clarifies the run, draw it as ASCII inside a plain code fence. Do not optimize away substance: explain the decisions, tradeoffs, evidence, and transferable lessons clearly enough that the reader learns from the run." - fi + body="Write the report to $report in plain Markdown. Start with the report title as the H1, put the subtitle directly below it, put a topical three-line haiku immediately after the subtitle, then include these headings: Metadata, The shape of the problem, What was built, What the review caught (and why it mattered), What to remember next time, Residual risk, Pointers. Use terminal-friendly Markdown only: no HTML tags, embedded CSS, Mermaid, SVG, images, or external assets. If a diagram clarifies the run, draw it as ASCII inside a plain code fence. Do not optimize away substance: explain the decisions, tradeoffs, evidence, and transferable lessons clearly enough that the reader learns from the run." prompt="$(cat <<EOF You are writing a learning-oriented post-mortem for a developer who just ran a devloop. diff --git a/scripts/devloop_test.sh b/scripts/devloop_test.sh index ed7cadd..c3ed934 100755 --- a/scripts/devloop_test.sh +++ b/scripts/devloop_test.sh @@ -721,7 +721,7 @@ if ! run_setup_output="$( maybe_enter_worktree() { :; } interactive_run_setup "spec.md" )"; then fail "run setup defaults failed"; fi -equals "$run_setup_output" "spec.md 5 markdown true true codex claude false 60" "run setup launches with defaults" +equals "$run_setup_output" "spec.md 5 true true codex claude false 60" "run setup launches with defaults" configured_agents_home="$work/agents-home" configured_agents_repo="$work/agents-repo" mkdir -p "$configured_agents_home" "$configured_agents_repo/.devloop/specs" @@ -869,20 +869,10 @@ cat > "$view_bin/gum" <<'GUM' } >> "$DEVLOOP_VIEW_LOG" cat >/dev/null GUM -cat > "$view_bin/open" <<'OPEN' -#!/usr/bin/env bash -{ - printf 'open' - for arg in "$@"; do printf ' <%s>' "$arg"; done - printf '\n' -} >> "$DEVLOOP_VIEW_LOG" -OPEN -chmod +x "$view_bin/glow" "$view_bin/gum" "$view_bin/open" +chmod +x "$view_bin/glow" "$view_bin/gum" view_md="$work/view.md" -view_html="$work/view.html" view_log_file="$work/view.logfile" printf '%s\n' "# View" > "$view_md" -printf '%s\n' "<html></html>" > "$view_html" printf '%s\n' "raw log" > "$view_log_file" old_path="$PATH" old_use_tui="$USE_TUI" @@ -912,11 +902,6 @@ GLOW chmod +x "$view_bin/glow" USE_TUI=false : > "$view_log" -view_file "$view_html" >/dev/null -contains "$(cat "$view_log")" "open <$view_html>" "html view opens browser" -not_contains "$(cat "$view_log")" "glow" "html view skips glow" -rm -f "$view_bin/gum" "$view_bin/open" -: > "$view_log" equals "$(view_file "$view_log_file")" "raw log" "non-markdown raw view" not_contains "$(cat "$view_log")" "glow" "non-markdown view skips glow" PATH="$old_path" @@ -1854,6 +1839,7 @@ run_loop() { local slug="$2" local mode="$3" local max="${4:-1}" + local extra="${5:-}" local args=() local old_home="$HOME" local old_path="$PATH" @@ -1863,9 +1849,8 @@ run_loop() { local old_enter_worktree="$ENTER_WORKTREE" local old_start_pass="$RUN_START_PASS" local code - shift 4 if [ "${DEVLOOP_FAKE_MODE+x}" = "x" ]; then had_mode=true; fi - args+=("$@") + if [ -n "$extra" ]; then args+=("$extra"); fi args+=(".specs/$slug.md" "$max") HOME="$install_home" PATH="$fake_bin:$bin_dir:$PATH" @@ -2039,20 +2024,6 @@ if grep -Eq '^gh pr (create|comment|list|view)' "$no_pr_gh_log" 2>/dev/null; the unset DEVLOOP_GH_LOG ok "e2e accept and verify" -loop_repo="$work/loop-html" -make_loop_repo "$loop_repo" "e2e-html" "E2E HTML" -if ! html_output="$(run_loop "$loop_repo" "e2e-html" accept 1 --report-format html 2>&1)"; then - printf '%s\n' "$html_output" >&2 - fail "html report loop failed" -fi -html_worktree="$(printf '%s\n' "$html_output" | sed -nE 's/^[[:space:]]*Worktree[[:space:]]+//p')" -[[ -f "$html_worktree/.devloop/reports/e2e-html.html" ]] || fail "html report loop did not write html report" -[[ ! -e "$html_worktree/.devloop/reports/e2e-html.md" ]] || fail "html report loop wrote markdown report" -contains "$(cat "$html_worktree/.devloop/tracks/e2e-html.md")" "- report-format: html" "html report track metadata" -contains "$(run_repo_main "$loop_repo" reports)" ".devloop/reports/e2e-html.html" "reports command includes html" -contains "$(run_repo_main "$loop_repo" status)" ".devloop/reports/e2e-html.html" "status command includes html report" -ok "e2e html report" - loop_repo="$work/loop-retry" make_loop_repo "$loop_repo" "e2e-retry" "E2E Retry" if ! retry_output="$(run_loop "$loop_repo" "e2e-retry" reject-then-accept 2 2>&1)"; then