Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d8c3d18
Enhance git-ai-commit with model auto-escalation and response cleaning
greenc-FNAL Jun 9, 2026
2103e93
The staged changes add two files to the `.github/prompts/` directory:
greenc-FNAL Jun 8, 2026
d4bae39
Update pre-commit hooks
greenc-FNAL Jun 8, 2026
3c62f31
Improve devcontainer networking and Kilo Code integration
greenc-FNAL Jun 8, 2026
e3761ba
docs: add export-actions-plan
greenc-FNAL Jun 9, 2026
f6f10dd
Plan fixes and revisions after execution of phase 2
greenc-FNAL Jun 9, 2026
5fb529d
chore: use standalone action repositories (supersedes #614)
greenc-FNAL Jun 9, 2026
601053f
docs: fix Phase 5 -- stage workflow edits before commit, use upstream…
greenc-FNAL Jun 9, 2026
429ead3
docs: add CI, release, and agent instructions for action repos
greenc-FNAL Jun 10, 2026
b1b6ca1
scripts/git-ai-commit: support forwarding arbitrary git commit options
greenc-FNAL Jun 10, 2026
4738cbd
Address Markdown errors and fix generation to avoid
greenc-FNAL Jun 10, 2026
b6470d7
Add man page support and git-ai-commit subcommand to devcontainer
greenc-FNAL Jun 10, 2026
c885148
Allow pre-commit hooks with Python 3.9
greenc-FNAL Jun 10, 2026
f783866
Split overlong line
greenc-FNAL Jun 10, 2026
4c35e4e
Accomodate apparent behavior change in CodeQL for actions
greenc-FNAL Jun 10, 2026
50d63c5
Put CodeQL suppression on the correct line
greenc-FNAL Jun 10, 2026
0a4d5ae
Add GITHUB_WORKSPACE to safe directories
greenc-FNAL Jun 10, 2026
a714765
scripts/check_codeql_alerts.py: add timeout and improve ref handling
greenc-FNAL Jun 10, 2026
8b4f5a4
scripts/git-ai-commit: update CodeQL suppression comment
greenc-FNAL Jun 11, 2026
45796ec
scripts/check_codeql_alerts.py: skip base comparison when base equals…
greenc-FNAL Jun 11, 2026
f1d4a13
This time for sure
greenc-FNAL Jun 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,39 @@ fi
echo "Python symlink validated: $python_link -> $(readlink "$python_link")"
VALIDATE_PYTHON_SYMLINK

# Remove the Ubuntu minimal dpkg excludes file so that subsequent package
# installs populate /usr/share/man and /usr/share/doc normally. Without this,
# every apt-get install silently strips man pages and other documentation,
# producing the "This system has been minimized" warning whenever `man` is run.
# Also install man-db (the `man` command and database) and groff (the troff
# rendering pipeline required to display man pages), then reinstall git and
# git-man to restore man pages that were already stripped at image build time.
# mandoc is added as a lightweight alternative renderer (e.g. for CI linting).
RUN <<'INSTALL_MAN_TOOLS'
set -euo pipefail
# Remove the dpkg excludes file so future installs populate /usr/share/man.
rm -f /etc/dpkg/dpkg.cfg.d/excludes
apt-get update
apt-get install -y --no-install-recommends man-db groff mandoc
# Reinstall git and git-man to recover already-stripped man pages.
apt-get install -y --no-install-recommends --reinstall git git-man
apt-get clean
rm -rf /var/lib/apt/lists/*
# Ubuntu minimal diverts /usr/bin/man to a stub shell script that prints the
# "This system has been minimized" warning. Installing man-db is not enough
# to restore the real binary — the divert must be explicitly removed.
if [ "$(dpkg-divert --truename /usr/bin/man)" = "/usr/bin/man.REAL" ]; then
rm -f /usr/bin/man
dpkg-divert --quiet --remove --rename /usr/bin/man
fi
INSTALL_MAN_TOOLS

# Install podman client and socat inside the container to support nested
# container communication and provide a 'docker' command alias.
RUN <<'INSTALL_PODMAN_CLIENT'
set -euo pipefail
apt-get update
apt-get install -y --no-install-recommends podman socat
apt-get install -y --no-install-recommends ssh podman socat
apt-get clean
rm -rf /var/lib/apt/lists/*
INSTALL_PODMAN_CLIENT
Expand Down
23 changes: 17 additions & 6 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,19 @@
"--format=docker"
]
},
"runArgs": [],
"workspaceFolder": "/workspaces/phlex",
"remoteUser": "root",
"remoteEnv": {
"KILO_CONFIG_CONTENT": "${localEnv:KILO_CONFIG_CONTENT}"
// KILO_CONFIG_CONTENT_DOCKER rewrites 127.0.0.1:PORT to
// host.docker.internal:RELAY_PORT for the socat relay used when
// headroom runs as a local proxy. Passed through here so that
// post-create.sh can wire it into the container's .bashrc
// conditionally: if non-empty it sets KILO_CONFIG_CONTENT to this
// value; otherwise KILO_CONFIG_CONTENT is left unset and Kilo falls
// back to ~/.config/kilo/kilo.jsonc (bind-mounted from the host).
"KILO_CONFIG_CONTENT_DOCKER": "${localEnv:KILO_CONFIG_CONTENT_DOCKER}",
"KILO_API_KEY": "${localEnv:HEADROOM_UPSTREAM_KEY}"
},
"containerEnv": {
"CMAKE_GENERATOR": "Ninja",
Expand All @@ -19,17 +28,18 @@
"GNUPGHOME": "/root/.gnupg"
},
"mounts": [
"source=${localWorkspaceFolder}/../phlex-coding-guidelines,target=/workspaces/phlex-coding-guidelines,type=bind",
"source=${localWorkspaceFolder}/../phlex-design,target=/workspaces/phlex-design,type=bind",
"source=${localWorkspaceFolder}/../phlex-examples,target=/workspaces/phlex-examples,type=bind",
"source=${localWorkspaceFolder}/../phlex-coding-guidelines,target=/workspaces/phlex-coding-guidelines,type=bind",
"source=${localWorkspaceFolder}/../phlex-spack-recipes,target=/workspaces/phlex-spack-recipes,type=bind",
"source=${localEnv:HOME}/.vscode-remote-user-data,target=/root/.vscode-server-insiders/data/User,type=bind",
"source=${localEnv:HOME}/.podman-proxy/podman.sock,target=/tmp/podman.sock,type=bind",
"source=${localEnv:HOME}/.aws,target=/root/.aws,type=bind",
"source=${localEnv:HOME}/.config/gh,target=/root/.config/gh,type=bind,readonly",
"source=${localEnv:HOME}/.config/kilo,target=/root/.config/kilo,type=bind",
"source=${localEnv:HOME}/.gnupg,target=/root/.gnupg,type=bind",
"source=${localEnv:HOME}/.kiro,target=/root/.kiro,type=bind"
"source=${localEnv:HOME}/.kiro,target=/root/.kiro,type=bind",
"source=phlex-kilo-data,target=/root/.local/share/kilo,type=volume",
"source=${localEnv:HOME}/.podman-proxy/podman.sock,target=/tmp/podman.sock,type=bind",
"source=${localEnv:HOME}/.vscode-remote-user-data,target=/root/.vscode-server-insiders/data/User,type=bind"
],
"initializeCommand": "bash .devcontainer/ensure-repos.sh",
"onCreateCommand": "bash .devcontainer/setup-repos.sh /workspaces",
Expand Down Expand Up @@ -81,7 +91,8 @@
"**/.venv/**": true,
"**/py_virtual_env/**": true
},
"github.copilot-chat.usePreReleaseVersion": false
"github.copilot-chat.usePreReleaseVersion": false,
"kilocode.new.extraCaCerts": ""
},
"extensions": [
"charliermarsh.ruff",
Expand Down
127 changes: 100 additions & 27 deletions .devcontainer/ensure-repos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,59 @@ clone_if_absent() {
done
}

ensure_bind_dir() {
mkdir -p "$@"
}

# start_socat_relay LABEL KILL_PATTERN LISTEN_SPEC CONNECT_SPEC LOGFILE READY_TEST
#
# Start a background socat relay, waiting up to 2 s for it to become ready.
#
# LABEL - human-readable name for log messages
# KILL_PATTERN - argument to pkill -f to stop any existing relay
# LISTEN_SPEC - socat first address (the listening side)
# CONNECT_SPEC - socat second address (the connecting side)
# LOGFILE - path for socat stdout/stderr
# READY_TEST - shell command whose exit code indicates readiness
#
# Returns 0 if the relay is ready, 1 otherwise.
start_socat_relay() {
local label="$1"
local kill_pattern="$2"
local listen_spec="$3"
local connect_spec="$4"
local logfile="$5"
local ready_test="$6"

if ! command -v socat >/dev/null 2>&1; then
echo "WARNING: socat not found; cannot start ${label} relay" >&2
return 1
fi

# Stop any pre-existing relay process.
pkill -f "${kill_pattern}" 2>/dev/null || true
sleep 0.2

echo "Starting ${label} relay: ${listen_spec} -> ${connect_spec} ..."
nohup setsid socat "${listen_spec}" "${connect_spec}" > "${logfile}" 2>&1 &

# Wait up to 2 s for the relay to become ready.
local i=0
while [ "${i}" -lt 20 ]; do
eval "${ready_test}" 2>/dev/null && break
sleep 0.1
i=$(( i + 1 ))
done

if eval "${ready_test}" 2>/dev/null; then
echo "${label} relay ready"
return 0
else
echo "WARNING: ${label} relay did not become ready in time" >&2
return 1
fi
}

clone_if_absent phlex-design
clone_if_absent phlex-examples
clone_if_absent phlex-coding-guidelines
Expand All @@ -68,35 +121,16 @@ PODMAN_REAL_SOCKET="${XDG_RUNTIME_DIR:-/run/user/${USER_ID}}/podman/podman.sock"
PROXY_DIR="${HOME}/.podman-proxy"
PROXY_SOCKET="${PROXY_DIR}/podman.sock"

# Kill any existing socat proxy for this socket
pkill -f "socat UNIX-LISTEN:${PROXY_SOCKET}" || true
# Wait for old process to die and socket to be removed
sleep 0.2
mkdir -p "${PROXY_DIR}"
chmod 700 "${PROXY_DIR}"

mkdir -p "${PROXY_DIR}"
chmod 700 "${PROXY_DIR}"
ensure_bind_dir -m 0700 "${PROXY_DIR}"

if [ -S "${PODMAN_REAL_SOCKET}" ]; then
if command -v socat >/dev/null 2>&1; then
echo "Proxying Podman socket ${PODMAN_REAL_SOCKET} -> ${PROXY_SOCKET} ..."
# Use a background socat to proxy the real socket to the proxy socket.
# This socket will be bind-mounted at the SAME PATH in the container.
nohup setsid socat "UNIX-LISTEN:${PROXY_SOCKET},fork,reuseaddr,unlink-early" "UNIX-CONNECT:${PODMAN_REAL_SOCKET}" > /tmp/socat-podman.log 2>&1 &
# Wait for socket to be created
i=0
while [ $i -lt 20 ] && [ ! -S "${PROXY_SOCKET}" ]; do
sleep 0.1
i=$((i + 1))
done
if [ ! -S "${PROXY_SOCKET}" ]; then
echo "WARNING: Socket creation timed out; creating placeholder" >&2
socat "UNIX-LISTEN:${PROXY_SOCKET},fork,reuseaddr" "UNIX-CONNECT:${PODMAN_REAL_SOCKET}" &
fi
else
echo "WARNING: socat not found on host; act will not work inside the container" >&2
fi
start_socat_relay \
"Podman socket" \
"socat UNIX-LISTEN:${PROXY_SOCKET}" \
"UNIX-LISTEN:${PROXY_SOCKET},fork,reuseaddr,unlink-early" \
"UNIX-CONNECT:${PODMAN_REAL_SOCKET}" \
"/tmp/socat-podman.log" \
"[ -S '${PROXY_SOCKET}' ]" || true
else
echo "WARNING: Podman socket not found at ${PODMAN_REAL_SOCKET}" >&2
echo " To use 'act' inside the devcontainer, enable the Podman socket:" >&2
Expand All @@ -116,4 +150,43 @@ s.bind('${PROXY_SOCKET}')
" 2>/dev/null || touch "${PROXY_SOCKET}"
fi

# --- Headroom Proxy Relay for Devcontainer ---
#
# The headroom proxy is an SSH-tunnelled port bound only to 127.0.0.1 on this
# host. Rootless Podman uses pasta for container networking, so containers
# reach the host via host.docker.internal (169.254.1.2) rather than via a
# bridge interface. However, pasta maps host.docker.internal to the host's
# loopback only for ports that are actually listening on all interfaces --
# headroom's port is bound to 127.0.0.1 only and is therefore unreachable.
#
# We relay headroom onto a different port on 0.0.0.0 so that containers can
# reach it via host.docker.internal:$HEADROOM_RELAY_PORT. A different port is
# required because 0.0.0.0:$HEADROOM_PORT would conflict with the existing
# 127.0.0.1:$HEADROOM_PORT listener. KILO_CONFIG_CONTENT_DOCKER is set in
# ~/.bashrc and ~/.zshenv to point at host.docker.internal:$HEADROOM_RELAY_PORT.
Comment on lines +165 to +166

HEADROOM_PORT="${HEADROOM_PORT:-9797}"
HEADROOM_RELAY_PORT=$(( HEADROOM_PORT + 10000 ))
HEADROOM_LOCAL="127.0.0.1:${HEADROOM_PORT}"

if ss -tlnp 2>/dev/null | grep -q "127.0.0.1:${HEADROOM_PORT}"; then
start_socat_relay \
"Headroom proxy" \
"socat TCP-LISTEN:${HEADROOM_RELAY_PORT}" \
"TCP-LISTEN:${HEADROOM_RELAY_PORT},fork,reuseaddr" \
"TCP:${HEADROOM_LOCAL}" \
"/tmp/socat-headroom.log" \
"ss -tlnp 2>/dev/null | grep -q ':${HEADROOM_RELAY_PORT} '" || true
else
echo "WARNING: headroom proxy not detected at ${HEADROOM_LOCAL}; skipping relay" >&2
echo " Ensure the SSH tunnel is active (headroom running on your laptop)" >&2
fi

# Ensure remaining source bind mount points exist.
ensure_bind_dir "$HOME/.aws"
ensure_bind_dir "$HOME/.config/"{gh,kilo}
ensure_bind_dir "$HOME/.gnupg"
ensure_bind_dir "$HOME/.kiro"
ensure_bind_dir "$HOME/.vscode-remote-user-data"

echo "SUCCESS: .devcontainer/ensure-repos.sh completed successfully"
61 changes: 61 additions & 0 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,67 @@ EOF
# installation on every rebuild.
rm -f /root/.vscode-server-insiders/data/Machine/.installExtensionsMarker

# Set KILO_CONFIG_CONTENT for interactive shells. When KILO_CONFIG_CONTENT_DOCKER
# is non-empty (headroom local proxy via socat relay), use it so that the baseURL
# points at host.docker.internal rather than 127.0.0.1. Otherwise leave
# KILO_CONFIG_CONTENT unset so Kilo falls back to ~/.config/kilo/kilo.jsonc,
# which is bind-mounted from the host and may point at an external provider.
if [ -n "${KILO_CONFIG_CONTENT_DOCKER:-}" ]; then
printf 'export KILO_CONFIG_CONTENT=%q\n' "${KILO_CONFIG_CONTENT_DOCKER}" >> /root/.bashrc
fi

# Seed the Kilo Code auth token into the container-private data volume.
# The volume is not shared with the host to avoid SQLite conflicts between
# the Remote-SSH and devcontainer Kilo Code instances. The API key is
# passed in via the KILO_API_KEY remoteEnv variable.
if [ -n "${KILO_API_KEY:-}" ]; then
mkdir -p /root/.local/share/kilo
printf '{\n "fnal-litellm": {\n "type": "api",\n "key": "%s"\n }\n}\n' \
"${KILO_API_KEY}" > /root/.local/share/kilo/auth.json
fi

# Expose repo scripts that follow the git-<subcommand> naming convention as
# proper git subcommands by symlinking them into /usr/local/bin. This makes
# `git ai-commit` work in any directory without requiring scripts/ on PATH.
ln -sf /workspaces/phlex/scripts/git-ai-commit /usr/local/bin/git-ai-commit

# Adjust MANPATH to enable `git help ai-commit` to find the man page.
# The scripts/man/man1 directory contains git-subcommand man pages.
# Prepend the repo man directory and ensure the resulting MANPATH always has
# an empty field (which tells man to search the system default paths). When
# there is no pre-existing MANPATH, or the existing one lacks an empty field,
# a trailing colon is appended to provide one; if an empty field already
# exists it is preserved as-is.
cat >> root/.bashrc <<'EOF'
Comment on lines +44 to +51

prepend-to-manpath() {
# Prepends one or more paths to MANPATH, preserving the position of any
# existing empty field (which tells man(1) where to insert the system
# default paths). Handles unset, empty, or degenerate MANPATH safely.
# If MANPATH has no empty field, a trailing : is added (system paths last).
local prefix
prefix=$(IFS=:; echo "$*")
# Unset or degenerate (only colons): start fresh, system defaults last.
if [[ -z ${MANPATH+set} || -z "${MANPATH//:}" ]]; then
export MANPATH="${prefix}:"
return
fi
case "$MANPATH" in
*::*|*:|:*)
# Already has an empty field somewhere; prepend and keep it in place.
# :foo -> newpath::foo (leading : becomes embedded :: after prepend)
# foo: -> newpath:foo: (trailing : stays trailing)
# f::b -> newpath:f::b (embedded :: stays embedded)
export MANPATH="${prefix}:${MANPATH}" ;;
*)
# No empty field: add trailing : so system paths are still searched.
export MANPATH="${prefix}:${MANPATH}:" ;;
esac
}

prepend-to-manpath "/workspaces/phlex/scripts/man"
EOF

# Install pre-commit hooks if available.
if command -v prek >/dev/null 2>&1; then
prek install || true
Expand Down
Loading
Loading