@oh-story/dsh 0.1.17 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/lib/client.js +147 -96
- package/lib/client.js.map +3 -3
- package/lib/drama/manifest.json +77 -247
- package/lib/drama/skills/short-drama/SKILL.md +9 -1
- package/lib/drama/skills/short-drama/references/creator-documents.md +51 -23
- package/lib/drama/skills/short-drama/references/reference-roles.md +1 -1
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +442 -129
- package/lib/drama/skills/short-drama/scripts/project_tool.py +54 -1
- package/lib/drama/skills/short-drama-assets/SKILL.md +2 -6
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +9 -3
- package/lib/drama/skills/short-drama-assets/references/era-anchors.md +3 -3
- package/lib/drama/skills/short-drama-assets/references/location-and-view.md +4 -0
- package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +2 -2
- package/lib/drama/skills/short-drama-develop/references/episode-design.md +19 -4
- package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +1 -1
- package/lib/drama/skills/short-drama-edit/SKILL.md +1 -1
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +28 -6
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +1 -1
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +1 -5
- package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +2 -2
- package/lib/drama/skills/short-drama-produce/SKILL.md +2 -0
- package/lib/drama/skills/short-drama-review/SKILL.md +0 -4
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +1 -1
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +9 -9
- package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +43 -13
- package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +5 -5
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +5 -5
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +27 -3
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +2 -1
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +6 -8
- package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +16 -1
- package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +23 -6
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +2 -2
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +8 -1
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +41 -2
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +1 -1
- package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +5 -1
- package/lib/drama/skills/short-drama-write/SKILL.md +7 -3
- package/lib/drama/skills/short-drama-write/references/screenplay-format.md +1 -1
- package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +94 -1
- package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +13 -4
- package/lib/drama/skills/short-drama-write/scripts/selftest.py +3 -77
- package/lib/index.js +18 -14
- package/lib/index.js.map +2 -2
- package/lib/novel-to-game/README.md +20 -34
- package/lib/novel-to-game/README_ZH.md +20 -34
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +2 -6
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +33 -39
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +2 -2
- package/lib/novel-to-game/manifest.json +46 -46
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +1 -1
- package/lib/novel-to-game/skills/game-build/references/baked-sprite-workflow.md +2 -3
- package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +6 -7
- package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +2 -2
- package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +1 -4
- package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +1 -3
- package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +1 -3
- package/lib/novel-to-game/skills/game-concept/SKILL.md +1 -1
- package/lib/novel-to-game/skills/game-qa/SKILL.md +3 -4
- package/lib/novel-to-game/skills/game-qa/references/qa-contract.md +1 -1
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +3 -2
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +13 -14
- package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +1 -2
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +4 -4
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +2 -3
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +0 -8
- package/lib/oh-story/manifest.json +49 -49
- package/lib/oh-story/skills/story/SKILL.md +2 -2
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story-deslop/SKILL.md +2 -2
- package/lib/oh-story/skills/story-import/SKILL.md +5 -5
- package/lib/oh-story/skills/story-import/references/deep-analysis.md +1 -1
- package/lib/oh-story/skills/story-import/references/import-report.md +1 -1
- package/lib/oh-story/skills/story-import/references/import-tracking.md +1 -1
- package/lib/oh-story/skills/story-import/references/outline-reverse.md +1 -1
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +6 -1
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +2 -2
- package/lib/oh-story/skills/story-long-write/SKILL.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/agent-calls.md +3 -3
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +2 -2
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +6 -1
- package/lib/oh-story/skills/story-review/SKILL.md +8 -7
- package/lib/oh-story/skills/story-review/references/agent-prompts.md +7 -7
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +2 -2
- package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +6 -1
- package/lib/oh-story/skills/story-setup/SKILL.md +6 -6
- package/lib/oh-story/skills/story-short-write/SKILL.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/agent-calls.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +1 -1
- package/lib/types/client/game-preview-state.d.ts +34 -0
- package/lib/types/client/game-preview-state.d.ts.map +1 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/video-studio.d.ts +24 -0
- package/lib/types/client/video-studio.d.ts.map +1 -1
- package/lib/types/host-python.d.ts +10 -3
- package/lib/types/host-python.d.ts.map +1 -1
- package/lib/types/workspace-route.d.ts +6 -0
- package/lib/types/workspace-route.d.ts.map +1 -1
- package/package.json +21 -21
- package/lib/drama/skills/short-drama-assets/assets/character-look.example.jsonl +0 -3
- package/lib/drama/skills/short-drama-assets/assets/continuity.example.jsonl +0 -4
- package/lib/drama/skills/short-drama-assets/assets/decisions.example.jsonl +0 -5
- package/lib/drama/skills/short-drama-assets/assets/location-view.example.jsonl +0 -3
- package/lib/drama/skills/short-drama-assets/assets/occurrences.example.jsonl +0 -5
- package/lib/drama/skills/short-drama-assets/assets/prop-state.example.jsonl +0 -3
- package/lib/drama/skills/short-drama-assets/examples/minimal/characters.jsonl +0 -2
- package/lib/drama/skills/short-drama-assets/examples/minimal/looks.jsonl +0 -2
- package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +0 -267
- package/lib/drama/skills/short-drama-assets/scripts/selftest.py +0 -89
- package/lib/drama/skills/short-drama-image-prompts/examples/minimal-image-prompt-specs.jsonl +0 -2
- package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +0 -443
- package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +0 -95
- package/lib/drama/skills/short-drama-review/assets/finding-template.jsonl +0 -2
- package/lib/drama/skills/short-drama-review/assets/supersession-decision.example.json +0 -42
- package/lib/drama/skills/short-drama-review/assets/verdict-template.json +0 -32
- package/lib/drama/skills/short-drama-review/examples/minimal-findings.jsonl +0 -2
- package/lib/drama/skills/short-drama-review/examples/minimal-verdict.json +0 -34
- package/lib/drama/skills/short-drama-review/scripts/review_check.py +0 -325
- package/lib/drama/skills/short-drama-review/scripts/selftest.py +0 -87
- package/lib/drama/skills/short-drama-storyboard/assets/coverage-audition.example.jsonl +0 -2
- package/lib/drama/skills/short-drama-storyboard/assets/revision-lineage.fragment.json +0 -12
- package/lib/drama/skills/short-drama-storyboard/assets/scene-visual-plan.example.jsonl +0 -2
- package/lib/drama/skills/short-drama-storyboard/scripts/selftest.py +0 -191
- package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +0 -824
- package/lib/drama/skills/short-drama-video-prompts/assets/coverage-scope.fragment.json +0 -21
- package/lib/drama/skills/short-drama-video-prompts/assets/motion-terminal.example.jsonl +0 -2
- package/lib/drama/skills/short-drama-video-prompts/assets/performance.fragment.json +0 -38
- package/lib/drama/skills/short-drama-video-prompts/examples/minimal-music-specs.jsonl +0 -2
- package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +0 -329
- package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +0 -451
- package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +0 -327
- package/lib/drama/skills/short-drama-video-prompts/scripts/selftest.py +0 -82
- package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +0 -416
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Validate standalone image-prompt specs without generating media."""
|
|
3
|
-
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
import argparse
|
|
7
|
-
import json
|
|
8
|
-
import re
|
|
9
|
-
import sys
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Any, NamedTuple
|
|
12
|
-
|
|
13
|
-
MINIMUM_PYTHON = (3, 9)
|
|
14
|
-
if sys.version_info < MINIMUM_PYTHON:
|
|
15
|
-
raise SystemExit("image_prompt_check.py requires Python 3.9 or newer")
|
|
16
|
-
|
|
17
|
-
# ---------------------------------------------------------------------------
|
|
18
|
-
# REFERENCE RESOLVER -- reference implementation.
|
|
19
|
-
#
|
|
20
|
-
# Each skill checker carries its own copy of this block. The suite has no shared
|
|
21
|
-
# library on purpose: a skill must stay runnable after copying only its own
|
|
22
|
-
# directory, so duplicating these few lines across skills is the correct shape.
|
|
23
|
-
# Copy the block verbatim; do not import it.
|
|
24
|
-
# ---------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
SOURCES_RECORD_TYPE = "sources"
|
|
27
|
-
SOURCES_SCHEMA_VERSION = "1.0.0"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class ResolvedRef(NamedTuple):
|
|
31
|
-
"""An upstream reference with its snapshot resolved, whichever form it used."""
|
|
32
|
-
|
|
33
|
-
owner: str
|
|
34
|
-
artifact: str
|
|
35
|
-
record_id: str | None
|
|
36
|
-
field: str | None
|
|
37
|
-
authority: str | None
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class RefFinding(NamedTuple):
|
|
41
|
-
"""A structural defect in a reference object."""
|
|
42
|
-
|
|
43
|
-
code: str
|
|
44
|
-
location: str
|
|
45
|
-
detail: str
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def load_sources(document: Any) -> dict[str, dict[str, Any]]:
|
|
49
|
-
"""Return the ``sources`` declaration of a parsed file, or ``{}`` if absent.
|
|
50
|
-
|
|
51
|
-
Accepts a parsed ``.json`` document (a dict) or the parsed record list of a
|
|
52
|
-
``.jsonl`` file, whose declaration lives on the first record.
|
|
53
|
-
"""
|
|
54
|
-
if isinstance(document, list):
|
|
55
|
-
document = document[0] if document else None
|
|
56
|
-
if not isinstance(document, dict):
|
|
57
|
-
return {}
|
|
58
|
-
declared = document.get("sources")
|
|
59
|
-
if not isinstance(declared, dict):
|
|
60
|
-
return {}
|
|
61
|
-
return {key: value for key, value in declared.items() if isinstance(value, dict)}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def resolve_ref(
|
|
65
|
-
ref: Any, sources: dict[str, dict[str, Any]], location: str
|
|
66
|
-
) -> tuple[ResolvedRef | None, RefFinding | None]:
|
|
67
|
-
"""Resolve a reference object written in either the compact or expanded form."""
|
|
68
|
-
if not isinstance(ref, dict):
|
|
69
|
-
return None, RefFinding("REF_IS_NOT_AN_OBJECT", location, f"got {type(ref).__name__}")
|
|
70
|
-
src = ref.get("src")
|
|
71
|
-
if isinstance(src, str):
|
|
72
|
-
entry = sources.get(src)
|
|
73
|
-
if entry is None:
|
|
74
|
-
return None, RefFinding(
|
|
75
|
-
"REF_SRC_IS_NOT_DECLARED", location, f"src {src!r} has no sources entry"
|
|
76
|
-
)
|
|
77
|
-
owner, artifact = entry.get("owner"), entry.get("artifact")
|
|
78
|
-
if not (isinstance(owner, str) and isinstance(artifact, str)):
|
|
79
|
-
return None, RefFinding(
|
|
80
|
-
"SOURCE_ENTRY_IS_INCOMPLETE", location, f"sources[{src!r}] needs owner/artifact"
|
|
81
|
-
)
|
|
82
|
-
elif all(isinstance(ref.get(key), str) for key in ("owner", "artifact")):
|
|
83
|
-
owner, artifact = ref["owner"], ref["artifact"]
|
|
84
|
-
else:
|
|
85
|
-
return None, RefFinding(
|
|
86
|
-
"REF_HAS_NO_UPSTREAM_BINDING", location, "needs src, or owner+artifact"
|
|
87
|
-
)
|
|
88
|
-
optional = {
|
|
89
|
-
key: ref[key] for key in ("record_id", "field", "authority") if isinstance(ref.get(key), str)
|
|
90
|
-
}
|
|
91
|
-
return (
|
|
92
|
-
ResolvedRef(
|
|
93
|
-
owner,
|
|
94
|
-
artifact,
|
|
95
|
-
optional.get("record_id"),
|
|
96
|
-
optional.get("field"),
|
|
97
|
-
optional.get("authority"),
|
|
98
|
-
),
|
|
99
|
-
None,
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
# ---------------------------------------------------------------------------
|
|
104
|
-
# END REFERENCE RESOLVER
|
|
105
|
-
# ---------------------------------------------------------------------------
|
|
106
|
-
|
|
107
|
-
SKILL_ROOT = Path(__file__).resolve().parents[1]
|
|
108
|
-
HASH_RE = re.compile(r"[0-9a-f]{64}")
|
|
109
|
-
# `common-recipe.md` forbids weight syntax and any one engine's control words: a
|
|
110
|
-
# generic prompt that carries provider control syntax has stopped being generic.
|
|
111
|
-
# This checks syntax only. Whether the prose leans on generic quality language is
|
|
112
|
-
# `IMG-02`, a `craft_default` a creator may override with a reason, so it is a
|
|
113
|
-
# reviewer's call citing what the description actually lacks — see
|
|
114
|
-
# `common-recipe.md`, which forbids blocking delivery on a fixed word list.
|
|
115
|
-
ENGINE_SYNTAX_RE = re.compile(
|
|
116
|
-
r"(?:^|[\s,,((])--(?:ar|v|q|niji|style|no|seed|cref|sref)\b"
|
|
117
|
-
r"|::-?\d"
|
|
118
|
-
# Weight syntax is written (x:1.2); prose writes "(aperture: 1.8)" with a space.
|
|
119
|
-
r"|:[01]\.\d\s*[))]",
|
|
120
|
-
re.IGNORECASE,
|
|
121
|
-
)
|
|
122
|
-
PURPOSES = {
|
|
123
|
-
"character_sheet",
|
|
124
|
-
"location_plate",
|
|
125
|
-
"prop_plate",
|
|
126
|
-
"look_state_variant",
|
|
127
|
-
"edit_delta",
|
|
128
|
-
"lookdev_frame",
|
|
129
|
-
}
|
|
130
|
-
VENDOR_FIELDS = {
|
|
131
|
-
"authorization",
|
|
132
|
-
"credential",
|
|
133
|
-
"credentials",
|
|
134
|
-
"model",
|
|
135
|
-
"model_id",
|
|
136
|
-
"model_name",
|
|
137
|
-
"provider",
|
|
138
|
-
"provider_id",
|
|
139
|
-
"api_key",
|
|
140
|
-
"task_id",
|
|
141
|
-
"remote_id",
|
|
142
|
-
"access_token",
|
|
143
|
-
"token",
|
|
144
|
-
"secret",
|
|
145
|
-
"password",
|
|
146
|
-
}
|
|
147
|
-
NORMALIZED_VENDOR_FIELDS = {re.sub(r"[^a-z0-9]", "", key) for key in VENDOR_FIELDS}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
# The source policy -> render treatment mapping from `common-recipe.md`. A
|
|
151
|
-
# treatment outside its policy's row is a structural defect: the fix is a
|
|
152
|
-
# creator override that changes the accepted policy, never a quiet widening here.
|
|
153
|
-
ALLOWED_TREATMENTS = {
|
|
154
|
-
"exact_readable": {"readable", "postproduction"},
|
|
155
|
-
"graphic_only": {"symbolic"},
|
|
156
|
-
"no_readable_text": {"blank", "symbolic"},
|
|
157
|
-
"pending_creator_text": {"postproduction"},
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
# A blanket "no text in the image" instruction, however it is phrased. Matching
|
|
161
|
-
# two literals (`no text` / `无文字`) let the reference document's own worked
|
|
162
|
-
# counter-example through: `画面中不要任何文字` contains neither.
|
|
163
|
-
NO_TEXT_CONSTRAINT = re.compile(
|
|
164
|
-
r"(无任何(文字|字|文本)|无文字|不要(任何)?(文字|字|文本)|不出现(任何)?(文字|字)"
|
|
165
|
-
r"|没有(任何)?(文字|字)|不含(任何)?(文字|字)|禁止(出现)?(文字|字)"
|
|
166
|
-
r"|no\s+(visible\s+)?(text|lettering|writing|words|typography)"
|
|
167
|
-
r"|without\s+(any\s+)?text|text[-\s]free)",
|
|
168
|
-
re.IGNORECASE,
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
class ValidationError(ValueError):
|
|
173
|
-
pass
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def resolve_input(value: str | Path) -> Path:
|
|
177
|
-
path = Path(value).expanduser()
|
|
178
|
-
if path.exists() or path.is_absolute():
|
|
179
|
-
return path
|
|
180
|
-
return SKILL_ROOT / path
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
def load_jsonl(value: str | Path) -> tuple[dict[str, dict[str, Any]], list[dict[str, Any]]]:
|
|
184
|
-
"""Return the file's ``sources`` declaration and its prompt-spec records.
|
|
185
|
-
|
|
186
|
-
A leading ``{"record_type": "sources", ...}`` header declares the upstream
|
|
187
|
-
snapshots the specs point at; it is not a spec and never counts as one.
|
|
188
|
-
"""
|
|
189
|
-
path = resolve_input(value)
|
|
190
|
-
records: list[dict[str, Any]] = []
|
|
191
|
-
for number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
|
192
|
-
if not line.strip():
|
|
193
|
-
continue
|
|
194
|
-
try:
|
|
195
|
-
record = json.loads(line)
|
|
196
|
-
except json.JSONDecodeError as exc:
|
|
197
|
-
raise ValidationError(f"{path}:{number}: invalid JSON") from exc
|
|
198
|
-
if not isinstance(record, dict):
|
|
199
|
-
raise ValidationError(f"{path}:{number}: each record must be an object")
|
|
200
|
-
records.append(record)
|
|
201
|
-
sources: dict[str, dict[str, Any]] = {}
|
|
202
|
-
if records and records[0].get("record_type") == SOURCES_RECORD_TYPE:
|
|
203
|
-
header = records.pop(0)
|
|
204
|
-
if not isinstance(header.get("sources"), dict):
|
|
205
|
-
raise ValidationError(f"{path}: the sources header must declare a sources object")
|
|
206
|
-
sources = load_sources(header)
|
|
207
|
-
if not records:
|
|
208
|
-
raise ValidationError(f"{path}: no prompt specs")
|
|
209
|
-
return sources, records
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
def text(record: dict[str, Any], key: str, label: str) -> str:
|
|
213
|
-
value = record.get(key)
|
|
214
|
-
if not isinstance(value, str) or not value.strip():
|
|
215
|
-
raise ValidationError(f"{label}: {key} must be non-empty text")
|
|
216
|
-
return value
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
def nonempty_list(record: dict[str, Any], key: str, label: str) -> list[Any]:
|
|
220
|
-
value = record.get(key)
|
|
221
|
-
if not isinstance(value, list) or not value:
|
|
222
|
-
raise ValidationError(f"{label}: {key} must be a non-empty list")
|
|
223
|
-
return value
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def validate_ref(
|
|
227
|
-
value: Any,
|
|
228
|
-
sources: dict[str, dict[str, Any]],
|
|
229
|
-
label: str,
|
|
230
|
-
*,
|
|
231
|
-
field_allowed: bool = True,
|
|
232
|
-
) -> None:
|
|
233
|
-
if not isinstance(value, dict):
|
|
234
|
-
raise ValidationError(f"{label}: reference must be an object")
|
|
235
|
-
resolved, finding = resolve_ref(value, sources, label)
|
|
236
|
-
if finding is not None:
|
|
237
|
-
raise ValidationError(f"{label}: {finding.code}: {finding.detail}")
|
|
238
|
-
if resolved is None:
|
|
239
|
-
raise ValidationError(f"{label}: reference could not be resolved")
|
|
240
|
-
if not resolved.owner.strip() or not resolved.artifact.strip():
|
|
241
|
-
raise ValidationError(f"{label}: owner and artifact must be non-empty text")
|
|
242
|
-
if "record_id" not in value and (not field_allowed or "field" not in value):
|
|
243
|
-
raise ValidationError(f"{label}: record_id or field is required")
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def validate_reference_bindings(
|
|
247
|
-
record: dict[str, Any], sources: dict[str, dict[str, Any]], label: str
|
|
248
|
-
) -> None:
|
|
249
|
-
bindings = record.get("reference_bindings", [])
|
|
250
|
-
if not isinstance(bindings, list):
|
|
251
|
-
raise ValidationError(f"{label}: reference_bindings must be a list")
|
|
252
|
-
slots: set[str] = set()
|
|
253
|
-
orders: set[int] = set()
|
|
254
|
-
for index, binding in enumerate(bindings, 1):
|
|
255
|
-
item = f"{label}.reference_bindings[{index}]"
|
|
256
|
-
if not isinstance(binding, dict):
|
|
257
|
-
raise ValidationError(f"{item}: binding must be an object")
|
|
258
|
-
slot = text(binding, "slot_id", item)
|
|
259
|
-
order = binding.get("order")
|
|
260
|
-
if slot in slots:
|
|
261
|
-
raise ValidationError(f"{item}: duplicate slot_id {slot}")
|
|
262
|
-
if not isinstance(order, int) or order < 1 or order in orders:
|
|
263
|
-
raise ValidationError(f"{item}: order must be a unique positive integer")
|
|
264
|
-
slots.add(slot)
|
|
265
|
-
orders.add(order)
|
|
266
|
-
validate_ref(binding.get("artifact_ref"), sources, f"{item}.artifact_ref")
|
|
267
|
-
text(binding, "role", item)
|
|
268
|
-
nonempty_list(binding, "may_control", item)
|
|
269
|
-
nonempty_list(binding, "must_not_control", item)
|
|
270
|
-
admission = binding.get("admission_status")
|
|
271
|
-
if admission not in {"unverified", "creator_described", "visually_inspected"}:
|
|
272
|
-
raise ValidationError(f"{item}: invalid admission_status")
|
|
273
|
-
if admission == "unverified" and not binding.get("unresolved_risks"):
|
|
274
|
-
raise ValidationError(f"{item}: unverified references need unresolved_risks")
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
def vendor_field_paths(value: object, prefix: str = "") -> list[str]:
|
|
278
|
-
leaked: list[str] = []
|
|
279
|
-
if isinstance(value, dict):
|
|
280
|
-
for key, child in value.items():
|
|
281
|
-
name = str(key)
|
|
282
|
-
path = f"{prefix}.{name}" if prefix else name
|
|
283
|
-
normalized = re.sub(r"[^a-z0-9]", "", name.casefold())
|
|
284
|
-
if normalized in NORMALIZED_VENDOR_FIELDS:
|
|
285
|
-
leaked.append(path)
|
|
286
|
-
leaked.extend(vendor_field_paths(child, path))
|
|
287
|
-
elif isinstance(value, list):
|
|
288
|
-
for index, child in enumerate(value):
|
|
289
|
-
leaked.extend(vendor_field_paths(child, f"{prefix}[{index}]"))
|
|
290
|
-
return leaked
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
def validate_asset_spec(
|
|
294
|
-
record: dict[str, Any], sources: dict[str, dict[str, Any]], label: str
|
|
295
|
-
) -> None:
|
|
296
|
-
binding = record.get("asset_binding")
|
|
297
|
-
if not isinstance(binding, dict):
|
|
298
|
-
raise ValidationError(f"{label}: asset_binding must be an object")
|
|
299
|
-
validate_ref(binding.get("identity_ref"), sources, f"{label}.asset_binding.identity_ref")
|
|
300
|
-
validate_ref(binding.get("variant_ref"), sources, f"{label}.asset_binding.variant_ref")
|
|
301
|
-
for index, ref in enumerate(nonempty_list(record, "source_refs", label), 1):
|
|
302
|
-
validate_ref(ref, sources, f"{label}.source_refs[{index}]")
|
|
303
|
-
nonempty_list(record, "identity_or_form_anchors", label)
|
|
304
|
-
|
|
305
|
-
if record["purpose"] == "edit_delta":
|
|
306
|
-
edit = record.get("edit")
|
|
307
|
-
if not isinstance(edit, dict):
|
|
308
|
-
raise ValidationError(f"{label}: edit_delta requires edit")
|
|
309
|
-
validate_ref(edit.get("target_ref"), sources, f"{label}.edit.target_ref")
|
|
310
|
-
nonempty_list(edit, "changes", f"{label}.edit")
|
|
311
|
-
nonempty_list(edit, "preserve", f"{label}.edit")
|
|
312
|
-
text(edit, "continuity_impact", f"{label}.edit")
|
|
313
|
-
|
|
314
|
-
handling = record.get("text_handling")
|
|
315
|
-
if isinstance(handling, dict):
|
|
316
|
-
if "source_policy_ref" in handling:
|
|
317
|
-
validate_ref(
|
|
318
|
-
handling.get("source_policy_ref"),
|
|
319
|
-
sources,
|
|
320
|
-
f"{label}.text_handling.source_policy_ref",
|
|
321
|
-
)
|
|
322
|
-
treatment = handling.get("render_treatment")
|
|
323
|
-
if not isinstance(treatment, dict):
|
|
324
|
-
raise ValidationError(f"{label}: text_handling.render_treatment is required")
|
|
325
|
-
# `common-recipe.md` gives the source policy -> render treatment mapping
|
|
326
|
-
# as a structural check. Nothing implemented it, so a prop whose accepted
|
|
327
|
-
# policy is `no_readable_text` could ask for readable lettering and come
|
|
328
|
-
# back `valid` -- inventing a name or a number the creator never accepted.
|
|
329
|
-
source_mode = handling.get("source_mode")
|
|
330
|
-
allowed = (
|
|
331
|
-
ALLOWED_TREATMENTS.get(source_mode)
|
|
332
|
-
if isinstance(source_mode, str)
|
|
333
|
-
else None
|
|
334
|
-
)
|
|
335
|
-
mode = treatment.get("mode")
|
|
336
|
-
if allowed is not None and mode not in allowed:
|
|
337
|
-
raise ValidationError(
|
|
338
|
-
f"{label}: text policy {source_mode!r} allows "
|
|
339
|
-
f"{' or '.join(sorted(allowed))}, not {mode!r}; a creator "
|
|
340
|
-
f"override must change the policy rather than the treatment"
|
|
341
|
-
)
|
|
342
|
-
if source_mode == "exact_readable" and mode == "readable":
|
|
343
|
-
text(treatment, "exact_text", f"{label}.text_handling.render_treatment")
|
|
344
|
-
negatives = " ".join(str(item) for item in record.get("negative_constraints", []))
|
|
345
|
-
if NO_TEXT_CONSTRAINT.search(negatives):
|
|
346
|
-
raise ValidationError(f"{label}: readable text conflicts with a no-text constraint")
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
def validate_lookdev_spec(
|
|
350
|
-
record: dict[str, Any], sources: dict[str, dict[str, Any]], label: str
|
|
351
|
-
) -> None:
|
|
352
|
-
validate_ref(record.get("direction_ref"), sources, f"{label}.direction_ref")
|
|
353
|
-
validate_ref(record.get("production_profile_ref"), sources, f"{label}.production_profile_ref")
|
|
354
|
-
subjects = nonempty_list(record, "subject_bindings", label)
|
|
355
|
-
for index, subject in enumerate(subjects, 1):
|
|
356
|
-
if not isinstance(subject, dict):
|
|
357
|
-
raise ValidationError(f"{label}.subject_bindings[{index}]: must be an object")
|
|
358
|
-
item = f"{label}.subject_bindings[{index}]"
|
|
359
|
-
validate_ref(subject.get("identity_ref"), sources, f"{item}.identity_ref")
|
|
360
|
-
if "variant_ref" in subject:
|
|
361
|
-
validate_ref(subject.get("variant_ref"), sources, f"{item}.variant_ref")
|
|
362
|
-
text(subject, "role", item)
|
|
363
|
-
text(record, "test_question", label)
|
|
364
|
-
nonempty_list(record, "stable_visual_rules", label)
|
|
365
|
-
if record.get("lookdev_axis") == "high_pressure_scene":
|
|
366
|
-
refs = nonempty_list(record, "story_context_refs", label)
|
|
367
|
-
for index, ref in enumerate(refs, 1):
|
|
368
|
-
validate_ref(ref, sources, f"{label}.story_context_refs[{index}]")
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
def validate_records(
|
|
372
|
-
records: list[dict[str, Any]], sources: dict[str, dict[str, Any]] | None = None
|
|
373
|
-
) -> dict[str, Any]:
|
|
374
|
-
sources = sources or {}
|
|
375
|
-
identifiers: set[str] = set()
|
|
376
|
-
for index, record in enumerate(records, 1):
|
|
377
|
-
label = f"spec[{index}]"
|
|
378
|
-
spec_id = text(record, "spec_id", label)
|
|
379
|
-
if spec_id in identifiers:
|
|
380
|
-
raise ValidationError(f"{label}: duplicate spec_id {spec_id}")
|
|
381
|
-
identifiers.add(spec_id)
|
|
382
|
-
purpose = record.get("purpose")
|
|
383
|
-
if purpose not in PURPOSES:
|
|
384
|
-
raise ValidationError(
|
|
385
|
-
f"{label}: invalid purpose {purpose!r}; "
|
|
386
|
-
f"use one of {', '.join(sorted(PURPOSES))}"
|
|
387
|
-
)
|
|
388
|
-
if record.get("status") not in {"candidate", "accepted"}:
|
|
389
|
-
raise ValidationError(f"{label}: status must be candidate or accepted")
|
|
390
|
-
leaked = sorted(vendor_field_paths(record))
|
|
391
|
-
if leaked:
|
|
392
|
-
raise ValidationError(f"{label}: provider execution fields are forbidden: {', '.join(leaked)}")
|
|
393
|
-
prompt = text(record, "generic_prompt", label)
|
|
394
|
-
if HASH_RE.search(prompt) or "<sha256>" in prompt:
|
|
395
|
-
raise ValidationError(f"{label}: generic_prompt leaks internal hashes")
|
|
396
|
-
engine_syntax = ENGINE_SYNTAX_RE.search(prompt)
|
|
397
|
-
if engine_syntax:
|
|
398
|
-
raise ValidationError(
|
|
399
|
-
f"{label}: generic_prompt carries engine-specific syntax "
|
|
400
|
-
f"{engine_syntax.group(0).strip()!r}; keep it in a provider adapter"
|
|
401
|
-
)
|
|
402
|
-
validate_reference_bindings(record, sources, label)
|
|
403
|
-
if purpose == "lookdev_frame":
|
|
404
|
-
validate_lookdev_spec(record, sources, label)
|
|
405
|
-
else:
|
|
406
|
-
validate_asset_spec(record, sources, label)
|
|
407
|
-
return {
|
|
408
|
-
"status": "valid",
|
|
409
|
-
"specs": len(records),
|
|
410
|
-
"sources": len(sources),
|
|
411
|
-
"checks": [
|
|
412
|
-
"unique_ids",
|
|
413
|
-
"accepted_bindings",
|
|
414
|
-
# Named for what it does: every reference names a snapshot this
|
|
415
|
-
# file declares. Whether that record exists in the target artifact
|
|
416
|
-
# is not knowable here -- the checker is handed this file only.
|
|
417
|
-
"source_declaration",
|
|
418
|
-
"reference_slots",
|
|
419
|
-
"prompt_hygiene",
|
|
420
|
-
],
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
def validate_file(path: str | Path) -> dict[str, Any]:
|
|
425
|
-
sources, records = load_jsonl(path)
|
|
426
|
-
return validate_records(records, sources)
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
def main() -> int:
|
|
430
|
-
parser = argparse.ArgumentParser(description=__doc__)
|
|
431
|
-
parser.add_argument("specs")
|
|
432
|
-
args = parser.parse_args()
|
|
433
|
-
try:
|
|
434
|
-
result = validate_file(args.specs)
|
|
435
|
-
except (OSError, ValidationError) as exc:
|
|
436
|
-
print(f"image prompt check failed: {exc}", file=sys.stderr)
|
|
437
|
-
return 2
|
|
438
|
-
print(json.dumps(result, ensure_ascii=True, indent=2))
|
|
439
|
-
return 0
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if __name__ == "__main__":
|
|
443
|
-
raise SystemExit(main())
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Offline self-test for the standalone image-prompt checker."""
|
|
3
|
-
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
import copy
|
|
7
|
-
import sys
|
|
8
|
-
from typing import Any
|
|
9
|
-
|
|
10
|
-
from image_prompt_check import SKILL_ROOT, ValidationError, load_jsonl, validate_records
|
|
11
|
-
|
|
12
|
-
MINIMUM_PYTHON = (3, 9)
|
|
13
|
-
if sys.version_info < MINIMUM_PYTHON:
|
|
14
|
-
raise SystemExit("selftest.py requires Python 3.9 or newer")
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def require(condition: bool, message: str) -> None:
|
|
18
|
-
if not condition:
|
|
19
|
-
raise AssertionError(message)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def fail(records: list[dict[str, Any]], sources: dict[str, dict[str, Any]], marker: str) -> None:
|
|
23
|
-
try:
|
|
24
|
-
validate_records(records, sources)
|
|
25
|
-
except ValidationError as exc:
|
|
26
|
-
require(marker in str(exc), f"expected {marker!r}, got {exc!s}")
|
|
27
|
-
else:
|
|
28
|
-
raise AssertionError(f"expected failure containing {marker!r}")
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def expanded(ref: dict[str, Any], sources: dict[str, dict[str, Any]]) -> dict[str, Any]:
|
|
32
|
-
entry = sources[ref["src"]]
|
|
33
|
-
inline = {key: entry[key] for key in ("owner", "artifact")}
|
|
34
|
-
inline.update({key: value for key, value in ref.items() if key != "src"})
|
|
35
|
-
return inline
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def main() -> int:
|
|
39
|
-
sources, records = load_jsonl(SKILL_ROOT / "examples/minimal-image-prompt-specs.jsonl")
|
|
40
|
-
require(validate_records(records, sources)["specs"] == 1, "valid fixture count")
|
|
41
|
-
require(len(sources) == 3, "fixture declares its upstream snapshots once")
|
|
42
|
-
|
|
43
|
-
duplicate = [records[0], copy.deepcopy(records[0])]
|
|
44
|
-
fail(duplicate, sources, "duplicate spec_id")
|
|
45
|
-
|
|
46
|
-
bad_order = copy.deepcopy(records)
|
|
47
|
-
bad_order[0]["reference_bindings"].append(copy.deepcopy(bad_order[0]["reference_bindings"][0]))
|
|
48
|
-
fail(bad_order, sources, "duplicate slot_id")
|
|
49
|
-
|
|
50
|
-
leaked = copy.deepcopy(records)
|
|
51
|
-
leaked[0]["provider"] = "example"
|
|
52
|
-
fail(leaked, sources, "provider execution fields")
|
|
53
|
-
|
|
54
|
-
# A/B Round 2: the suite forbids provider control syntax in a generic prompt,
|
|
55
|
-
# but nothing enforced it — the only prompt check was for leaked hashes.
|
|
56
|
-
# Generic quality language is `IMG-02`, a `craft_default`; it is not checked
|
|
57
|
-
# here, because a validator cannot block delivery on a fixed word list.
|
|
58
|
-
for syntax in ("--ar 9:16", "(red coat:1.2)", "cat::2"):
|
|
59
|
-
engine_syntax = copy.deepcopy(records)
|
|
60
|
-
engine_syntax[0]["generic_prompt"] += f" {syntax}"
|
|
61
|
-
fail(engine_syntax, sources, "engine-specific syntax")
|
|
62
|
-
|
|
63
|
-
nested_provider = copy.deepcopy(records)
|
|
64
|
-
nested_provider[0]["reference_bindings"][0]["provider"] = "example"
|
|
65
|
-
fail(nested_provider, sources, "reference_bindings[0].provider")
|
|
66
|
-
|
|
67
|
-
nested_secret = copy.deepcopy(records)
|
|
68
|
-
nested_secret[0]["asset_binding"]["credentials"] = {"token": "not-safe"}
|
|
69
|
-
fail(nested_secret, sources, "asset_binding.credentials")
|
|
70
|
-
|
|
71
|
-
undeclared = copy.deepcopy(records)
|
|
72
|
-
undeclared[0]["asset_binding"]["identity_ref"]["src"] = "no-such-source"
|
|
73
|
-
fail(undeclared, sources, "REF_SRC_IS_NOT_DECLARED")
|
|
74
|
-
|
|
75
|
-
unbound = copy.deepcopy(records)
|
|
76
|
-
del unbound[0]["source_refs"][0]["src"]
|
|
77
|
-
fail(unbound, sources, "REF_HAS_NO_UPSTREAM_BINDING")
|
|
78
|
-
|
|
79
|
-
# Projects released before the sources declaration write the snapshot inline on
|
|
80
|
-
# every reference; both forms resolve to the same upstream binding.
|
|
81
|
-
inline = copy.deepcopy(records)
|
|
82
|
-
binding = inline[0]["asset_binding"]
|
|
83
|
-
binding["identity_ref"] = expanded(binding["identity_ref"], sources)
|
|
84
|
-
binding["variant_ref"] = expanded(binding["variant_ref"], sources)
|
|
85
|
-
inline[0]["source_refs"] = [expanded(ref, sources) for ref in inline[0]["source_refs"]]
|
|
86
|
-
for slot in inline[0]["reference_bindings"]:
|
|
87
|
-
slot["artifact_ref"] = expanded(slot["artifact_ref"], sources)
|
|
88
|
-
require(validate_records(inline, {})["specs"] == 1, "inline snapshots resolve without sources")
|
|
89
|
-
|
|
90
|
-
print("10 self-tests passed")
|
|
91
|
-
return 0
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if __name__ == "__main__":
|
|
95
|
-
raise SystemExit(main())
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
{"record_type":"sources","schema_version":"1.0.0","sources":{"screenplay-index":{"owner":"short-drama-write","artifact":"剧集/EP001/screenplay-index.jsonl"},"shots":{"owner":"short-drama-storyboard","artifact":"剧集/EP001/storyboard/shots.jsonl"},"screenplay":{"owner":"short-drama-write","artifact":"剧集/EP001/screenplay.md"}}}
|
|
2
|
-
{"finding_id":"FIND-EXAMPLE","diagnostic_code":"SCR_SCENE_NO_TURN","knowhow_rule_id":"SCR-01","scope":"story_script","classification":"reviewed_invariant","enforcer":"reviewer","evidence":"Bounded explanation of the conflict","evidence_refs":[{"role":"source","src":"screenplay-index","record_id":"BLK-EP001-SC001-A01"},{"role":"conflicting_consumer","src":"shots","record_id":"SHOT-001","field":"/purpose"}],"impact":"Audience or production consequence","disposition":"keep | post_production | targeted_edit | resubmit | rewrite | not_applicable","disposition_rationale":"Why this disposition: is the defect text-controllable, and has it recurred? Use not_applicable outside project calibration.","required_change":"Outcome required from the owner, or empty when the disposition calls for no change","owner_skill":"short-drama-write","severity":"error","status":"open","target_ref":{"src":"screenplay","record_id":"EP001-SC001"}}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"sources": {
|
|
3
|
-
"motion-specs-alternate": {
|
|
4
|
-
"owner": "short-drama-video-prompts",
|
|
5
|
-
"artifact": "剧集/EP001/storyboard/motion-specs.jsonl"
|
|
6
|
-
},
|
|
7
|
-
"motion-specs-master": {
|
|
8
|
-
"owner": "short-drama-video-prompts",
|
|
9
|
-
"artifact": "剧集/EP001/storyboard/motion-specs.jsonl"
|
|
10
|
-
},
|
|
11
|
-
"shots": {
|
|
12
|
-
"owner": "short-drama-storyboard",
|
|
13
|
-
"artifact": "剧集/EP001/storyboard/shots.jsonl"
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"example_only": true,
|
|
17
|
-
"decision_id": "SUPERSESSION-EXAMPLE",
|
|
18
|
-
"alternate_ref": {
|
|
19
|
-
"src": "motion-specs-alternate",
|
|
20
|
-
"record_id": "MOTION-ALTERNATE",
|
|
21
|
-
"authority": "candidate"
|
|
22
|
-
},
|
|
23
|
-
"master_ref": {
|
|
24
|
-
"src": "motion-specs-master",
|
|
25
|
-
"record_id": "MOTION-MASTER",
|
|
26
|
-
"authority": "accepted"
|
|
27
|
-
},
|
|
28
|
-
"checked_obligations": [
|
|
29
|
-
{
|
|
30
|
-
"source_ref": {
|
|
31
|
-
"src": "shots",
|
|
32
|
-
"record_id": "SHOT-EXAMPLE",
|
|
33
|
-
"field": "/end_boundary",
|
|
34
|
-
"authority": "accepted"
|
|
35
|
-
},
|
|
36
|
-
"result": "covered | missing | changed",
|
|
37
|
-
"evidence": "<指出 alternate 中的对应字段>"
|
|
38
|
-
}
|
|
39
|
-
],
|
|
40
|
-
"decision": "approve_supersession | reject_supersession",
|
|
41
|
-
"rationale": "<覆盖、对白、揭示、连续性和项目要求的复核结论>"
|
|
42
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"sources": {
|
|
3
|
-
"screenplay": {
|
|
4
|
-
"owner": "short-drama-write",
|
|
5
|
-
"artifact": "剧集/EP001/screenplay.md"
|
|
6
|
-
},
|
|
7
|
-
"findings": {
|
|
8
|
-
"owner": "short-drama-review",
|
|
9
|
-
"artifact": "审查/findings.jsonl"
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
"review_id": "REVIEW-EXAMPLE",
|
|
13
|
-
"scope": [
|
|
14
|
-
"story_script"
|
|
15
|
-
],
|
|
16
|
-
"reviewed_artifacts": [
|
|
17
|
-
{
|
|
18
|
-
"src": "screenplay"
|
|
19
|
-
}
|
|
20
|
-
],
|
|
21
|
-
"findings_ref": {
|
|
22
|
-
"src": "findings"
|
|
23
|
-
},
|
|
24
|
-
"review_method": "uninvolved_reviewer | self_check",
|
|
25
|
-
"reviewer": "<honest reviewer label>",
|
|
26
|
-
"structural_validation": "not_run",
|
|
27
|
-
"verdict": "PROVISIONAL",
|
|
28
|
-
"blocking_findings": [],
|
|
29
|
-
"open_blocker_count": 0,
|
|
30
|
-
"notes": [],
|
|
31
|
-
"supersession_decisions": []
|
|
32
|
-
}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
{"record_type":"sources","schema_version":"1.0.0","sources":{"screenplay-index":{"owner":"short-drama-write","artifact":"screenplay-index.jsonl"},"screenplay":{"owner":"short-drama-write","artifact":"screenplay.md"}}}
|
|
2
|
-
{"finding_id":"FIND-SC001-TURN","diagnostic_code":"SCR_SCENE_NO_TURN","knowhow_rule_id":"SCR-01","scope":"story_script","classification":"reviewed_invariant","enforcer":"reviewer","evidence":"SC001 ends with the same decision and relationship state with which it began.","evidence_refs":[{"role":"source","src":"screenplay-index","record_id":"SC001-A01"},{"role":"source","src":"screenplay-index","record_id":"SC001-A04"}],"impact":"The scene consumes time without changing the audience's understanding or the characters' options.","disposition":"targeted_edit","disposition_rationale":"The defect is local to the written scene turn and has not recurred elsewhere.","required_change":"End SC001 with a visible choice or consequence while preserving the accepted entrance facts.","owner_skill":"short-drama-write","severity":"error","status":"open","target_ref":{"src":"screenplay","record_id":"SC001"}}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"sources": {
|
|
3
|
-
"screenplay": {
|
|
4
|
-
"owner": "short-drama-write",
|
|
5
|
-
"artifact": "screenplay.md"
|
|
6
|
-
},
|
|
7
|
-
"findings": {
|
|
8
|
-
"owner": "short-drama-review",
|
|
9
|
-
"artifact": "minimal-findings.jsonl"
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
"review_id": "REVIEW-SC001",
|
|
13
|
-
"scope": [
|
|
14
|
-
"story_script"
|
|
15
|
-
],
|
|
16
|
-
"reviewed_artifacts": [
|
|
17
|
-
{
|
|
18
|
-
"src": "screenplay"
|
|
19
|
-
}
|
|
20
|
-
],
|
|
21
|
-
"findings_ref": {
|
|
22
|
-
"src": "findings"
|
|
23
|
-
},
|
|
24
|
-
"review_method": "self_check",
|
|
25
|
-
"reviewer": "standalone example self-check",
|
|
26
|
-
"structural_validation": "passed",
|
|
27
|
-
"verdict": "REVISE",
|
|
28
|
-
"blocking_findings": [
|
|
29
|
-
"FIND-SC001-TURN"
|
|
30
|
-
],
|
|
31
|
-
"open_blocker_count": 1,
|
|
32
|
-
"notes": [],
|
|
33
|
-
"supersession_decisions": []
|
|
34
|
-
}
|