Files
35cad6b48f feat(analyzer): add the official Analyzer system prompt (#16) (#60)
Ship the official Analyzer system prompt and make it loadable/overridable, per
MASTER_SPEC §13.

- prompts/analyzer_system_prompt.md: the official prompt assembled from §13.1-
  §13.12 (deterministic semantic parser; extract explicit concepts only; never
  generate tags or invent information; ignore subjective/artistic language;
  explicit camera/lighting/environment only; independent characters with
  scene-level interactions; preserve unknown concepts; return valid JSON only,
  no markdown) plus the §12.2 Scene JSON output shape and example.
- compiler/analyzer/system_prompt.py: load_default_system_prompt() reads the
  shipped default; resolve_system_prompt(config) returns config.analyzer.
  system_prompt when set, otherwise the default.
- Add optional analyzer.system_prompt to the config model and schema (nullable).

Tests: default file loads, a parametrized drift guard asserting every required
§13 directive is present, output-shape keys present, config override and default
resolution, and config round-trip of the override.

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 13:51:21 +02:00

64 lines
2.2 KiB
Python

"""Tests for the official Analyzer system prompt (issue #16, MASTER_SPEC §13)."""
from __future__ import annotations
import pytest
from compiler.analyzer.system_prompt import (
DEFAULT_SYSTEM_PROMPT_PATH,
load_default_system_prompt,
resolve_system_prompt,
)
from compiler.common.config import Config
def test_default_prompt_file_exists_and_loads() -> None:
assert DEFAULT_SYSTEM_PROMPT_PATH.is_file()
assert load_default_system_prompt().strip()
# Drift guard: required normative directives from §13 must remain present.
REQUIRED_PHRASES = [
"Scene Analyzer", # §13.1 core identity
"deterministic semantic parser", # §13.1
"Extract explicit concepts", # §13.2 primary objective
"Never generate Illustrious or Danbooru tags", # §13.3 forbidden
"Extract only explicitly described information", # §13.4
"implicit information MUST be ignored", # §13.5
"Ignore subjective adjectives", # §13.6
"masterpiece", # §13.6 artistic language example
"camera", # §13.7
"lighting", # §13.7
"environment", # §13.8
"independent Character object", # §13.9
"Interactions belong to the scene", # §13.9
"preserve the original wording", # §13.10
"no markdown", # §13.11 JSON only
"valid JSON", # §13.11
]
@pytest.mark.parametrize("phrase", REQUIRED_PHRASES)
def test_prompt_contains_required_directive(phrase: str) -> None:
assert phrase in load_default_system_prompt(), f"missing directive: {phrase!r}"
def test_prompt_includes_output_shape_keys() -> None:
prompt = load_default_system_prompt()
for key in ("characters", "interactions", "objects", "environment", "camera", "lighting"):
assert key in prompt
def test_resolve_uses_default_when_unset() -> None:
assert resolve_system_prompt(Config()) == load_default_system_prompt()
def test_resolve_uses_config_override() -> None:
config = Config.from_json({"analyzer": {"system_prompt": "CUSTOM PROMPT"}})
assert resolve_system_prompt(config) == "CUSTOM PROMPT"
def test_config_roundtrips_system_prompt() -> None:
config = Config.from_json({"analyzer": {"system_prompt": "X"}})
assert config.to_json()["analyzer"]["system_prompt"] == "X"