Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 13 additions & 5 deletions DreamcoderShell/.bashrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# shellcheck shell=bash disable=SC1090,SC1091
set -euo pipefail
# Dreamcoder interactive Bash. No errexit/nounset/pipefail here: in an
# interactive shell they close the terminal on the first failing command.
[[ -z "${TERM:-}" || "${TERM}" == "dumb" ]] && export TERM="xterm-256color"
export COLORTERM="${COLORTERM:-truecolor}"
[[ "${-}" != *i* ]] && return
Expand All @@ -16,6 +17,13 @@ BUN_COMPLETION="${HOME}/.bun/_bun"
[[ -f "${BUN_COMPLETION}" ]] && source "${BUN_COMPLETION}"
export BUN_INSTALL="${HOME}/.bun"
[[ -d "${BUN_INSTALL}/bin" ]] && export PATH="${BUN_INSTALL}/bin:${PATH}"
# gga reviews: Codex provider with the pinned model shim first (docs/configuration/gga.md).
_dc_gga_bin="${XDG_CONFIG_HOME:-${HOME}/.config}/gga/bin"
if [[ -d "${_dc_gga_bin}" ]]; then
export GGA_PROVIDER="codex"
[[ ":${PATH}:" == *":${_dc_gga_bin}:"* ]] || export PATH="${_dc_gga_bin}:${PATH}"
fi
unset _dc_gga_bin
SHELL_DIR="${XDG_CONFIG_HOME:-${HOME}/.config}/shell"
for group in core aliases functions; do
for file in "${SHELL_DIR}/${group}"/*.sh; do [[ -f "${file}" ]] && source "${file}"; done
Expand All @@ -27,7 +35,7 @@ if command -v starship >/dev/null; then
fi
command -v zoxide >/dev/null && eval "$(zoxide init bash)"
[[ -f "${HOME}/.cargo/env" ]] && source "${HOME}/.cargo/env"
unset PATH_DIRS dir group file BUN_COMPLETION SHELL_DIR
. "$HOME/.cargo/env"
export PATH="$HOME/.local/share/fnm:$PATH"
eval "$(fnm env --use-on-cd --shell bash)"
_dc_fnm_dir="${HOME}/.local/share/fnm"
[[ -d "${_dc_fnm_dir}" && ":${PATH}:" != *":${_dc_fnm_dir}:"* ]] && export PATH="${_dc_fnm_dir}:${PATH}"
command -v fnm >/dev/null && eval "$(fnm env --use-on-cd --shell bash)"
unset PATH_DIRS dir group file BUN_COMPLETION SHELL_DIR _dc_fnm_dir
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# gga reviews: Codex provider with the pinned model shim first on PATH.
# The shim only changes `codex exec` calls made by gga (docs/configuration/gga.md).
set -l _dc_config_home $HOME/.config
set -q XDG_CONFIG_HOME[1]; and test -n "$XDG_CONFIG_HOME"; and set _dc_config_home $XDG_CONFIG_HOME
set -l _dc_gga_bin $_dc_config_home/gga/bin
if test -d $_dc_gga_bin
set -gx GGA_PROVIDER codex
fish_add_path --global --move --path $_dc_gga_bin
end
11 changes: 10 additions & 1 deletion DreamcoderShell/.zshrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ BUN_INSTALL="${HOME}/.bun"
[[ -s "${BUN_INSTALL}/_bun" ]] && source "${BUN_INSTALL}/_bun"
[[ -d "${BUN_INSTALL}/bin" ]] && path=("${BUN_INSTALL}/bin" "${path[@]}")
export BUN_INSTALL
# gga reviews: Codex provider with the pinned model shim first (docs/configuration/gga.md).
_dc_gga_bin="${XDG_CONFIG_HOME:-${HOME}/.config}/gga/bin"
if [[ -d "${_dc_gga_bin}" ]]; then
export GGA_PROVIDER="codex"
path=("${_dc_gga_bin}" "${path[@]}")
fi
unset _dc_gga_bin

# ── Dreamcoder Theme Hooks ─────────────────────────────────────
# Source shell-level theme snippets based on current mode.
Expand All @@ -19,7 +26,9 @@ _dc_mode_cache="${XDG_CACHE_HOME:-${HOME}/.cache}/dreamcoder/cursor-cli.env"
_dc_mode="$([[ -r "${_dc_mode_cache}" ]] && sed -nE 's/^(export )?DREAMCODER_THEME_MODE="?(light|dark)"?$/\2/p' "${_dc_mode_cache}" | tail -n1)"
_dc_mode="${_dc_mode:-${DREAMCODER_THEME_MODE:-dark}}"
export DREAMCODER_THEME_MODE="${_dc_mode}"
_dc_theme_dir="${DREAMCODER_DOTS_DIR:-${HOME}/Documents/PROYECTOS/dreamcoder-dots}/themes/dreamcoder"
# Without DREAMCODER_DOTS_DIR, resolve the repo through the stowed ~/.zshrc link
# (<repo>/DreamcoderShell/.zshrc).
_dc_theme_dir="${DREAMCODER_DOTS_DIR:-${${:-${HOME}/.zshrc}:A:h:h}}/themes/dreamcoder"

[[ -f "${_dc_theme_dir}/ls-colors-dreamcoder-${_dc_mode}.sh" ]] && source "${_dc_theme_dir}/ls-colors-dreamcoder-${_dc_mode}.sh"
[[ -f "${_dc_theme_dir}/bat-dreamcoder-${_dc_mode}.sh" ]] && source "${_dc_theme_dir}/bat-dreamcoder-${_dc_mode}.sh"
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Configuration guides for individual components.
| [Multiplexer](configuration/multiplexer-config.md) | Tmux, Zellij |
| [ML4W Integration](configuration/ml4w.md) | Profile-driven keybindings, native dispatchers, file layout |
| [Theme System](configuration/theme-system.md) | Theme engine architecture, tokens, variants |
| [gga Model Pin](configuration/gga.md) | Gentleman Guardian Angel reviews pinned to Codex `gpt-6.1-sol` (medium) |

---

Expand Down
47 changes: 47 additions & 0 deletions docs/configuration/gga.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Gentleman Guardian Angel (gga) Model Pin

← Back to [docs/README.md](../README.md)

Every gga review, in every repository, runs on the OpenAI Codex provider with
`gpt-6.1-sol` at `medium` reasoning effort.

## What is pinned and where

| File | Role |
|------|------|
| `~/.config/gga/bin/codex` | Shim (source: `scripts/gga-codex-shim.sh`). Injects `-m <model> -c model_reasoning_effort=<effort>` into `codex exec` only when a `gga` process is an ancestor and no `-m`/`--model` is given. Everything else passes through to the real `codex`. |
| `~/.config/gga/pin.env` | `GGA_PIN_MODEL` and `GGA_PIN_EFFORT`. Created once; never overwritten. |
| `~/.config/gga/config` | Marked block at the end (`# >>> dreamcoder gga pin >>>`): `PROVIDER`/`GGA_PROVIDER="codex"` and the shim dir first on `PATH`. |
| `~/.config/environment.d/50-gga-pin.conf` | Same provider and `PATH` for desktop-launched programs (IDE git hooks). |
| Fish, Zsh, Bash startup | Export `GGA_PROVIDER=codex` and put the shim dir first on `PATH` when it exists. |

## Why a shim

gga runs `codex exec "<prompt>"` without model options, so it would use the model
that `~/.codex/config.toml` selects for interactive work. The shim pins the review
model without touching that file, and interactive `codex` stays unchanged.

`GGA_PROVIDER` is applied by gga after the global config and the project `.gga`,
so it beats a project's own `PROVIDER`.

## Change the model or effort

Edit `~/.config/gga/pin.env`:

```bash
GGA_PIN_MODEL="gpt-6.1-sol"
GGA_PIN_EFFORT="high"
```

The next review uses it; nothing needs re-installing.

## After `gentle-ai sync` or `gentle-ai install`

Both rewrite the whole `~/.config/gga/config` and drop the pin block. The shim,
`pin.env`, the shell exports and `environment.d` survive, so reviews keep the
pinned model; re-apply the config block with either command:

```bash
./scripts/dreamcoder repair # runs the installer when gga is on PATH
./scripts/install-gga-pin.sh # idempotent; --dry-run shows pending changes
```
12 changes: 9 additions & 3 deletions scripts/dreamcoder-lib.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail
# used by sourcing scripts (dreamcoder.sh, dreamcoder-maintenance.sh)
# Sourced by dreamcoder-maintenance.sh, which sets strict mode itself; a sourced
# library must not change its caller's shell options.
# Stow packages owned by install/repair; keep in sync with
# src/dreamcoder_theme/installer.py (installer_plan()["modules"]).
DREAMCODER_MODULES=(DreamcoderShell DreamcoderKitty DreamcoderGhostty DreamcoderFastfetch DreamcoderWarp DreamcoderBat DreamcoderSystemd)
DREAMCODER_TARGETS=("${CONFIG_HOME}/kitty" "${CONFIG_HOME}/ghostty" "${CONFIG_HOME}/fastfetch" "${CONFIG_HOME}/dreamcoder" "${CONFIG_HOME}/fish" "${CONFIG_HOME}/starship.toml" "${CONFIG_HOME}/bat" "${DATA_HOME}/warp-terminal/themes" "${HOME}/.zshrc" "${HOME}/.bashrc" "${HOME}/.inputrc" "${CONFIG_HOME}/systemd/user/dreamcoder-theme-auto.service" "${CONFIG_HOME}/systemd/user/dreamcoder-theme-auto.timer")
dreamcoder_control() { PYTHONPATH="${DREAMCODER_DOTS_DIR}/src${PYTHONPATH:+:${PYTHONPATH}}" python3 -m dreamcoder_theme.control "$@"; }
dreamcoder_json_get() { python3 -c 'import json,sys; print(json.load(sys.stdin)[sys.argv[1]])' "$1"; }
dreamcoder_backup() { dreamcoder_control backup create "${DREAMCODER_TARGETS[@]}" --reason "${1}" --json; }
dreamcoder_apply_hooks() { "${DREAMCODER_DOTS_DIR}/scripts/apply-ml4w-hooks.sh"; "${DREAMCODER_DOTS_DIR}/scripts/apply-cli-env-hooks.sh"; "${DREAMCODER_DOTS_DIR}/scripts/apply-fastfetch-assets.sh"; }
dreamcoder_apply_hooks() {
"${DREAMCODER_DOTS_DIR}/scripts/apply-ml4w-hooks.sh"
"${DREAMCODER_DOTS_DIR}/scripts/apply-cli-env-hooks.sh"
"${DREAMCODER_DOTS_DIR}/scripts/apply-fastfetch-assets.sh"
# Re-apply the gga model pin that `gentle-ai sync` drops from ~/.config/gga/config.
if command -v gga >/dev/null; then "${DREAMCODER_DOTS_DIR}/scripts/install-gga-pin.sh"; fi
}
dreamcoder_enable_timer() { command -v systemctl >/dev/null || return 0; systemctl --user daemon-reload || true; systemctl --user enable --now dreamcoder-theme-auto.timer || true; }

# Print every target (relative to $HOME) that blocks stowing the modules,
Expand Down
91 changes: 91 additions & 0 deletions scripts/gga-codex-shim.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env bash
# GGA-only model pin for the Codex CLI, installed as ~/.config/gga/bin/codex.
#
# Gentleman Guardian Angel (gga) runs `codex exec "<prompt>"` with no model
# options, so reviews would follow whatever ~/.codex/config.toml selects for
# interactive work. This shim sits first on PATH and, only for `codex exec`
# calls made by a gga process, injects the pinned model and reasoning effort.
# Every other invocation (interactive codex, other tools) passes through
# untouched, and an explicit -m/--model always wins.
#
# The pin lives in ~/.config/gga/pin.env (GGA_PIN_MODEL, GGA_PIN_EFFORT) and
# never in ~/.config/gga/config, which `gentle-ai sync` rewrites wholesale.
# Installed by scripts/install-gga-pin.sh; see docs/configuration/gga.md.
set -euo pipefail

readonly DEFAULT_MODEL="gpt-6.1-sol"
readonly DEFAULT_EFFORT="medium"
readonly MAX_ANCESTORS=10
# Linux procfs; without it the shim never pins and passes every call through.
readonly PROC_ROOT="${GGA_SHIM_PROC_ROOT:-/proc}"

self_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"

find_real_codex() {
local candidate
while IFS= read -r candidate; do
[[ "$(cd "$(dirname "${candidate}")" && pwd -P)" == "${self_dir}" ]] && continue
printf '%s\n' "${candidate}"
return 0
done < <(type -ap codex)
return 1
}

# True when one of the first MAX_ANCESTORS ancestors is a gga process: its
# comm is `gga` (the kernel names a script after its file) or it runs an
# interpreter whose script argument is named gga.
has_gga_ancestor() {
local pid="${PPID}" depth comm ppid arg
local -a argv
[[ -d "${PROC_ROOT}/self" ]] || return 1
for ((depth = 0; depth < MAX_ANCESTORS; depth++)); do
[[ "${pid}" =~ ^[0-9]+$ && "${pid}" -gt 1 && -r "${PROC_ROOT}/${pid}/status" ]] || return 1
comm="$(cat "${PROC_ROOT}/${pid}/comm" 2>/dev/null)" || return 1
[[ "${comm}" == "gga" ]] && return 0
argv=()
while IFS= read -r -d '' arg; do argv+=("${arg}"); done <"${PROC_ROOT}/${pid}/cmdline" 2>/dev/null || true
for arg in "${argv[@]:0:2}"; do
[[ "${arg##*/}" == "gga" ]] && return 0
done
ppid="$(sed -n 's/^PPid:[[:space:]]*//p' "${PROC_ROOT}/${pid}/status" 2>/dev/null)" || return 1
pid="${ppid}"
done
return 1
}

has_explicit_model() {
local arg
for arg in "$@"; do
case "${arg}" in -m | --model | --model=* | -m?*) return 0 ;; esac
done
return 1
}

# Read GGA_PIN_MODEL / GGA_PIN_EFFORT from pin.env without executing it.
read_pin() {
local key="$1" fallback="$2" file line value=""
file="${XDG_CONFIG_HOME:-${HOME}/.config}/gga/pin.env"
if [[ -r "${file}" ]]; then
while IFS= read -r line || [[ -n "${line}" ]]; do
line="${line#export }"
[[ "${line}" == "${key}="* ]] || continue
value="${line#"${key}"=}"
value="${value#[\"\']}"
value="${value%[\"\']}"
done <"${file}"
fi
printf '%s\n' "${value:-${fallback}}"
}

real="$(find_real_codex)" || {
echo "gga codex shim: the real codex CLI was not found on PATH" >&2
exit 127
}

if [[ "${1:-}" == "exec" ]] && ! has_explicit_model "$@" && has_gga_ancestor; then
shift
model="$(read_pin GGA_PIN_MODEL "${DEFAULT_MODEL}")"
effort="$(read_pin GGA_PIN_EFFORT "${DEFAULT_EFFORT}")"
exec "${real}" exec -m "${model}" -c "model_reasoning_effort=${effort}" "$@"
fi
exec "${real}" "$@"
147 changes: 147 additions & 0 deletions scripts/install-gga-pin.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env bash
# Pin Gentleman Guardian Angel (gga) reviews to the Codex provider and model.
#
# `gentle-ai sync` / `gentle-ai install` rewrite the whole gga config, so the pin
# is kept outside it where possible and re-applied here idempotently:
# 1. <gga>/bin/codex shim that injects the model for gga's `codex exec`
# 2. <gga>/pin.env model and effort (created once, never overwritten)
# 3. <gga>/config marked block at the end: PROVIDER/GGA_PROVIDER=codex
# and the shim dir first on PATH
# 4. environment.d/50-gga-pin.conf the same for desktop-launched programs
# One line is printed per changed file; nothing when everything is current.
#
# Usage: install-gga-pin.sh [--dry-run]
set -euo pipefail

readonly BLOCK_BEGIN="# >>> dreamcoder gga pin >>>"
readonly BLOCK_END="# <<< dreamcoder gga pin <<<"

dry_run=0
case "${1:-}" in
"") ;;
--dry-run) dry_run=1 ;;
-h | --help)
sed -n '2,13p' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
*)
printf 'install-gga-pin: unknown option: %s\n' "$1" >&2
exit 2
;;
esac

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
config_home="${XDG_CONFIG_HOME:-${HOME}/.config}"
gga_dir="${config_home}/gga"
shim_dir="${gga_dir}/bin"
shim_src="${script_dir}/gga-codex-shim.sh"
shim_dst="${shim_dir}/codex"
pin_file="${gga_dir}/pin.env"
config_file="${gga_dir}/config"
env_file="${config_home}/environment.d/50-gga-pin.conf"

# The shim dir is written unescaped into shell code (gga config) and into
# environment.d, so only plain path characters are accepted.
if [[ ! "${shim_dir}" =~ ^/[A-Za-z0-9._@+/-]+$ ]]; then
printf 'install-gga-pin: unsupported characters in %s; set a plain HOME/XDG_CONFIG_HOME\n' "${shim_dir}" >&2
exit 1
fi

[[ -r "${shim_src}" ]] || {
printf 'install-gga-pin: shim source not found: %s\n' "${shim_src}" >&2
exit 1
}

# Write stdin to $1 when its content differs; report what changed.
write_if_changed() {
local target="$1" label="$2" tmp
tmp="$(mktemp)"
cat >"${tmp}"
if [[ -f "${target}" ]] && cmp -s "${tmp}" "${target}"; then
rm -f "${tmp}"
return 0
fi
if ((dry_run)); then
printf '→ Would update %s (%s)\n' "${target}" "${label}"
else
mkdir -p "$(dirname "${target}")"
# cat (not mv) keeps an existing file's inode, mode and symlink target.
cat "${tmp}" >"${target}"
printf '✓ %s: %s\n' "${label}" "${target}"
fi
rm -f "${tmp}"
}

install_shim() {
write_if_changed "${shim_dst}" "gga codex shim" <"${shim_src}"
if ((!dry_run)) && [[ -f "${shim_dst}" && ! -x "${shim_dst}" ]]; then
chmod 0755 "${shim_dst}"
fi
}

install_pin_env() {
[[ -e "${pin_file}" ]] && return 0
write_if_changed "${pin_file}" "gga pin model/effort" <<'EOF'
# Model used for every gga review; read by ~/.config/gga/bin/codex.
# Edit freely: scripts/install-gga-pin.sh never overwrites this file.
GGA_PIN_MODEL="gpt-6.1-sol"
GGA_PIN_EFFORT="medium"
EOF
}

pin_block() {
cat <<EOF
${BLOCK_BEGIN}
# Managed by dreamcoder-dots scripts/install-gga-pin.sh (docs/configuration/gga.md).
# gentle-ai rewrites this file; re-run the installer or \`dreamcoder repair\` after it.
PROVIDER="codex"
GGA_PROVIDER="codex"
case ":\${PATH}:" in *":${shim_dir}:"*) ;; *) export PATH="${shim_dir}:\${PATH}" ;; esac
${BLOCK_END}
EOF
}

# Print the config without the pin block (and the blank line placed before
# it), then the block itself at the end.
render_config() {
local rest=""
if [[ -f "${config_file}" ]]; then
if grep -qxF "${BLOCK_BEGIN}" "${config_file}" && ! grep -qxF "${BLOCK_END}" "${config_file}"; then
printf 'install-gga-pin: %s has an unterminated pin block; fix it by hand\n' "${config_file}" >&2
return 1
fi
rest="$(awk -v begin="${BLOCK_BEGIN}" -v end="${BLOCK_END}" '
$0 == begin { skip = 1; held = 0; next }
skip { if ($0 == end) skip = 0; next }
{
if (held) print ""
held = 0
if ($0 == "") held = 1; else print
}
END { if (held) print "" }
' "${config_file}")"
fi
if [[ -n "${rest}" ]]; then
printf '%s\n\n' "${rest}"
fi
pin_block
}

install_config_block() {
local rendered
rendered="$(render_config)"
printf '%s\n' "${rendered}" | write_if_changed "${config_file}" "gga config pin block"
}

install_environment_d() {
write_if_changed "${env_file}" "gga pin for desktop sessions" <<EOF
# Managed by dreamcoder-dots scripts/install-gga-pin.sh (docs/configuration/gga.md).
GGA_PROVIDER=codex
PATH=${shim_dir}:\${PATH}
EOF
}

install_shim
install_pin_env
install_config_block
install_environment_d
Loading
Loading