← Files DesignlyARCHIVED FILE
skills/composition-director/scripts/design_lint.py
6.54 KB · Sep 30, 2026 · 23:14 UTC
#!/usr/bin/env python3
import argparse, json, re, sys
from pathlib import Path
VAGUE_ADJECTIVES = {
"premium", "modern", "bold", "cinematic", "futuristic", "luxury", "luxurious",
"elegant", "stunning", "beautiful", "dynamic", "sleek", "sophisticated", "creative",
"eye-catching", "impactful", "epic"
}
SLOP_EFFECTS = {
"neon", "glow", "hologram", "holographic", "particles", "chrome", "liquid metal",
"lens flare", "cyberpunk", "glass", "smoke", "sparks", "floating objects", "bokeh"
}
def norm_words(text):
return re.findall(r"[a-zA-Z-]+", (text or "").lower())
def has_exception(data, needle):
for item in data.get("heuristic_exceptions", []):
if needle.lower() in str(item.get("heuristic", "")).lower():
return True
return False
def lint(data):
findings=[]
def add(sev, code, msg): findings.append({"severity":sev,"code":code,"message":msg})
intent=data.get("intent",{})
concept=data.get("concept",{})
hierarchy=data.get("hierarchy",{})
comp=data.get("composition",{})
typ=data.get("typography",{})
color=data.get("color",{})
effects=data.get("effects",[])
styles=data.get("style_families",[])
primary=str(intent.get("primary_message","")).strip()
if not primary:
add("critical","missing-primary-message","No primary message is locked")
prop=str(concept.get("visual_proposition","")).strip()
words=norm_words(prop)
meaningful=[w for w in words if w not in VAGUE_ADJECTIVES and w not in {"and","with","a","an","the","visual","image","design"}]
vague=[w for w in words if w in VAGUE_ADJECTIVES]
if not prop:
add("critical","missing-visual-proposition","The concept has no executable visual proposition")
elif len(meaningful) < 5 and len(vague) >= 2:
add("major","adjective-only-concept","The concept depends on style adjectives more than visible subject/action/relationship")
if not str(hierarchy.get("primary","")).strip() or not str(hierarchy.get("one_second_read","")).strip():
add("major","weak-hierarchy-contract","Primary focal event and one-second read must be explicit")
anchors=comp.get("alignment_anchors",[]) or []
grid=comp.get("grid_type")
if not grid:
add("major","missing-grid-logic","No grid type or structural layout behavior is defined")
if len(anchors) == 0:
add("major","missing-alignment-anchors","No alignment anchors are defined")
if grid == "freeform-anchors" and len(anchors) < 2:
add("major","underdefined-freeform","Freeform composition needs at least two explicit anchors")
if len(anchors) > 4 and not has_exception(data,"alignment"):
add("minor","anchor-entropy","More than four alignment anchors may fragment the composition; justify the exception if intentional")
focal=comp.get("focal_points",1)
if isinstance(focal,int) and focal > 2 and not has_exception(data,"focal"):
add("major","too-many-focal-points","More than two focal points are declared without an explicit multi-focal rationale")
if not str(comp.get("negative_space","")).strip():
add("minor","unassigned-negative-space","Negative space has no stated job")
if not str(comp.get("crop_logic","")).strip():
add("minor","missing-crop-logic","Crop behavior is not defined")
if not str(comp.get("eye_path","")).strip():
add("major","missing-eye-path","No eye path connects the focal event to supporting information")
tclass=typ.get("class")
roles=typ.get("roles",[]) or []
if tclass in {"typography-heavy","poster-ad-layout"} and not typ.get("exact_copy_locked"):
add("major","unlocked-exact-copy","Typography-heavy work should lock required copy before execution")
if len(roles) > 4 and not has_exception(data,"type"):
add("minor","type-role-proliferation","More than four type roles need a clear editorial or brand reason")
if tclass != "image-only" and not str(color.get("contrast_target","")).strip():
add("minor","missing-contrast-plan","Text-bearing work has no final contrast verification target")
if len(styles) > 2 and not has_exception(data,"style"):
add("major","style-family-entropy","More than two style families are mixed without an explicit reason")
unpurposed=[e for e in effects if not str(e.get("purpose","")).strip()]
if unpurposed:
add("critical","effect-without-job","At least one effect has no stated communication, hierarchy, depth, material, or brand job")
decorative_purposes=[]
for e in effects:
purpose=str(e.get("purpose","")).strip().lower()
if purpose in {"decoration","decorative","aesthetic","visual interest","looks cool","make it pop"}:
decorative_purposes.append(str(e.get("name","effect")))
if len(decorative_purposes)>=2:
add("major","effects-justified-only-as-decoration","Several effects exist only for decoration: " + ", ".join(decorative_purposes))
elif len(decorative_purposes)==1:
add("minor","effect-justified-only-as-decoration","Effect has no job beyond decoration: " + decorative_purposes[0])
if len(effects) > 3 and not has_exception(data,"effect"):
add("major","effect-stack","More than three explicit effects are stacked without an exception rationale")
effect_names=" ".join(str(e.get("name","")) for e in effects).lower()
slop_hits=sorted({term for term in SLOP_EFFECTS if term in effect_names})
if len(slop_hits) >= 4:
add("major","slop-effect-family-stack","Several common synthetic-effect families are stacked: " + ", ".join(slop_hits))
roles_text=" ".join(color.get("roles",[]) or []).lower()
accent=str(color.get("accent_strategy","")).lower()
if "accent" in roles_text and not accent.strip():
add("minor","undefined-accent-strategy","Accent color exists but its attention job is not defined")
return findings
def main():
ap=argparse.ArgumentParser(description="Lint a structured Art Direction Spec before generation")
ap.add_argument("spec")
ap.add_argument("--json", action="store_true", dest="as_json")
args=ap.parse_args()
data=json.loads(Path(args.spec).read_text(encoding="utf-8"))
findings=lint(data)
if args.as_json:
print(json.dumps(findings, indent=2, ensure_ascii=False))
elif not findings:
print("PASS: design preflight found no deterministic structural issues")
else:
for f in findings:
print(f"{f['severity'].upper()} [{f['code']}]: {f['message']}")
return 1 if any(f["severity"] in {"critical","major"} for f in findings) else 0
if __name__ == "__main__":
raise SystemExit(main())
SHA-256: f636f9b9440207c45f86868ccf44aba4a5eb0f1a4c88e1be2767173eebe47ce2