Merge remote-tracking branch 'origin/claude-estate-cleanup'

This commit is contained in:
2026-07-13 22:47:04 +02:00
87 changed files with 960 additions and 3714 deletions
+13 -12
View File
@@ -75,24 +75,25 @@ def get_base_url(key: str, default: str) -> str:
return load_config().get(key, default)
def get_api_key(env_var: str, config_key: str) -> str:
"""Get an API key from the environment or config.json.
def get_api_key(env_var: str, config_key: str = "") -> str:
"""Get an API key from the environment — environment-only, by design.
Checks the ``env_var`` environment variable first, then ``config_key`` in
config.json. Prints a JSON error and exits 1 if neither is set — connector
scripts emit machine-readable JSON on all paths.
``tooling/db/config.json`` is a *tracked* file and holds endpoints only;
it must never carry secrets, so there is deliberately no config.json
fallback here (the old one steered users toward committing paid API keys).
``config_key`` is retained in the signature for caller compatibility but
is ignored. Prints a JSON error and exits 1 if the variable is unset —
connector scripts emit machine-readable JSON on all paths.
"""
key = os.environ.get(env_var)
if key:
return key
try:
with open(CONFIG_PATH) as f:
config = json.load(f)
return config.get(config_key, "")
except Exception:
pass
print(json.dumps({
"ok": False,
"error": f"No {env_var} found in environment or config.json"
"error": (
f"{env_var} not set. Export it in your shell or add it to the "
"machine-local .claude/settings.local.json env block (untracked). "
"Never put keys in tooling/db/config.json — it is tracked."
)
}, indent=2))
sys.exit(1)
+15 -10
View File
@@ -2,7 +2,7 @@
"""
Gemini image generator connector — direct API wrapper.
Generates images via Google's Gemini 2.0 Flash image generation API.
Generates images via Google's gemini-2.5-flash-image generation API.
API key from GEMINI_API_KEY env var or config.json.
Usage:
@@ -87,13 +87,13 @@ def generate(prompt, output=None, aspect_ratio="1:1", image_size=None,
# Add input image if provided (image-to-image)
if input_image:
if not os.path.isfile(input_image):
print(json.dumps({"ok": False, "error": f"Input image not found: {input_image}"}), indent=2)
print(json.dumps({"ok": False, "error": f"Input image not found: {input_image}"}, indent=2))
sys.exit(1)
with open(input_image, "rb") as f:
image_data = base64.b64encode(f.read()).decode("utf-8")
# Detect mime type
ext = os.path.splitext(input_image)[1].lower()
mime = {"png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
mime = {".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
".webp": "image/webp"}.get(ext, "image/png")
parts.append({
"inlineData": {
@@ -102,20 +102,25 @@ def generate(prompt, output=None, aspect_ratio="1:1", image_size=None,
}
})
# Build enhanced prompt with aspect ratio and size hints
# Build enhanced prompt with a size hint. Unlike aspect ratio below, Gemini
# has no dedicated resolution parameter for this model — this is a
# best-effort prompt hint only and may not be honored.
enhanced_prompt = prompt
if aspect_ratio and aspect_ratio != "1:1":
enhanced_prompt += f" Aspect ratio: {aspect_ratio}."
if image_size:
enhanced_prompt += f" Resolution: {image_size}."
parts.append({"text": enhanced_prompt})
generation_config = {"responseModalities": ["TEXT", "IMAGE"]}
if aspect_ratio:
# Real API parameter (not a prompt hint). Valid values: 1:1, 3:2,
# 2:3, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9.
# https://ai.google.dev/gemini-api/docs/image-generation
generation_config["imageConfig"] = {"aspectRatio": aspect_ratio}
payload = json.dumps({
"contents": [{"parts": parts}],
"generationConfig": {
"responseModalities": ["TEXT", "IMAGE"],
}
"generationConfig": generation_config
})
req = urllib.request.Request(
@@ -142,7 +147,7 @@ def generate(prompt, output=None, aspect_ratio="1:1", image_size=None,
}, indent=2))
sys.exit(1)
except Exception as e:
print(json.dumps({"ok": False, "error": str(e)}), indent=2)
print(json.dumps({"ok": False, "error": str(e)}, indent=2))
sys.exit(1)
# Extract image data from response
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env bash
# tooling/godot-cold-parse [--run-menu] — cold-cache headless parse check.
#
# Used by /pr-process step 1c before push. Deletes the cached script-class
# registry so the parse simulates the cold-start ordering CI / fresh clones
# see: Sprint 36 close caught a new `class_name MetaScreen` base class and
# six extending scripts that parsed fine on warm developer caches but hit
# `Could not find base class "MetaScreen"` post-merge, because the
# autoload-vs-class_name registration order only resolves correctly once the
# class cache is seeded (see CLAUDE.md -> GDScript conventions -> Autoload
# parse-order rule).
#
# Godot's resource scanner emits category errors (e.g. "Export type can only
# be built-in, a resource, a node, or an enum") that do NOT always prefix
# with SCRIPT ERROR — they appear as plain ERROR lines. The filter below
# catches both, then drops known pre-existing noise from the autoload
# class_name parse-order trap. Sprint 36 shipped a scanner error the old
# narrower grep missed; this is why the filter stays wide.
#
# --run-menu: also launch main_menu.tscn briefly (for branches with UI changes).
#
# Exit 0 + "clean" if no matches. Exit 1 + the matched lines if any are found.
set -euo pipefail
REPO_ROOT="$(git rev-parse --show-toplevel)"
RUN_MENU=false
[ "${1:-}" = "--run-menu" ] && RUN_MENU=true
rm -f "$REPO_ROOT/client/.godot/global_script_class_cache.cfg"
# A truly cold checkout (fresh clone or worktree — .godot/ is gitignored) has
# no resource-import cache, and every imported asset (fonts, ogg) then "fails
# loading" during the parse run: a wall of false positives. Seed the cache
# with an import pass first; source assets are tracked, so this is always
# reconstructible. (Found live: first run in a fresh worktree, 2026-07-13.)
if [ ! -d "$REPO_ROOT/client/.godot/imported" ] || [ -z "$(ls -A "$REPO_ROOT/client/.godot/imported" 2>/dev/null)" ]; then
echo "godot-cold-parse: no import cache — running one-time import pass..." >&2
set +e
IMPORT_OUT=$(godot --headless --path "$REPO_ROOT/client" --import 2>&1)
IMPORT_EXIT=$?
set -e
if [ "$IMPORT_EXIT" -ne 0 ]; then
echo "godot-cold-parse: import pass exited $IMPORT_EXIT" >&2
printf '%s\n' "$IMPORT_OUT" | tail -20 >&2
exit "$IMPORT_EXIT"
fi
fi
FILTER='^(SCRIPT )?ERROR|Parse Error|Export type'
# Capture the godot run separately from the filter pipeline: with the
# trailing `|| true` on the greps, a nonzero exit from godot itself (crash,
# missing binary, corrupted install) would otherwise report "clean". Nothing
# downstream re-reads the raw output now that this is scripted, so fail loud.
set +e
RAW=$(godot --headless --path "$REPO_ROOT/client" --quit 2>&1)
GODOT_EXIT=$?
set -e
if [ "$GODOT_EXIT" -ne 0 ]; then
echo "godot-cold-parse: godot itself exited $GODOT_EXIT — not a parse verdict" >&2
printf '%s\n' "$RAW" | tail -20 >&2
exit "$GODOT_EXIT"
fi
MATCHES=$(printf '%s\n' "$RAW" \
| grep -iE "$FILTER" \
| grep -v "Failed loading resource: res://assets" \
| grep -v "Cannot infer the type" \
| grep -vE '(Messagepack|LocalBridge|ServerProcess|Constants)" not declared' || true)
if [ "$RUN_MENU" = true ]; then
# Deliberately no exit-code check here: `timeout` kills the menu after
# 10s by design (exit 124 is the expected shutdown path); only the
# scraped error lines carry signal for this bounded run.
MENU_MATCHES=$(timeout 10 godot --path "$REPO_ROOT/client" res://scenes/main_menu.tscn 2>&1 \
| grep -iE "$FILTER" || true)
if [ -n "$MENU_MATCHES" ]; then
MATCHES="$MATCHES
$MENU_MATCHES"
fi
fi
if [ -n "$MATCHES" ]; then
echo "$MATCHES"
exit 1
fi
echo "godot-cold-parse: clean"
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# tooling/pr-watchlist-diff <base> <head> — list watch-list files changed
# between <base> and <head>.
#
# Used by /pr-process step 4a (T-858) to decide whether `make regen-db` must
# run before push. The stamped generator sources come from the shared
# registry tooling/generator_sources.py (T-1067) — imported live here so this
# list can't drift from the stamp writer / tooling/check-systems-db-stamp.
#
# The extra hardcoded paths below are non-stamped watch items: the surviving
# one-time planet-gen importers (import_heightmaps.py, import_province_
# boundaries.py — not part of `make regen-db`, but their data feeds the
# committed DB), the schema DDL (stamped separately via schema_sha), and the
# wiki data directories that feed the generators.
set -euo pipefail
BASE="${1:?usage: tooling/pr-watchlist-diff <base> <head>}"
HEAD="${2:?usage: tooling/pr-watchlist-diff <base> <head>}"
# Load the registry via plain assignment (set -e sees its failure), not
# `mapfile < <(...)` — a failed process substitution is invisible to set -e
# and would silently yield an empty watch list, disabling the DB-staleness
# net exactly when the shared registry breaks. Guard the empty case too.
SOURCES_RAW="$(python3 tooling/generator_sources.py --list)"
if [ -z "$SOURCES_RAW" ]; then
echo "pr-watchlist-diff: generator_sources.py --list returned nothing" >&2
exit 1
fi
mapfile -t GENERATOR_SOURCES <<< "$SOURCES_RAW"
git diff --name-only "$BASE...$HEAD" -- \
"${GENERATOR_SOURCES[@]}" \
tooling/planet-gen/import_heightmaps.py \
tooling/planet-gen/import_province_boundaries.py \
server/data/systems-schema.sql \
wiki/star-systems/ \
wiki/economics/