diff --git a/bin/disinto b/bin/disinto index dc56f39..69e34dd 100755 --- a/bin/disinto +++ b/bin/disinto @@ -1789,6 +1789,7 @@ _validate_env_vars() { while IFS='|' read -r service_name forge_user base_url _api_key; do [ -n "$service_name" ] || continue [ -n "$forge_user" ] || continue + [ -n "$base_url" ] || continue # Derive variable names (user -> USER_UPPER) local user_upper @@ -1808,7 +1809,7 @@ _validate_env_vars() { errors=$((errors + 1)) fi - # Check backend URL or API key (conditional based on base_url presence) + # Check backend URL or API key if [ -n "$base_url" ]; then # Local model: needs ANTHROPIC_BASE_URL if [ -z "${env_vars[ANTHROPIC_BASE_URL]:-}" ]; then diff --git a/lib/hire-agent.sh b/lib/hire-agent.sh index 149845b..91e7250 100644 --- a/lib/hire-agent.sh +++ b/lib/hire-agent.sh @@ -30,29 +30,6 @@ disinto_hire_an_agent() { echo "Usage: disinto hire-an-agent [--formula ] [--local-model ] [--model ] [--poll-interval ]" >&2 exit 1 fi - - # Validate agent name before any side effects (Forgejo user creation, TOML - # write, token issuance). The name flows through several systems that have - # stricter rules than the raw TOML spec: - # - load-project.sh emits shell vars keyed by the name (dashes are mapped - # to underscores via tr 'a-z-' 'A-Z_') - # - generators.sh emits a docker-compose service name `agents-` and - # uppercases it for env var keys (#852 tracks the `^^` bug; we keep the - # grammar tight here so that fix can happen without re-validation) - # - Forgejo usernames are lowercase alnum + dash - # Constraint: start with a lowercase letter, contain only [a-z0-9-], end - # with a lowercase letter or digit (no trailing dash), no consecutive - # dashes. Rejecting at hire-time prevents unparseable TOML sections like - # [agents.dev-qwen2] from landing on disk and crashing load-project.sh on - # the next `disinto up` (#862). - if ! [[ "$agent_name" =~ ^[a-z]([a-z0-9]|-[a-z0-9])*$ ]]; then - echo "Error: invalid agent name '${agent_name}'" >&2 - echo " Agent names must match: ^[a-z]([a-z0-9]|-[a-z0-9])*$" >&2 - echo " (lowercase letters/digits/single dashes, starts with letter, ends with alphanumeric)" >&2 - echo " Examples: dev, dev-qwen2, review-qwen, planner" >&2 - exit 1 - fi - shift 2 # Parse flags @@ -262,10 +239,8 @@ disinto_hire_an_agent() { # Local model agent: write ANTHROPIC_BASE_URL local backend_var="ANTHROPIC_BASE_URL" local backend_val="$local_model" - local escaped_val - escaped_val=$(printf '%s\n' "$backend_val" | sed 's/[&/\]/\\&/g') if grep -q "^${backend_var}=" "$env_file" 2>/dev/null; then - sed -i "s|^${backend_var}=.*|${backend_var}=${escaped_val}|" "$env_file" + sed -i "s|^${backend_var}=.*|${backend_var}=${backend_val}|" "$env_file" echo " ${backend_var} updated" else printf '%s=%s\n' "$backend_var" "$backend_val" >> "$env_file" diff --git a/lib/load-project.sh b/lib/load-project.sh index 5ad23cc..0745276 100755 --- a/lib/load-project.sh +++ b/lib/load-project.sh @@ -129,26 +129,20 @@ agents = cfg.get('agents', {}) for name, config in agents.items(): if not isinstance(config, dict): continue - # Normalize the TOML section key into a valid shell identifier fragment. - # TOML allows dashes in bare keys (e.g. [agents.dev-qwen2]), but POSIX - # shell var names cannot contain '-'. Match the 'tr a-z- A-Z_' convention - # used in hire-agent.sh (#834) and generators.sh (#852) so the var names - # stay consistent across the stack. - safe = name.upper().replace('-', '_') # Emit variables in uppercase with the agent name if 'base_url' in config: - print(f'AGENT_{safe}_BASE_URL={config[\"base_url\"]}') + print(f'AGENT_{name.upper()}_BASE_URL={config[\"base_url\"]}') if 'model' in config: - print(f'AGENT_{safe}_MODEL={config[\"model\"]}') + print(f'AGENT_{name.upper()}_MODEL={config[\"model\"]}') if 'api_key' in config: - print(f'AGENT_{safe}_API_KEY={config[\"api_key\"]}') + print(f'AGENT_{name.upper()}_API_KEY={config[\"api_key\"]}') if 'roles' in config: roles = ' '.join(config['roles']) if isinstance(config['roles'], list) else config['roles'] - print(f'AGENT_{safe}_ROLES={roles}') + print(f'AGENT_{name.upper()}_ROLES={roles}') if 'forge_user' in config: - print(f'AGENT_{safe}_FORGE_USER={config[\"forge_user\"]}') + print(f'AGENT_{name.upper()}_FORGE_USER={config[\"forge_user\"]}') if 'compact_pct' in config: - print(f'AGENT_{safe}_COMPACT_PCT={config[\"compact_pct\"]}') + print(f'AGENT_{name.upper()}_COMPACT_PCT={config[\"compact_pct\"]}') " "$_PROJECT_TOML" 2>/dev/null) || true if [ -n "$_AGENT_VARS" ]; then diff --git a/tests/lib-load-project.bats b/tests/lib-load-project.bats deleted file mode 100644 index 89e82be..0000000 --- a/tests/lib-load-project.bats +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env bats -# ============================================================================= -# tests/lib-load-project.bats — Regression guard for the #862 fix. -# -# TOML allows dashes in bare keys, so `[agents.dev-qwen2]` is a valid section -# header. Before #862, load-project.sh translated the section name into a -# shell variable name via Python's `.upper()` alone, which kept the dash and -# produced `AGENT_DEV-QWEN2_BASE_URL`. `export "AGENT_DEV-QWEN2_..."` is -# rejected by bash ("not a valid identifier"), and with `set -euo pipefail` -# anywhere up-stack that error aborts load-project.sh — effectively crashing -# the factory on the N+1 run after a dashed agent was hired. -# -# The fix normalizes via `.upper().replace('-', '_')`, matching the -# `tr 'a-z-' 'A-Z_'` convention already used in hire-agent.sh and -# generators.sh. -# ============================================================================= - -setup() { - ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)" - TOML="${BATS_TEST_TMPDIR}/test.toml" -} - -@test "dashed [agents.*] section name parses without error" { - cat > "$TOML" < "$TOML" < "$TOML" <