@@ -51,21 +52,35 @@ Or run the deterministic helpers directly:
```bash
python skills/refine-readme/scripts/inspect_repository.py /path/to/repository
+python skills/refine-readme/scripts/plan_directions.py /path/to/repository
python skills/refine-readme/scripts/check_readme.py /path/to/repository
python skills/refine-readme/scripts/render_cover.py \
--style protocol-grid \
--title "My Project" \
--tagline "One clear promise backed by real proof" \
+ --eyebrow "ASYNC VIDEO API" \
+ --proof-label "POST /v1/tasks" \
--output assets/readme/cover.svg
```
-## Six project-native cover directions
+## From repository evidence to three directions
+
+Before creating a cover, the Skill resolves five things: audience, one-sentence
+value, primary proof, first successful action, and native visual material. It
+then proposes three directions that each identify a repository-specific motif,
+proof source, construction mode, hero composition, and risk.
+
+The bundled styles below are direction seeds, not fixed templates. Their palette,
+composition, and proof slots are adapted to the project. If removing the project
+name would make the result fit an unrelated repository, the direction fails.
+
+## Six cover direction seeds
-| Style | Best for | Visual language |
+| Seed | Best for | Visual language |
| --- | --- | --- |
| Protocol Grid | APIs, SDKs, CLIs, infrastructure | Terminal rhythm, request/response blocks, grids, system paths |
| Product Proof | SaaS, web apps, AI tools | Real screenshots or outputs framed by precise SVG typography |
@@ -77,6 +92,25 @@ python skills/refine-readme/scripts/render_cover.py \
Generated imagery never owns exact project text. When a style needs an organic
subject, image generation creates only the subject or background; deterministic
SVG overlays the project name, commands, labels, and factual claims.
+User covers contain no README Refiner watermark or branding by default.
+
+## Real Before / After
+
+
+
+
+
+The public [`BeatAPI/awesome-3d-prompts`](https://github.com/BeatAPI/awesome-3d-prompts)
+history provides a durable comparison: the
+[`a591c0f` snapshot](https://github.com/BeatAPI/awesome-3d-prompts/blob/a591c0ffee88fb5d529f4da0931465ce37980a25/README.md)
+is an 80-line, text-first catalog with 29 accepted cases; the
+[`ac37217` snapshot](https://github.com/BeatAPI/awesome-3d-prompts/blob/ac37217b7b723fbe38095503e06e3b818fbb1a85/README.md)
+is a 300+ case visual gallery with a hero, workflow navigation, result media,
+prompt-fidelity labels, and source attribution.
+
+[See the evidence and exact comparison](examples/awesome-3d-prompts-before-after.md).
+This is a real repository-history reference for the Refiner quality bar, not a
+claim that this Skill authored the historical commits.
## Modes
@@ -84,7 +118,7 @@ SVG overlays the project name, commands, labels, and factual claims.
| --- | --- |
| `audit` | Read-only review of clarity, proof, trust, and maintenance cost |
| `cover` | Recommend three directions and create cover assets only |
-| `beautify` | Apply the complete five-layer workflow and produce a README diff |
+| `beautify` | Apply the complete project-native workflow and produce a README diff |
| `check` | Run factual and GitHub rendering checks without redesigning |
No mode commits, pushes, opens a pull request, or publishes without explicit
@@ -116,8 +150,9 @@ assets/readme/architecture.svg optional workflow or system explanation
## Status
-This is the first public version. The core workflow, six cover presets, repository
-inspector, README checker, and deterministic SVG renderer are available now.
+This is the first public version. The project-native direction gate, six cover
+seeds, repository inspector, direction planner, README checker, and deterministic
+SVG renderer are available now.
GitHub-like browser previews, additional project fixtures, and continuous README
checks will be added through real repository usage.
diff --git a/assets/readme/awesome-3d-prompts-before-after.svg b/assets/readme/awesome-3d-prompts-before-after.svg
new file mode 100644
index 0000000..da29cb4
--- /dev/null
+++ b/assets/readme/awesome-3d-prompts-before-after.svg
@@ -0,0 +1,83 @@
+
diff --git a/examples/awesome-3d-prompts-before-after.md b/examples/awesome-3d-prompts-before-after.md
new file mode 100644
index 0000000..52daf29
--- /dev/null
+++ b/examples/awesome-3d-prompts-before-after.md
@@ -0,0 +1,53 @@
+# Awesome 3D Prompts: real README before and after
+
+This comparison uses two immutable commits from the public
+[`BeatAPI/awesome-3d-prompts`](https://github.com/BeatAPI/awesome-3d-prompts)
+repository. It is a reference for README Refiner's quality bar, not a claim that
+README Refiner authored the historical change.
+
+## Fixed snapshots
+
+- **Before:** [`a591c0ffee88fb5d529f4da0931465ce37980a25`](https://github.com/BeatAPI/awesome-3d-prompts/blob/a591c0ffee88fb5d529f4da0931465ce37980a25/README.md)
+- **After:** [`ac37217b7b723fbe38095503e06e3b818fbb1a85`](https://github.com/BeatAPI/awesome-3d-prompts/blob/ac37217b7b723fbe38095503e06e3b818fbb1a85/README.md)
+- **README diff:** [`a591c0f...ac37217`](https://github.com/BeatAPI/awesome-3d-prompts/compare/a591c0ffee88fb5d529f4da0931465ce37980a25...ac37217b7b723fbe38095503e06e3b818fbb1a85)
+
+## Evidence ledger
+
+| Signal | Before | After |
+| --- | --- | --- |
+| README length | 80 lines | 949 lines |
+| Catalog size | 29 accepted cases | 306 source-backed cases |
+| Opening visual | None | Repository-owned hero image |
+| Result proof | Text tables link to detail pages | Featured entries show visible result media in the README |
+| Navigation | Category headings | Six workflow links with counts |
+| Trust cues | Source and rights explanation | Source attribution, prompt-fidelity labels, evidence notes, and rights links |
+| Media summary | No media summary | 250 WebM videos and 56 WebP images |
+
+Counts above are read from the two committed READMEs and their committed prompt
+data. The comparison does not change or reinterpret the source prompts.
+
+## What changed in the reading experience
+
+The earlier README is accurate, but a visitor must read tables and open detail
+pages before seeing why the collection is useful. The later README leads with a
+clear promise, visible output, workflow-level navigation, and evidence labels.
+The underlying source discipline remains intact; the presentation makes that
+discipline easier to understand and trust.
+
+This is the pattern README Refiner should reproduce on other repositories:
+
+1. preserve repository truth;
+2. move the strongest real proof forward;
+3. establish a project-native visual system;
+4. improve scanning without hiding detail;
+5. keep every important claim traceable to source.
+
+## Reproduce the comparison
+
+From a clone of the 3D prompt repository:
+
+```bash
+git show a591c0ffee88fb5d529f4da0931465ce37980a25:README.md
+git show ac37217b7b723fbe38095503e06e3b818fbb1a85:README.md
+git diff a591c0ffee88fb5d529f4da0931465ce37980a25..ac37217b7b723fbe38095503e06e3b818fbb1a85 -- README.md
+```
diff --git a/skills/refine-readme/SKILL.md b/skills/refine-readme/SKILL.md
index 07813f0..5aa851b 100644
--- a/skills/refine-readme/SKILL.md
+++ b/skills/refine-readme/SKILL.md
@@ -16,14 +16,14 @@ wants `audit`, `cover`, `beautify`, or `check`.
- `audit`: inspect and report only; do not edit files.
- `cover`: create visual assets only; do not edit README content or references.
-- `beautify`: execute the complete five-layer workflow and propose a README diff.
+- `beautify`: execute the complete project-native workflow and propose a README diff.
- `check`: validate facts and GitHub rendering without redesigning.
Reading repository files does not grant permission to edit, commit, push, open a
pull request, or publish. Always preview first. Require separate explicit
approval for commit, push, PR, and publication actions.
-## Run the five-layer workflow
+## Run the project-native workflow
1. Establish repository truth.
- Inspect the current README, manifests, package scripts, public routes,
@@ -33,7 +33,19 @@ approval for commit, push, PR, and publication actions.
- Build a fact ledger that separates confirmed evidence, reasonable
inference, user-supplied claims, and unsupported claims.
-2. Rebuild the story and Markdown hierarchy.
+2. Pass the project-native direction gate.
+ - Resolve the audience, one-sentence value, primary proof, first successful
+ action, and native visual material before selecting a style.
+ - Run `scripts/plan_directions.py ` for three evidence-led
+ candidates, then sharpen them with repository-specific reasoning.
+ - If the planner reports a blocked gate, request repository evidence instead
+ of returning arbitrary zero-evidence styles.
+ - For every direction, explain why it fits, its project-native motif, its
+ real proof, construction mode, hero composition, and primary risk.
+ - Treat bundled styles as seeds and constraints, not immutable templates.
+ - Read `references/project-native-directions.md` before recommending styles.
+
+3. Rebuild the story and Markdown hierarchy.
- Make the first screen answer what the project is, who it is for, what proof
exists, and how to try it.
- Move real screenshots, outputs, examples, or a minimal command ahead of
@@ -42,20 +54,21 @@ approval for commit, push, PR, and publication actions.
precise and scannable.
- Read `references/five-layer-workflow.md` for the default section logic.
-3. Create the cover and visual identity.
+4. Create the cover and visual identity.
- In `beautify` mode, create a cover unless the user explicitly opts out.
- Default to `1200x400` SVG. Use `1200x320` for compact technical projects
and `1200x480` for proof-rich showcases.
- - When no style is selected, recommend exactly three suitable presets from
- `references/style-catalog.md`, explain each in one sentence, and ask the
- user to choose. Auto-select only when the user says to decide automatically.
- - Use exactly one style preset. Do not blend multiple visual systems.
+ - When no direction is selected, recommend exactly three evidence-led
+ directions and ask the user to choose. Auto-select only when the user says
+ to decide automatically.
+ - Choose one coherent direction. Adapt one style seed to the project instead
+ of applying a fixed template or mixing unrelated decorative traits.
- Use `scripts/render_cover.py` for deterministic exact-text SVG covers.
- If generated imagery is needed, generate only the subject, texture, or
background. Add project names, commands, metrics, and labels through SVG.
- Read `references/cover-system.md` before creating or reviewing a cover.
-4. Add proof and explanation.
+5. Add proof and explanation.
- Prefer real screenshots, outputs, input/output comparisons, terminal
captures, or diagrams over decorative images.
- Use SVG for exact diagrams and coordinated section transitions; use
@@ -63,7 +76,7 @@ approval for commit, push, PR, and publication actions.
- Keep explanations and commands in Markdown so they remain searchable and
copyable.
-5. Validate and deliver.
+6. Validate and deliver.
- Run `scripts/check_readme.py `.
- Review factual claims against the ledger and `references/fact-check.md`.
- Apply the GitHub rendering rules in `references/github-rendering.md`.
@@ -92,8 +105,11 @@ to the run.
- The first screen communicates one concrete promise and one real proof.
- The cover uses exact, readable text and survives narrow rendering.
- The title, palette, motifs, and proof feel native to this repository.
+- Removing the project name would not make the visual fit an unrelated project.
- Claims, commands, paths, versions, ports, and public interfaces match source.
- Images have meaningful alt text and do not replace essential body content.
- Relative paths and heading anchors resolve.
- The result remains useful with images disabled.
- The user can review every change before anything is published.
+- Do not place README Refiner branding in user assets by default; attribution is
+ optional and belongs outside the project hero.
diff --git a/skills/refine-readme/references/cover-system.md b/skills/refine-readme/references/cover-system.md
index bc9c69f..bbb97d9 100644
--- a/skills/refine-readme/references/cover-system.md
+++ b/skills/refine-readme/references/cover-system.md
@@ -22,6 +22,8 @@ strong symbolic or typographic identities. Keep all factual text in SVG source.
Use real screenshots, terminal output, generated results, or prompt cards inside
a deterministic SVG frame. Do not invent dashboard data or polished outputs.
+Use a single-board hero only when the proof remains readable at the target
+GitHub width. Otherwise make the cover simple and place proof immediately below.
### Hybrid generated subject
@@ -37,6 +39,8 @@ place it under exact SVG typography. Preserve a source prompt and editable SVG.
- Use no more than one short supporting line in the cover.
- Avoid tiny fake UI, dense diagrams, long feature lists, and decorative badges.
- Test the cover on light and dark GitHub themes and near 360 CSS pixels wide.
+- Keep project attribution separate from tool attribution. User covers contain
+ no README Refiner branding or watermark by default.
## Required checks
diff --git a/skills/refine-readme/references/project-native-directions.md b/skills/refine-readme/references/project-native-directions.md
new file mode 100644
index 0000000..0e023d7
--- /dev/null
+++ b/skills/refine-readme/references/project-native-directions.md
@@ -0,0 +1,44 @@
+# Project-native direction gate
+
+Do not begin production by asking only which visual preset the user likes. Read
+the repository and produce three defensible directions first.
+
+## Required project brief
+
+Resolve or explicitly mark unknown:
+
+1. **Audience** — who reaches this repository and what they already understand.
+2. **One-sentence value** — one confirmed outcome, without slogans.
+3. **Primary proof** — the strongest real output, screenshot, command, or flow.
+4. **First successful action** — the shortest verified path to useful output.
+5. **Native visual material** — product UI, terminal grammar, data shape, workflow,
+ domain object, logo, palette, or other material that genuinely belongs here.
+
+If the repository contains too little evidence to support a direction, stop at
+the gate and request the missing proof. Do not fill all three slots with
+zero-evidence presets.
+
+## Three-direction contract
+
+Each direction must state:
+
+- why it fits this repository;
+- which native motif it uses;
+- which real proof leads the design;
+- whether it uses deterministic SVG, a proof composite, or a generated subject;
+- whether the hero integrates proof or is followed by a separate proof board;
+- its main legibility, factual, or maintenance risk.
+
+Style presets are seeds and constraints, not finished templates. Adapt their
+palette, composition, motif, and proof slots to repository evidence. Never mix
+decorative traits from several presets without one coherent art direction.
+
+## Decision test
+
+Before producing assets, remove the repository name mentally. If the direction
+could fit an unrelated project without changing its motif or proof, redesign it.
+
+Use an integrated hero only when the proof remains legible near 900 CSS pixels.
+Otherwise use a simpler title cover followed immediately by a real proof board.
+At 360 CSS pixels, the title and one concrete promise must remain readable even
+when the proof itself moves below the cover.
diff --git a/skills/refine-readme/references/style-catalog.md b/skills/refine-readme/references/style-catalog.md
index d078956..81e632f 100644
--- a/skills/refine-readme/references/style-catalog.md
+++ b/skills/refine-readme/references/style-catalog.md
@@ -1,7 +1,9 @@
-# Cover style catalog
+# Cover direction seeds
-Recommend exactly three styles when the user has not selected one. Select one
-final style before generating production assets.
+Recommend exactly three evidence-led directions when the user has not selected
+one. These entries provide useful constraints and production primitives; they
+are not six fixed templates. Adapt one seed around a repository-native motif and
+real proof before generating production assets.
| Style | Best for | Distinguishing anchors |
| --- | --- | --- |
@@ -13,3 +15,7 @@ final style before generating production assets.
| `integration-bridge` | Plugins, MCP, APIs, integrations | Two recognizable endpoints, one restrained connector, derived accent colors |
Full machine-readable metadata lives in `../assets/styles/presets.json`.
+
+Do not expose the internal seed name as user-facing branding inside the cover.
+Do not add README Refiner branding, watermarks, or promotional links to project
+assets unless the user explicitly requests attribution.
diff --git a/skills/refine-readme/scripts/inspect_repository.py b/skills/refine-readme/scripts/inspect_repository.py
index 0c301f6..fdeb5ab 100644
--- a/skills/refine-readme/scripts/inspect_repository.py
+++ b/skills/refine-readme/scripts/inspect_repository.py
@@ -53,6 +53,28 @@ def package_facts(path: Path) -> dict:
}
+def pyproject_facts(path: Path) -> dict:
+ text = read_text(path)
+ if not text:
+ return {"parse_error": True}
+ project_match = re.search(r"(?ms)^\[project\]\s*(.*?)(?=^\[|\Z)", text)
+ project_text = project_match.group(1) if project_match else ""
+
+ def scalar(name: str) -> str | None:
+ match = re.search(rf'(?m)^{re.escape(name)}\s*=\s*["\']([^"\']+)["\']', project_text)
+ return match.group(1) if match else None
+
+ scripts_match = re.search(r"(?ms)^\[project\.scripts\]\s*(.*?)(?=^\[|\Z)", text)
+ scripts_text = scripts_match.group(1) if scripts_match else ""
+ scripts = sorted(re.findall(r"(?m)^([A-Za-z0-9_.-]+)\s*=", scripts_text))
+ return {
+ "name": scalar("name"),
+ "description": scalar("description"),
+ "requires_python": scalar("requires-python"),
+ "scripts": scripts,
+ }
+
+
def inventory(root: Path) -> dict:
readme = find_readme(root)
readme_text = read_text(readme) if readme else ""
@@ -65,10 +87,15 @@ def inventory(root: Path) -> dict:
for name in MANIFESTS:
path = root / name
if path.is_file():
- manifests[name] = package_facts(path) if name == "package.json" else "present"
+ if name == "package.json":
+ manifests[name] = package_facts(path)
+ elif name == "pyproject.toml":
+ manifests[name] = pyproject_facts(path)
+ else:
+ manifests[name] = "present"
assets: list[str] = []
- for base_name in ("assets", "public", "docs", "media", "screenshots"):
+ for base_name in ("assets", "_assets", "public", "docs", "media", "screenshots"):
base = root / base_name
if not base.is_dir():
continue
@@ -81,7 +108,10 @@ def inventory(root: Path) -> dict:
break
package = manifests.get("package.json") if isinstance(manifests.get("package.json"), dict) else {}
+ pyproject = manifests.get("pyproject.toml") if isinstance(manifests.get("pyproject.toml"), dict) else {}
project_name = package.get("name") if package else None
+ if not project_name and pyproject:
+ project_name = pyproject.get("name")
if not project_name and headings:
project_name = re.sub(r"[*_`]", "", headings[0]["text"])
@@ -89,6 +119,7 @@ def inventory(root: Path) -> dict:
"root": str(root),
"project_name": project_name or root.name,
"readme": readme.name if readme else None,
+ "readme_excerpt": re.sub(r"\s+", " ", readme_text[:2_500]).strip(),
"headings": headings,
"manifests": manifests,
"existing_visual_assets": sorted(assets),
diff --git a/skills/refine-readme/scripts/plan_directions.py b/skills/refine-readme/scripts/plan_directions.py
new file mode 100644
index 0000000..471907a
--- /dev/null
+++ b/skills/refine-readme/scripts/plan_directions.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python3
+"""Recommend three evidence-led README directions without editing the repository."""
+
+from __future__ import annotations
+
+import argparse
+import json
+import re
+import sys
+from pathlib import Path
+
+
+HERE = Path(__file__).resolve().parent
+sys.path.insert(0, str(HERE))
+
+from inspect_repository import inventory # noqa: E402
+
+
+STYLE_RULES = {
+ "protocol-grid": {
+ "terms": ("api", "sdk", "cli", "endpoint", "developer", "server", "infrastructure", "async", "asynchronous", "task", "request", "response"),
+ "why": "The repository exposes a technical interface that can become the visual grammar.",
+ "motif": "one real request, its task lifecycle, and the verified output",
+ "proof": "a copyable command or real request/response",
+ },
+ "product-proof": {
+ "terms": ("app", "dashboard", "web", "studio", "generator", "video", "image", "product", "gallery", "preview"),
+ "why": "The result is easier to trust when real product output leads the visual system.",
+ "motif": "one real product frame or output, not a generic device mockup",
+ "proof": "a real screenshot, generated result, or before/after",
+ },
+ "research-field": {
+ "terms": ("research", "model", "dataset", "benchmark", "paper", "analysis", "knowledge", "evidence"),
+ "why": "The repository is better framed through its method and evidence than through decorative UI.",
+ "motif": "one repository-specific method, signal, or data relationship",
+ "proof": "a verified method, dataset, benchmark, or result",
+ },
+ "ink-archive": {
+ "terms": ("database", "storage", "archive", "index", "query", "record", "catalog", "collection"),
+ "why": "Records, queries, or indexed material provide a native restrained visual language.",
+ "motif": "one real record, query, index, or catalog structure",
+ "proof": "a real query, record, or indexed output",
+ },
+ "modular-build": {
+ "terms": ("builder", "workflow", "automation", "pipeline", "template", "tutorial", "compose"),
+ "why": "The project value is created through an understandable sequence of building blocks.",
+ "motif": "the smallest real sequence from input to completed output",
+ "proof": "a verified workflow, module path, or completed result",
+ },
+ "integration-bridge": {
+ "terms": ("plugin", "integration", "mcp", "dify", "connector", "webhook", "provider"),
+ "why": "The project connects two recognizable systems through one concrete action.",
+ "motif": "the two real endpoints and the action that crosses between them",
+ "proof": "a verified integration flow or successful action",
+ },
+}
+
+
+def repository_text(data: dict) -> str:
+ pieces = [str(data.get("project_name", "")), str(data.get("readme_excerpt", ""))]
+ for heading in data.get("headings", []):
+ pieces.append(str(heading.get("text", "")))
+ for manifest in data.get("manifests", {}).values():
+ if isinstance(manifest, dict):
+ pieces.extend(str(manifest.get(key, "")) for key in ("name", "description"))
+ pieces.extend(data.get("existing_visual_assets", []))
+ return " ".join(pieces).lower()
+
+
+def recommend(data: dict) -> dict:
+ text = repository_text(data)
+ tokens = set(re.findall(r"[a-z0-9]+", text))
+ visual_markers = ("screenshot", "demo", "output", "result", "preview", "hero", "gallery")
+ has_visual_proof = any(
+ marker in asset.lower()
+ for asset in data.get("existing_visual_assets", [])
+ for marker in visual_markers
+ )
+
+ scored: list[tuple[int, str, list[str]]] = []
+ for style, rule in STYLE_RULES.items():
+ matched_terms = sorted(term for term in rule["terms"] if term in tokens)
+ score = len(matched_terms) * 3
+ if style == "product-proof" and has_visual_proof:
+ score += 5
+ scored.append((score, style, matched_terms))
+ scored.sort(key=lambda item: (-item[0], item[1]))
+
+ evidence_backed = [item for item in scored if item[0] > 0]
+ directions = []
+ if evidence_backed:
+ for score, style, matched_terms in evidence_backed[:3]:
+ rule = STYLE_RULES[style]
+ directions.append({
+ "style_seed": style,
+ "evidence_score": score,
+ "matched_terms": matched_terms,
+ "why_it_fits": rule["why"],
+ "project_native_motif": rule["motif"],
+ "primary_proof": rule["proof"],
+ "construction_mode": "proof-composite" if has_visual_proof else "deterministic-svg",
+ "decision_status": "candidate; verify against repository facts before production",
+ })
+
+ gate_status = "ready" if len(directions) == 3 else "blocked"
+ gate_reason = (
+ "Repository evidence supports three candidate directions."
+ if gate_status == "ready"
+ else "Fewer than three evidence-backed directions were found. Add or identify a README, manifest description, public interface, workflow, or real visual proof before selecting a direction."
+ )
+ return {
+ "repository": data.get("root"),
+ "project_name": data.get("project_name"),
+ "direction_gate": {"status": gate_status, "reason": gate_reason},
+ "project_brief": {
+ "audience": "verify from current docs, package metadata, and user evidence",
+ "one_sentence_value": "derive from confirmed behavior, not the repository tagline alone",
+ "primary_proof": "select the strongest real output before designing the hero",
+ "first_successful_action": "identify the shortest verified path to a useful result",
+ "native_visual_material": data.get("existing_visual_assets", [])[:12],
+ },
+ "directions": directions,
+ "selection_rule": "Choose the direction with the strongest repository-specific motif and proof, not the highest score alone.",
+ }
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("repository", nargs="?", default=".")
+ parser.add_argument("--output")
+ args = parser.parse_args()
+
+ root = Path(args.repository).expanduser().resolve()
+ if not root.is_dir():
+ parser.error(f"repository is not a directory: {root}")
+ payload = recommend(inventory(root))
+ rendered = json.dumps(payload, indent=2, ensure_ascii=False) + "\n"
+ if args.output:
+ output = Path(args.output).expanduser().resolve()
+ output.parent.mkdir(parents=True, exist_ok=True)
+ output.write_text(rendered, encoding="utf-8")
+ else:
+ print(rendered, end="")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/skills/refine-readme/scripts/render_cover.py b/skills/refine-readme/scripts/render_cover.py
index c7745ea..2aa0a03 100644
--- a/skills/refine-readme/scripts/render_cover.py
+++ b/skills/refine-readme/scripts/render_cover.py
@@ -5,6 +5,7 @@
import argparse
import json
+import re
import textwrap
from html import escape
from pathlib import Path
@@ -12,17 +13,33 @@
HERE = Path(__file__).resolve().parent
PRESETS_PATH = HERE.parent / "assets" / "styles" / "presets.json"
+SAFE_COLOR = re.compile(
+ r"^(?:#[0-9a-fA-F]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|oklch)\([0-9.,% /+-]+\))$"
+)
def load_presets() -> dict:
return json.loads(PRESETS_PATH.read_text(encoding="utf-8"))
-def motif(style: str, accent: str, support: str, foreground: str) -> str:
+def validate_color(value: str) -> str:
+ if not SAFE_COLOR.fullmatch(value):
+ raise ValueError("accent must be a plain CSS color value")
+ return value
+
+
+def motif(
+ style: str,
+ accent: str,
+ support: str,
+ foreground: str,
+ proof_label: str,
+) -> str:
+ safe_proof_label = escape(textwrap.shorten(proof_label, width=42, placeholder="…"))
if style == "protocol-grid":
return f'''
- POST /v1/tasks
+ {safe_proof_label}REQUEST
@@ -31,7 +48,7 @@ def motif(style: str, accent: str, support: str, foreground: str) -> str:
OUTPUT
- truth → design → proof → check
+ REQUEST → TASK → VERIFIED OUTPUT'''
if style == "product-proof":
return f'''
@@ -74,7 +91,15 @@ def motif(style: str, accent: str, support: str, foreground: str) -> str:
'''
-def render(style_id: str, title: str, tagline: str, accent_override: str | None = None) -> str:
+def render(
+ style_id: str,
+ title: str,
+ tagline: str,
+ accent_override: str | None = None,
+ eyebrow: str | None = None,
+ badge: str | None = None,
+ proof_label: str = "PROJECT PROOF",
+) -> str:
presets = load_presets()
if style_id not in presets:
raise KeyError(f"unknown style: {style_id}; choose from {', '.join(sorted(presets))}")
@@ -82,12 +107,28 @@ def render(style_id: str, title: str, tagline: str, accent_override: str | None
width, height = (int(value) for value in preset["canvas"].split("x"))
background = preset["background"]
foreground = preset["foreground"]
- accent = accent_override or preset["accent"]
+ accent = validate_color(accent_override) if accent_override else preset["accent"]
support = preset["support"]
safe_title = escape(title)
safe_tagline = escape(textwrap.shorten(tagline, width=78, placeholder="…"))
- style_name = escape(preset["name"])
+ safe_eyebrow = escape(textwrap.shorten(eyebrow, width=48, placeholder="…")) if eyebrow else ""
+ raw_badge = textwrap.shorten(badge, width=24, placeholder="…") if badge else ""
+ safe_badge = escape(raw_badge)
font_size = 76 if len(title) <= 18 else 60 if len(title) <= 30 else 48
+ eyebrow_svg = (
+ f'{safe_eyebrow}'
+ if safe_eyebrow
+ else ""
+ )
+ badge_width = min(260, max(150, 36 + len(raw_badge) * 9))
+ badge_text_x = 72 + badge_width / 2
+ badge_svg = (
+ f'''
+ {safe_badge}'''
+ if safe_badge
+ else ""
+ )
return f''''''
@@ -119,6 +159,9 @@ def main() -> int:
parser.add_argument("--title")
parser.add_argument("--tagline", default="A clear, polished, GitHub-ready README")
parser.add_argument("--accent", help="Optional CSS color override")
+ parser.add_argument("--eyebrow", help="Optional project-owned category or product label")
+ parser.add_argument("--badge", help="Optional short verified callout; omitted by default")
+ parser.add_argument("--proof-label", default="PROJECT PROOF", help="Short project-native label used by the motif")
parser.add_argument("--output")
parser.add_argument("--list-styles", action="store_true")
args = parser.parse_args()
@@ -130,8 +173,16 @@ def main() -> int:
if not args.style or not args.title or not args.output:
parser.error("--style, --title, and --output are required unless --list-styles is used")
try:
- svg = render(args.style, args.title, args.tagline, args.accent)
- except KeyError as error:
+ svg = render(
+ args.style,
+ args.title,
+ args.tagline,
+ args.accent,
+ args.eyebrow,
+ args.badge,
+ args.proof_label,
+ )
+ except (KeyError, ValueError) as error:
parser.error(str(error))
output = Path(args.output).expanduser().resolve()
output.parent.mkdir(parents=True, exist_ok=True)
diff --git a/skills/refine-readme/scripts/validate_skill.py b/skills/refine-readme/scripts/validate_skill.py
index 9c62067..d066aad 100644
--- a/skills/refine-readme/scripts/validate_skill.py
+++ b/skills/refine-readme/scripts/validate_skill.py
@@ -14,6 +14,7 @@
"agents/openai.yaml",
"references/five-layer-workflow.md",
"references/cover-system.md",
+ "references/project-native-directions.md",
"references/style-catalog.md",
"references/fact-check.md",
"references/github-rendering.md",
diff --git a/tests/test_scripts.py b/tests/test_scripts.py
index 2457cdd..ef2df6d 100644
--- a/tests/test_scripts.py
+++ b/tests/test_scripts.py
@@ -27,6 +27,32 @@ def test_all_cover_presets_render_exact_title(self):
self.assertIn("Exact Project", svg)
self.assertIn('role="img"', svg)
self.assertNotIn("{{", svg)
+ self.assertNotIn("README REFINER", svg)
+ self.assertNotIn("BEAUTIFUL · TRUE", svg)
+
+ def test_cover_uses_only_explicit_project_labels(self):
+ renderer = load("render_cover")
+ svg = renderer.render(
+ "protocol-grid",
+ "BeatAPI for Dify",
+ "Create and monitor async video tasks",
+ eyebrow="DIFY PLUGIN",
+ badge="2 VERIFIED TOOLS",
+ proof_label="CREATE → POLL",
+ )
+ self.assertIn("DIFY PLUGIN", svg)
+ self.assertIn("2 VERIFIED TOOLS", svg)
+ self.assertIn("CREATE → POLL", svg)
+
+ def test_cover_rejects_markup_in_color_override(self):
+ renderer = load("render_cover")
+ with self.assertRaises(ValueError):
+ renderer.render(
+ "protocol-grid",
+ "Demo",
+ "Verified tagline",
+ accent_override='red" onload="alert(1)',
+ )
def test_inspector_reads_package_scripts(self):
inspector = load("inspect_repository")
@@ -39,6 +65,63 @@ def test_inspector_reads_package_scripts(self):
result = inspector.inventory(root)
self.assertEqual(result["project_name"], "demo")
self.assertEqual(result["manifests"]["package.json"]["scripts"], ["test"])
+ self.assertEqual(result["readme_excerpt"], "# Demo")
+
+ def test_inspector_reads_pyproject_and_private_asset_folder(self):
+ inspector = load("inspect_repository")
+ with tempfile.TemporaryDirectory() as directory:
+ root = Path(directory)
+ (root / "README.md").write_text("# Python Demo\n", encoding="utf-8")
+ (root / "pyproject.toml").write_text(
+ '[project]\nname = "python-demo"\ndescription = "A useful CLI"\n'
+ 'requires-python = ">=3.11"\n\n[project.scripts]\ndemo = "demo:main"\n',
+ encoding="utf-8",
+ )
+ (root / "_assets").mkdir()
+ (root / "_assets" / "preview.webp").write_bytes(b"RIFF")
+ result = inspector.inventory(root)
+ self.assertEqual(result["project_name"], "python-demo")
+ self.assertEqual(result["manifests"]["pyproject.toml"]["scripts"], ["demo"])
+ self.assertEqual(result["existing_visual_assets"], ["_assets/preview.webp"])
+
+ def test_direction_planner_returns_three_evidence_led_candidates(self):
+ planner = load("plan_directions")
+ result = planner.recommend({
+ "root": "/tmp/beatapi-dify-plugin",
+ "project_name": "beatapi-dify-plugin",
+ "headings": [{"text": "Dify plugin for an asynchronous API"}],
+ "manifests": {},
+ "existing_visual_assets": ["assets/demo-output.webp"],
+ })
+ self.assertEqual(result["direction_gate"]["status"], "ready")
+ self.assertEqual(len(result["directions"]), 3)
+ self.assertEqual(result["directions"][0]["style_seed"], "integration-bridge")
+ self.assertEqual(result["directions"][0]["construction_mode"], "proof-composite")
+
+ def test_direction_planner_blocks_when_repository_has_no_style_evidence(self):
+ planner = load("plan_directions")
+ result = planner.recommend({
+ "root": "/tmp/unknown",
+ "project_name": "unknown",
+ "headings": [],
+ "manifests": {},
+ "existing_visual_assets": [],
+ })
+ self.assertEqual(result["direction_gate"]["status"], "blocked")
+ self.assertEqual(result["directions"], [])
+
+ def test_direction_planner_does_not_pad_with_zero_evidence_styles(self):
+ planner = load("plan_directions")
+ result = planner.recommend({
+ "root": "/tmp/api-only",
+ "project_name": "api-only",
+ "headings": [],
+ "manifests": {},
+ "existing_visual_assets": [],
+ })
+ self.assertEqual(result["direction_gate"]["status"], "blocked")
+ self.assertEqual(len(result["directions"]), 1)
+ self.assertTrue(all(item["evidence_score"] > 0 for item in result["directions"]))
def test_checker_reports_missing_local_path(self):
checker = load("check_readme")