Merge remote-tracking branch 'origin/claude-estate-cleanup'
This commit is contained in:
+13
-12
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Executable
+87
@@ -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"
|
||||
Executable
+37
@@ -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/
|
||||
Reference in New Issue
Block a user