Commit Graph
15 Commits
Author SHA1 Message Date
c777bebf00 feat(analyzer): add Analyzer retry orchestration (#18) (#62)
Add compiler/analyzer/scene_analyzer.py: analyze(description, backend, config,
logger=None) -> CompilerResult, per MASTER_SPEC §12.9, §12.10.

- Composes the prompt once (resolved system prompt + user description) and reuses
  it unchanged on every attempt; JSON is never repaired heuristically.
- Bounded retries: total attempts = max_retries + 1 (1 initial + up to N
  retries, default 3). A bad response (parse/schema failure SC0011, or an
  unexpected backend response SC0018) is retried; the last such error is returned
  once attempts are exhausted.
- Terminal failures return immediately: connection failure -> Fatal SC0012;
  timeout -> SC0013 (no retry).
- Attaches telemetry to the result metadata (model, attempts, retries, duration,
  validation status) and logs the outcome.

Tests (mocked backend): first-attempt success, succeed-on-retry, prompt reused
unchanged, prompt includes system prompt + description, exhaust-retries -> SC0011
with the right call count, zero-retries single attempt, connection -> SC0012 (no
retry), timeout -> SC0013 (no retry), and telemetry.

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 13:59:17 +02:00
5948b20741 feat(analyzer): parse and validate Analyzer responses (#17) (#61)
Add compiler/analyzer/response_parser.py: parse_scene_response(text, logger=None)
-> CompilerResult, per MASTER_SPEC §12.2, §12.8.

- Strictly json.loads the model response; JSON is never repaired heuristically
  (§12.9), so malformed or markdown-fenced output fails here (feeding the retry
  path in #18).
- Validates the parsed document against the Scene schema; any failure (malformed
  JSON, non-object, missing field, wrong type) is reported as SC0011 and stops
  the compilation -- invalid responses are never silently accepted.
- Handles both object and string concept forms via the Scene model; confidence
  stays metadata-only.
- Stamps a default schema_version onto compiler-owned metadata (the model emits
  `"metadata": {}`) before validation.

Tests: valid response -> Scene, metadata stamping, object/string concepts +
confidence, and rejection (SC0011) of malformed JSON, non-object JSON, missing
fields, wrong types, and markdown-fenced (unrepaired) output.

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 13:54:39 +02:00
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
c1e6afeb41 Implement the Ollama Analyzer backend (#15) (#59)
* feat(analyzer): add the Ollama Analyzer backend (#15)

Add compiler/analyzer/backend.py per MASTER_SPEC §12.3, §12.10.

- AnalyzerBackend Protocol keeps the LLM backend swappable with no downstream
  coupling; the module imports no downstream compiler stage.
- OllamaBackend talks to a local Ollama /api/generate endpoint using only the
  standard library (urllib). The HTTP call is an injectable transport, so the
  backend is fully unit-testable without a live Ollama.
- Sends the configured model, temperature (default 0.0), and the user prompt
  unmodified, applying the configured timeout. BackendResult exposes the response
  text, model name, and request duration for logging.
- Failure modes map to codes: connection failure -> BackendUnavailableError
  (SC0012, Fatal); timeout -> BackendTimeoutError (SC0013); HTTP error /
  unexpected response -> BackendResponseError (SC0018, new extension).

Adds SC0018 (Analyzer Unexpected Response) to the message-code registry.

Tests (no live Ollama): interface conformance, from_config, success with
telemetry + unmodified prompt, and timeout/connection/URLError/HTTP/malformed
response mapping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(analyzer): use pytest.approx and single throwing call in backend tests

Avoid float-equality on temperature (SonarCloud reliability) and construct the
backend outside pytest.raises so each block has a single throwing invocation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 20:10:57 +02:00
30312136f9 feat(builder): implement the Prompt Builder (#14) (#58)
Add compiler/builder/prompt_builder.py: build_prompts(category_map, config,
logger=None) -> CompilerResult (data = tuple[PromptOutput]), per MASTER_SPEC §19.

- Emits one output per canonical category (in order) plus reserved `negative`
  and `scene`; V1 always emits all 21 outputs for stable node sockets.
- Tags joined with config.prompt_builder.separator (default comma, no surrounding
  whitespace, no trailing separator). Empty categories yield an empty string,
  never null/placeholder (§19.4).
- Formatting only: no tag generation, removal, reordering, dedup, or escaping
  (§19.1, §19.5). Output names are the lowercase canonical category ids and
  `negative`/`scene`, mapping to the §19.2/§19.3 display names.

Tests: output set (19 + 2 reserved), comma join, order preservation, empty
strings, reserved-empty, custom separator, no-trailing-separator, no escaping,
determinism.

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 20:04:08 +02:00
499a2490ca feat(splitter): implement the Category Splitter (#13) (#57)
Add compiler/splitter/category_splitter.py: split_into_categories(tags,
logger=None) -> CompilerResult (data = CategoryMap), per MASTER_SPEC §18.

- Each Resolved Tag is placed in the category recorded on it by the Resolver
  (its Knowledge Base Entry category); tags are never modified, generated, or
  removed (original objects kept verbatim).
- Tags within a category preserve Resolver order; category ordering follows the
  shared canonical 19-category order; empty categories are omitted (§18.4).
- Defensive: a tag whose category is outside the 19 stops with SC0008.

Tests: assignment, within-category ordering, canonical category ordering,
empty-category omission, tag identity preserved, empty input, and SC0008.

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 20:00:54 +02:00
76d440fbaf feat(resolver): implement the deterministic Illustrious Resolver (#12) (#56)
Add compiler/resolver/illustrious_resolver.py: resolve_scene(scene,
knowledge_base, config, logger=None) -> CompilerResult, per MASTER_SPEC §17. No
LLM; no concept or tag is invented.

- normalize_concept (§17.3): NFKC + lowercase, unifying underscores/spaces so
  natural concepts, snake_case ids, and spaced aliases share one lookup key. The
  resolver builds its own normalized index over the Knowledge Base.
- Per-concept pipeline in scene discovery order (§17.8): normalize -> direct
  alias resolution (no chains; skipped when allow_aliases is false) -> canonical
  lookup -> recursive expansion -> tag generation.
- Expansion (§17.5): own tags first, then depth-first in list order per expand
  target, inserted immediately after; capped by max_expansion_depth; skipped when
  expansion_enabled is false; defensive path-based cycle detection -> SC0003.
- Deduplication after expansion keeps the first occurrence (SC0007).
- Unknown concept -> SC0001 warning (continue); deprecated -> SC0006 (continue);
  invalid category -> SC0008 (stop); circular expansion -> SC0003 (stop).
- Every ResolvedTag keeps its entry's category and full traceability
  (source_concept = originating scene concept, knowledge_base_entry = generating
  entry). Verbose logging of each decision.

Tests (no Ollama): normalization, alias resolution + disable, expansion order,
nested/depth-capped/disabled expansion, traceability, dedup, unknown/deprecated,
circular/invalid-category stops, category preservation, and determinism.

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:58:12 +02:00
3f0358bfa9 feat(validator): implement the Scene Validator (#11) (#55)
Add the Scene Validator per MASTER_SPEC §14 and a central message-code registry.

- compiler/common/message_codes.py: single source of truth for message codes
  (Appendix B SC0001-SC0014 plus documented extensions SC0015 Unexpected Field
  Removed, SC0016 Empty Concept Removed, SC0017 Interaction Dropped) with a
  message() factory that pins each code's severity and title.
- compiler/validator/scene_validator.py: validate_scene(scene_json, config,
  logger=None) -> CompilerResult.
  - Strict schema validation classifies failures: missing required -> SC0009,
    wrong type/malformed -> SC0002 (Errors that stop the compilation).
  - Recoverable normalization: unexpected fields -> SC0015 warning + removed
    (suppressed when config.validator.allow_unknown_fields is true); empty/
    whitespace concepts -> SC0016 warning + removed after trimming; interactions
    whose participants reference a non-existent character -> SC0017 warning +
    dropped.
  - Never creates concepts, generates tags, or does KB lookup. Output is a
    Scene model wrapped in a CompilerResult; order is deterministic.

New warning codes (SC0015-SC0017) are extensions to be folded into Appendix B.

Tests: valid input, hard errors (SC0009/SC0002) that stop, recoverable warnings
(SC0015/SC0016/SC0017), allow_unknown_fields suppression, determinism, and the
code registry.

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:52:24 +02:00
583350034e feat(knowledge-base): add the Knowledge Base loader (#7) (#53)
Implement Knowledge Base loading and indexing per MASTER_SPEC §15.11, §27.3 in
compiler/common/knowledge_base.py.

- KnowledgeBaseEntry: frozen model of a canonical concept.
- KnowledgeBase: immutable, with id->entry and alias->id indexes and
  get / resolve_alias / lookup accessors (read-only mappings).
- load_knowledge_base: reads every *.json (array of entries) from a directory,
  delegates full validation to validate_knowledge_base (#9), and on any finding
  raises Fatal SC0004 KnowledgeBaseError with the findings attached -- indexes
  are built only after validation passes, so a failed load leaves no partial
  state.
- KnowledgeBaseLoader: caches the Knowledge Base after first get(); reload() is
  explicit and side-effect-free on failure (the previous KB is kept). No
  automatic file watching. Optional structured logger.

Tests: multi-file load, id/alias lookups, expand/deprecated preservation, empty
dir, invalid KB / duplicate id / invalid JSON -> SC0004, caching identity,
reload picks up changes, reload failure keeps the previous KB, and immutability.

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:36:31 +02:00
d6dcabe4f7 feat(knowledge-base): add Knowledge Base validation tooling (#9) (#52)
Implement whole-Knowledge-Base validation per MASTER_SPEC §15.10, §26.4:
compiler/common/kb_validation.py (importable by the loader) plus a CLI at
scripts/validate_knowledge_base.py.

validate_knowledge_base(entries) collects every problem (never stops at the
first); each Message carries a code, the offending id, and a description:
- duplicate canonical id -> SC0005
- invalid category -> SC0008 (drawn from the shared 19-category constant)
- circular expansion (incl. self-expansion) -> SC0003, via DFS cycle detection
- duplicate alias, alias colliding with a canonical id (no chains), missing
  expansion target, and per-entry schema nonconformance (empty tags, unknown
  fields, ...) -> umbrella SC0004
Findings are deterministically ordered.

The CLI loads every *.json (array of entries) from a directory, reports invalid
JSON / non-array files as SC0004, writes JSON findings to stdout, and exits
non-zero when invalid.

Tests: a passing fixture plus a fixture per failure mode, multi-problem
reporting, determinism, and the CLI on valid/invalid/broken-JSON inputs.

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:31:16 +02:00
07b0f9a18b feat(knowledge-base): define KB file format, entry rules, and categories (#8) (#51)
Formalize the Knowledge Base on-disk format and tighten the entry schema per
MASTER_SPEC §15.3-§15.10, §18.2.

- compiler/common/categories.py: single source of truth for the 19 canonical
  categories (lowercase identifiers of the §18.2 list) plus is_valid_category.
- knowledge_base_entry.schema.json (v1.1): category restricted to the 19; tags
  require at least one item; expand entries must be snake_case canonical ids;
  aliases uniqueItems.
- knowledge_base/README.md: by-domain file layout, the "each file is a JSON array
  of entries" format, and contributor field rules (id/alias global uniqueness,
  tag order, expansion targets, one-tag minimum).
- examples/knowledge_base.example.json: worked file demonstrating an alias and an
  expansion.

Cross-entry rules (global uniqueness, expand targets exist) are deferred to the
KB validation tooling (#9); this issue defines per-entry structure only.

Tests: 19-category constant and schema-enum consistency, entry validation
(category/tags/id/expand/unknown-field), and the worked example (array shape,
per-entry validity, alias+expansion present, expand targets resolve).

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:23:50 +02:00
8bfb8468b4 feat(compiler): add centralized structured logging (#6) (#50)
Implement structured, machine-readable logging per MASTER_SPEC §22, §25.7 in
compiler/common/log.py.

- DebugLevel IntEnum with the four levels (None/Basic/Verbose/Developer),
  ordered; DebugLevel.from_config maps a debug config section to a threshold
  (disabled -> None).
- LogRecord: immutable structured record (level, event, fields) with a
  machine-readable to_json; no timestamps, so logging stays deterministic.
- StructuredLogger: filters by threshold, emits to an injectable sink, and
  offers basic/verbose/developer helpers plus from_config. No global state.
- json_lines_sink writes one JSON object per line to a stream (never print).
- Tighten configuration.schema.json debug.level to enum
  none/basic/verbose/developer.

Tests: level filtering across all four levels, structured/immutable records,
config integration, and a guard asserting no print() exists in the compiler
package.

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:17:00 +02:00
d1bebdb571 feat(compiler): add typed, injectable configuration system (#5) (#49)
Implement the compiler configuration per MASTER_SPEC §23 in
compiler/common/config.py, with a strict configuration schema.

- Frozen dataclasses per section (analyzer, resolver, validator, prompt_builder,
  debug) plus a top-level `schema` version; every default matches §23.2.
- Config.from_json validates against schemas/json/configuration.schema.json and
  raises ConfigError carrying a Fatal SC0014 message on invalid input; every
  section/field is optional and falls back to defaults.
- load_config(path) reads and validates a JSON file.
- Deterministic by construction: additionalProperties:false rejects unknown keys
  (including any random seed); temperature has minimum 0.
- Injected into stages; no global/singleton config state.
- Ships config/default_config.json (the §23.2 example) as the canonical default.

Tests: defaults match §23.2, full/empty/partial load, file load, and
invalid-config rejection (wrong type, unknown field, forbidden seed, negative
temperature) producing SC0014.

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:11:44 +02:00
60eac7f1d4 feat(compiler): add Compiler Result wrapper and diagnostic messages (#4) (#48)
Implement the uniform result wrapper every stage returns (MASTER_SPEC §8.6, §21),
in compiler/common/result.py.

- Severity: StrEnum with the four levels (Information/Warning/Error/Fatal, §21.1).
- Message: immutable, machine-readable diagnostic (code, severity, title,
  description, optional read-only context) with lossless to_json/from_json.
- CompilerResult: immutable wrapper (data, warnings, errors, metadata) with
  success/has_warnings properties and functional add_warning/add_error/merge
  returning new instances. merge concatenates warnings then errors
  deterministically, takes the downstream data, and unions metadata.
- to_json serializes stage data via its own to_json when available.

Tests: construction, message round-trip, severity handling, immutability,
deterministic merge, and machine-readable serialization.

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:07:25 +02:00
72a60aa28b chore: initialize repository structure and tooling (#1) (#45)
Scaffold the Phase 0 foundation per MASTER_SPEC §24:

- Directory layout: nodes/, compiler/{analyzer,validator,resolver,splitter,
  builder,common}/, knowledge_base/, schemas/, prompts/, config/,
  tests/{analyzer,resolver,validator,integration,regression}/, examples/,
  scripts/, docs/.
- pyproject.toml targeting Python 3.11+, jsonschema as the single runtime dep,
  ruff + pytest as dev deps, explicit package list, and ruff/pytest config.
- ruff for linting and formatting; pytest with repo root on pythonpath.
- GitHub Actions CI running install, ruff lint, ruff format check, and pytest
  on Python 3.11 and 3.12 for pushes and pull requests.
- docs/ skeleton and CONTRIBUTING.md referencing the wiki.
- Layout test asserting the §24 structure and package importability.

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 16:59:32 +02:00