* perf: KB load-once cache, compiled index, lazy loading, parallel resolution (#38)
Closes#130, #131, #132, #133, #134.
- Loader node routes through a process-wide KnowledgeBaseLoader cache so the KB
loads once per (path, version) and is reused across run() calls.
- Normalized lookup table compiled once per KB (per NSFW gating) and reused across
resolves instead of rebuilt per resolve_scene. normalize_concept moved to
knowledge_base (re-exported from the resolver).
- Incremental per-category lazy file loading behind KnowledgeBaseLoader.
- Optional parallel per-concept resolution (thread pool), merged in exact
discovery order; deterministic and threshold-gated (default sequential).
- Performance regression guard (generous ceilings + relative ratio). Refreshed
docs/benchmarks.md (resolution 57.6us -> 17.8us).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(changelog): note V2 performance improvements (#38)
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>
* feat(kb-editor): web KB editor with live validation & atomic saves (#33)
Closes#123, #124, #125, #126.
- Backend API + transport-independent handlers under /scene-compiler/kb,
registered from the package init and guarded so it no-ops without ComfyUI.
- Static self-contained web page (list/search/filter/create/edit/delete) with
live validation and save blocking on errors.
- Validation reuses kb_validation (new validate_entry / validate_cross_entry) as
the single source of truth, against the candidate and the whole-KB state.
- Atomic (temp+fsync+rename), format-safe saves: only the edited entry changes,
key order and trailing newline preserved, curated-vs-generated split respected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(kb-editor): associate form labels with inputs; await load (SonarCloud a11y)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(kb-editor): label sidebar controls; async IIFE for init (SonarCloud)
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>
Closes#114, #115, #116, #117.
- Deterministic, offline, dependency-free char-ngram embedding backend + config
section (semantic.enabled default false, min_similarity, backend).
- Committed embedding index (data/kb_embedding_index.json) mapping vectors to real
KB entry ids only; built by scripts/build_embedding_index.py, loaded once/cached.
- Resolver falls back to the nearest entry on a deterministic miss when enabled,
accepting >= min_similarity and emitting SC0020; NSFW-gated neighbours are
skipped. Disabled => byte-identical to before.
- Guarantee tests: deterministic lookup always wins (exact hit never queries the
index), fallback emits only KB tags (no invention), identical input => identical
output. SC0020 registered.
Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Closes#111, #112, #113.
- Resolver recovers leading modifiers dropped by the head-noun reduction as
<modifier> <head-noun> compounds (open white shirt -> white shirt + open
shirt). KB-only, deterministic, no bare-word invention; goldens unchanged.
- Analyzer system prompt instructs full list transcription; a deterministic
post-parse check warns SC0021 when a tag-list yields fewer concepts than items.
- Data-driven fidelity regression suite locks both failure modes.
Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
remove_duplicate_tags now gates the Resolver's post-expansion dedup (SC0007);
trim_empty_outputs was inert since the V1.1 category removal and is dropped
from config, schema, node, and default_config.
Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: recover concept detail lost in resolver and harden analyzer parsing
Three shippability fixes for the v1 pipeline:
P2 (main): the Illustrious Resolver did exact-key KB lookup only, so any
compound concept the analyzer emits ("white summer dress", "black business
suit") missed and was dropped silently as SC0001. Add a deterministic
head-noun fallback (§17.2): on a miss, drop leading modifiers and look up the
longest remaining suffix ("white summer dress" -> "dress"). No tag/concept is
invented. Emits SC0019 (Concept Reduced) so the reduction is visible.
P1a: the analyzer required the entire model response to be a single JSON
document, so any surrounding prose ("Sure! Here is the scene:") failed parsing
and, with the prompt reused unchanged, every retry failed identically ->
empty output. Extract the first balanced top-level {...} object from
surrounding text (string-aware brace scan); the object bytes are unchanged and
must still validate, so this is unwrapping, not JSON repair.
P1b: the node's "raw" output was to_raw_json(result.data) -- a re-serialization
of the parsed Scene, so on failure it was empty and the model's actual text was
invisible. Capture the last raw model text in analyzer metadata and surface it
as the node's raw output.
Regenerated goldens: unknown_concepts "hologram jacket" now resolves to
"jacket" via head-noun fallback (intended).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: add entry rating + resolver.include_nsfw gating
Foundations for the Danbooru-vocab KB: a per-entry `rating` (general|explicit,
default general) and a `resolver.include_nsfw` config flag (default false). The
resolver omits explicit-rated entries from lookup and expansion unless enabled,
so a gated concept is reported as unknown (SC0001). Schema, model, config, and
example config updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: generate KB from embedded Danbooru vocab (188 -> 16.4k tags)
Replace the coverage ceiling of the 188-entry hand KB by importing a large,
validated Danbooru tag vocabulary. This is the fix for the core "detail loss"
symptom: compound concepts the analyzer emits ("red dress", "sunflower field",
"train station platform") are now real tags that resolve instead of being
dropped.
- scripts/build_vocab.py: one-time dev step that filters the Raffle node's
categorized tag dump (drops artist/character/copyright/attribution categories
and subjective adjectives, keeps only id-pattern-valid tags) into a committed,
self-contained snapshot at data/danbooru_vocab.txt (16.4k tags).
- scripts/generate_kb_from_vocab.py: additive, non-destructive generator. Writes
knowledge_base/gen_<category>.json, mapping the Raffle categories to the 19
canonical categories and tagging NSFW categories rating=explicit. Skips any tag
already defined as a hand-curated id/alias, so the curated canonicalization
(female + aliases) and expansions (school_uniform -> blazer, pleated_skirt) are
preserved and always win.
Result: 16.4k additive entries + 188 curated. Plain natural-language concepts
now resolve broadly via curated aliases + head-noun fallback with no analyzer
prompt change; NSFW gated behind resolver.include_nsfw; golden and dataset tests
stay green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style: ruff format build_vocab.py
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(security): drop untrusted CLI path in build_vocab (SonarCloud S8707)
The --source CLI argument fed an untrusted path to the file system (taint).
The script is a one-time dev step; hard-code the source path constant instead.
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>
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>
* 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>
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>
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>
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>
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>
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>
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>
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>
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>