marketing-machine

agent
Guvenlik Denetimi
Basarisiz
Health Uyari
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 8 GitHub stars
Code Basarisiz
  • process.env — Environment variable access in lib/browser.mjs
  • network request — Outbound network request in lib/browser.mjs
  • exec() — Shell command execution in lib/detect.mjs
  • exec() — Shell command execution in lib/html.mjs
  • exec() — Shell command execution in lib/update.mjs
  • network request — Outbound network request in lib/update.mjs
  • network request — Outbound network request in marketing.config.example.json
Permissions Gecti
  • Permissions — No dangerous permissions requested

Bu listing icin henuz AI raporu yok.

SUMMARY

Claim-provenance and brand-compliance harness for marketing content: scans your codebase for product truth, lints copy against it, renders on-brand cards and video. Installs as a Claude Code plugin/skill, Cursor rule, or AGENTS.md section.

README.md

marketing-machine

A claim-provenance and brand-compliance harness for marketing content. It reads your codebase
to establish what the product actually does, enforces the rules mechanically, then renders cards
(PNG) and video (MP4) from a single HTML contract.

The pitch: your marketing copy cannot outrun what your product actually does, and the build checks.

test
license: MIT
node
runtime dependencies

Built for teams that already work with a coding agent — Claude Code, Cursor, or anything that reads
AGENTS.md — and want the marketing it produces to survive an audit.

Requirements: Node 20+ (record needs 22+ for the global WebSocket) and a Chrome/Edge/Chromium
found at runtime. ffmpeg-static is an optional dependency, used by video only — linting, scanning
and card rendering have zero dependencies.


Quick start

git clone https://github.com/cagatayuncu/marketing-machine ../marketing-machine
cd your-repo
node ../marketing-machine/scripts/init.mjs --agent claude   # or cursor, agents, all

Then, in your agent: setup — the skill completes its own configuration, installs the fonts
the renderer needs, proposes the brand-role mapping, and asks you only for the things a codebase
cannot answer (your one-liner, your stage, which claims you will defend).

From there you talk to it:

You say It does
setup completes config, fonts and brand roles; offers the CI gate
"write a post about inventory turnover" drafts to the anatomy, adds the mandatory verification table, lints
"make a card for post 12" picks a format (default: no visual), renders a PNG in your brand, writes alt text
"render the intro video" verifies a single frame first, then records the MP4
"review this draft" runs the linter, then the judgment passes a linter cannot do

The skill lands wherever your agent reads instructions — .claude/skills/ for Claude Code,
.cursor/rules/ for Cursor, an AGENTS.md section for everything else. Per-agent detail:
Installing it as an agent skill.

Other ways to get the toolchain:

  • Claude Code plugin — no clone, no paths: /plugin marketplace add cagatayuncu/marketing-machine, then /plugin install marketing-machine. The toolchain travels inside the plugin.
  • skills CLInpx skills add cagatayuncu/marketing-machine; the skill clones the toolchain itself on first use.
  • Vendor it — copy the directory into tools/marketing-machine/. Best for teams: the installed path stays relative.
  • Submodulegit submodule add https://github.com/cagatayuncu/marketing-machine tools/marketing-machine to pin a version and pull updates.
  • Fork it — add your own rule packs (rules/lang.<code>.json) and card templates on top.

Prefer driving it by hand? Every step is a plain command:

node <marketing-machine>/scripts/doctor.mjs    # environment and config
node <marketing-machine>/scripts/scan.mjs      # product-facts draft
node <marketing-machine>/scripts/lint.mjs      # enforce
node <marketing-machine>/scripts/render-card.mjs <name>
node <marketing-machine>/scripts/record.mjs <name>

npm link inside this directory gives you mkt as a shorthand for all of them.

What init derives from your codebase

Value Source
Product name package.json name, or the .sln/.slnx name
Domain homepage, or the first external link in the README
Brand colors The stylesheet declaring the most of them — CSS custom properties, SCSS/Less variables, or a Tailwind theme. Prefixed token conventions (--color-bg, --c-primary, --theme-text) are understood
Font families font-family declarations, matched to the packages that ship them — including a Google Fonts @import, which names the equivalent @fontsource package
Languages The localisation directory, with a key count per language
Legal routes Routes like /privacy or /terms found in the code — these become the linter's draft-link blocklist
Source roots Directories that actually contain code

Anything it cannot find is left as [VERIFY: ...]. doctor lists those, and unmapped brand roles
block rendering rather than falling back to someone else's palette.


Why

There is no shortage of tools that write marketing copy. What is missing is the layer underneath:
something that proves the copy stayed inside what the product actually does. That layer is the
whole of this package.

Four principles:

  1. Everything comes from the codebase it is installed into. Brand colors, font families, product
    name, domain, languages, legal-page routes, source roots — all detected from the host repo. No
    product's colors, name or fonts are baked into the package, and no value is inherited from the
    example config. When detection fails the field stays null with a [VERIFY] note and
    rendering is refused — publishing artwork in the wrong brand is worse than failing.
  2. Product truth is derived from code and tiered by evidence. WORKS / PARTIAL / ABSENT / LIMITS,
    every line carrying file:line.
  3. Nothing becomes claimable automatically. A scanner sees that a symbol exists; it cannot see
    that the feature works. The draft waits for a human.
  4. A rule is a build gate, not advice. Everything a machine can decide lives in the linter;
    everything requiring judgment lives in the skill. The two are never mixed.

Adoption walkthrough

Two commands and a conversation. The friction is concentrated in one place, and it is deliberate.

1 · Get the directory

git clone https://github.com/cagatayuncu/marketing-machine ../marketing-machine

It does not have to live inside your repo — init writes an absolute path into the installed skill,
so the toolchain can sit anywhere. Vendoring it in (tools/marketing-machine/) also works if you
would rather pin a version.

Working in a team? Vendor it. An absolute path baked into a committed AGENTS.md or .claude/
skill runs on exactly one machine; a vendored toolchain keeps the path relative and the repo
self-contained. The alternative is each developer running init --refresh once after cloning —
init defaults to that model when the toolchain sits outside the repo: the machine-specific skill
copies go into .gitignore and stay out of everyone's commits.

2 · Install and hand over

cd your-repo
node ../marketing-machine/scripts/init.mjs --agent claude

init also maintains a marked block in your .gitignore covering everything the toolchain
writes and can rewrite — rendered PNGs and MP4s, the scan draft, the _template-* starter files,
and (when the toolchain path is absolute) the machine-specific skill copies. Content stays in git:
config, context files, posts, card and scene HTML. That split is the collaboration model — a
render is reproducible from the committed content, while the committed content is exactly what
your team reviewed and your CI gate reads. Paths you already ignore in your own spelling are left
alone, and --no-gitignore skips the whole step.

Then tell your agent: setup. From here the skill drives, and it will ask you for exactly four
kinds of thing:

It asks Why it cannot answer this itself
Which language the copy is written in It can read your locale files, but a product's interface languages and its marketing language are different decisions. A UI in three languages may market in one.
Who the audience is Their words for the problem, what they already tried, the first objection, and who this is explicitly not for. None of that is in the code.
Which channel this is for Sets card and video geometry, and the post anatomy
Your one-line product description Positioning is a judgment, not a fact in the code
Your stage (pre-launch / beta / GA) Same
A sanity check on the brand-role mapping Role guessing is name-based and can be confidently wrong
A .woff2 path, if your typeface is licensed Not resolvable from a public registry

The language answer is not a single field: the rule pack, the verification-table heading and the
verify marker all move with it, which is why changing it goes through
init --refresh --lang <code> rather than a hand edit. Anything you had already tuned survives that.
Audience answers go into marketing/context/audience.md, because an audience description is prose,
not configuration.

Everything else it handles: running doctor, installing the font package the renderer needs, writing
the role mapping, detecting a logo file if the repo ships one (brand.logo — cards then use the
image instead of the text wordmark), and offering to wire the CI gate.

3 · Establish product truth — the part that is not automated

Ask the skill to scan. It writes a draft where every line carries a file:line reference and
nothing is claimable yet, then walks it with you section by section asking "does this work end to
end, and what is the evidence?" It will not promote a line on its own.

Pay particular attention to the outbound-call inventory. If you intend to say anything about where
data goes, every row there has to be reviewed — one unreviewed call falsifies an absolute claim.

The same conversation fills claims.md. Its UNPROVABLE rows get written as rules in
marketing/rules/project.json, which is the moment they stop being advice and start failing builds.

4 · Produce

Ask for write, card, video, or review. Every output goes through the linter before you see it.

Everything written comes from your repository. Not from what products in the category usually
claim, not from your README's own marketing language. The chain is: the scan found it in the code → a
human confirmed it → it is WORKS in product-facts.md → it may appear in a sentence → the sentence
carries its file:line. Anything that cannot be traced back through that chain either gets a verify
marker or gets left out.

Time to first artwork

Steps 1, 2 and 4 are minutes. Step 3 is the real investment, and it is deliberately not
automatable: establishing what your product provably does, and which claims you will stand behind, is
the thing that makes everything downstream trustworthy. A scanner can hand you the candidates; only
you can confirm them.


Installing it as an agent skill

The instructions live in one place — skills/marketing-machine/SKILL.md — and init wraps that same body in whatever
shape your agent expects. Rewriting the body per target would guarantee the copies drift, so the
body is installed verbatim and only the wrapper differs.

node <marketing-machine>/scripts/init.mjs --agent claude    # default
node <marketing-machine>/scripts/init.mjs --agent cursor
node <marketing-machine>/scripts/init.mjs --agent agents
node <marketing-machine>/scripts/init.mjs --agent all
node <marketing-machine>/scripts/init.mjs --agent claude,cursor

Re-running init --refresh reinstalls every target with current paths — that is also the fix when
a teammate clones a repo whose skill was installed from a different machine.

Claude Code — as a plugin (no clone)

/plugin marketplace add cagatayuncu/marketing-machine
/plugin install marketing-machine

The plugin carries the whole toolchain — scripts, rules, templates — inside the plugin directory,
and the skill finds it through $CLAUDE_PLUGIN_ROOT. Nothing to clone, no path to manage: open
your repo, say setup, and the skill creates marketing.config.json there. Updates arrive
through the plugin marketplace rather than git pull.

npx skills CLI

npx skills add cagatayuncu/marketing-machine

The skills CLI copies the skill into whichever agents it
manages — but only the skill, not the toolchain. The skill handles that itself: its first step on a
machine without a toolchain is to clone one (the resolution ladder is written into the skill). If
you would rather no agent cloned repos on your behalf, use any other route on this page.

Claude Code — via init

<your-repo>/.claude/skills/marketing-machine/
├── SKILL.md          name + description frontmatter, tool allowlist
├── TOOLCHAIN         where the scripts live
└── references/
    └── video-guide.md

Claude Code discovers project skills in .claude/skills/ automatically — no registration step, no
settings change, and it works the same in the CLI, the desktop app and the IDE extensions. The
whole skills/marketing-machine/ directory travels, so the video guide's relative link resolves locally.

Invoke it by name:

/marketing-machine setup
/marketing-machine write post about inventory turnover
/marketing-machine card 12

or just describe the task — routing matches on the description frontmatter, so "write a LinkedIn
post about X", "make a card for this" or "review this copy" reach the skill without naming it.

One note on permissions: the frontmatter's allowed-tools (Read, Write, Edit, Glob, Grep, Bash) is
a ceiling, not a grant. Claude Code still asks before running toolchain commands unless you
allowlist them in your project settings.

Cursor

<your-repo>/.cursor/rules/marketing-machine.mdc

Installed as a project rule of the agent-requested kind — description in the frontmatter plus
alwaysApply: false — so the agent pulls it in when the task looks like marketing work instead of
carrying it in every request. Verify it landed: Cursor Settings → Rules lists it.

To use it, open the agent panel and describe the task, or reference the rule explicitly
(@marketing-machine) when you want it loaded for certain. The rule tells the agent to run the
toolchain scripts from the repo root; Cursor shows each command for approval as usual.

Recent Cursor versions also read AGENTS.md, so --agent agents reaches Cursor too. Pick one
route, not both, or the same instructions land in context twice.

AGENTS.md (portable convention)

<your-repo>/AGENTS.md

The instructions are appended as a ## Marketing content (marketing-machine) section between
<!-- marketing-machine:start --> / :end markers. Re-running init replaces what sits between
the markers and leaves the rest of your AGENTS.md alone.

This is the widest net: OpenAI Codex, GitHub Copilot's coding agent, Google Jules, Amp, Zed and a
growing list of tools read AGENTS.md as project instructions. The trade-off is the loading model.
An AGENTS.md section sits in every prompt, while a Claude skill or a Cursor rule loads on demand —
so if your AGENTS.md is already long, prefer the tool's own rules mechanism where one exists.

Everything else — manual paste

skills/marketing-machine/SKILL.md is plain Markdown with YAML frontmatter. Strip the frontmatter, replace
<TOOLCHAIN> with the path to this directory, and paste the body wherever your tool reads project
instructions — that substitution is the only templating in the file. As of this writing:

Tool Where it reads project instructions
GitHub Copilot (VS Code / JetBrains chat) .github/copilot-instructions.md
Windsurf .windsurf/rules/ — save the body as a rule with a description trigger
Cline / Roo Code .clinerules
Gemini CLI GEMINI.md
Anything with a custom-instructions field paste the body directly

A first-class wrapper for a tool you use daily is a small, welcome PR — the cursor branch in
scripts/init.mjs is the pattern to copy.

What the skill actually does

Six commands, in the order they get used:

Ask The skill does
setup Installs itself, completes the config, installs the fonts the renderer needs, proposes the brand-role mapping, offers the CI gate — and asks you only for what code cannot answer
scan Runs the scanner, then walks the draft with you asking "does this actually work, and what is the evidence" — it will not promote a line on its own
write <channel> <topic> Loads the context files, writes to the anatomy, saves with a mandatory verification table, runs the linter, fixes violations
card <post> Applies the decision table (default: no visual), builds from the template, renders, writes alt text
video <scene> Checks a single frame first, then records; enforces the purity rules and the provenance declaration
review <file> Runs the linter, then adds the four passes a linter cannot do: claim verification, limitation quality, audience, CTA fit

Commands

Command What it does
init Detect, write config and scaffolding, install the skill
scan Scan the codebase, write a product-facts draft
lint Check posts, cards and scenes against the rules
card HTML → PNG
record HTML scene → MP4 (or a single frame with --shot)
doctor Browser, fonts, brand roles, ffmpeg, paths — plus freshness: is the installed skill copy in sync, is the toolchain behind upstream (--offline skips the network check)

Useful flags: init --refresh (re-derive brand/fonts and reinstall every agent target found on
disk, keep the rest) · init --no-gitignore (leave the host .gitignore alone) ·
lint --only post|card|video · lint --warnings-as-errors (for CI) · lint --json ·
card --channel <name> · record --shot 6.5 · record --scale 2. Note on card --loose-fonts:
it only permits a source whose <!--FONTS--> marker is absent — it does not skip font
resolution when the marker is present.


One HTML contract

Cards and video go through the same pipeline. The only difference is that a scene declares a
timeline.

Marker What it does
<!--FONTS--> Required. Fonts are inlined here as data URIs. Without it the page fetches fonts over the network and non-ASCII glyphs (ı ş ğ İ, ä ö ü ß, ą ę ł) break silently. The linter blocks its absence.
<!--TOKENS--> Injects brand colors and font families from config
{{product.name}}, {{brand.footerUrl}}, … Identity text from config. An unresolved placeholder fails the render.
body { width/height } Output geometry. Change the aspect ratio in the source and the renderer follows.

A scene adds four globals:

window.__duration = 15;                // total seconds
window.__seek = (t) => { ... };        // a PURE function of t
window.__ready = true;                 // once fonts and layout have settled
window.__provenance = 'simulation';    // real-recording | driven-app | simulation

__seek must be pure. The recorder does not play the page and capture the screen; for every
frame it calls __seek(frame/fps) and screenshots. So: no CSS transition/animation, nothing
driving visuals from requestAnimationFrame, no state accumulating between calls.

The cost is more code. What it buys: the output of --shot 6.5 is byte-for-byte frame 195 at 30fps,
so you verify a frame instead of reviewing a video. Full contract:
skills/marketing-machine/references/video-guide.md.


Why a browser render, not an image model

Every card and every video frame is a screenshot of a page you can read. That is a deliberate
trade, and it buys four things a generative image or video model does not offer:

  • Type and identity survive. The text on a card is text: your fonts inlined as data URIs,
    your brand tokens injected from config. There is no diffusion pass to warp a glyph or melt a
    wordmark — which is where generated imagery still fails most visibly, and exactly what a brand
    asset cannot afford.
  • Output is deterministic. The same source renders the same pixels. A video frame at a given
    second is exactly the frame the encoder saw, so one still can be reviewed instead of a whole
    video re-watched, and an approval survives a re-render.
  • Revision is an edit, not a re-roll. No per-asset inference cost, no prompt roulette, no
    almost-right variant you settle for. Change a line, render again; nothing else moves.
  • The output does not read machine-made. On the visual side there are none of the artifacts
    that flag AI imagery. On the copy side the linter treats the tells of generated prose — the
    in-sentence em dash, the not-only-but-also formula, the rule-of-three tic, hype vocabulary —
    as violations, so what ships reads like it was written, because what survives the gate
    effectively was.

The cost of the trade is honest too: a browser render cannot invent a photograph, an
illustration or a texture. When a post genuinely needs one, that asset comes from somewhere
else — this pipeline covers cards, product scenes and motion design, not photography.

The brand role contract

Templates style against six canonical roles: bg, bgDeep, fg, accent, accentSoft, muted.
config.brand.roles binds each one to a hex value or to $name referencing an entry in
config.brand.colors.

Without that indirection a template has to hardcode one project's color names (--navy), and a
project that calls the same color --surface falls silently through to the fallbacks: the artwork
looks fine and is in the wrong brand.

So the templates contain no hex value and no font name, and no fallbacks. Font families arrive
the same way (--mm-font-sans / --mm-font-mono) — a template naming a family literally would
inline that font and then never use it, quietly falling back to a system font. init guesses
the roles from the color names, leaves anything uncertain as null, and doctor reports it while
rendering refuses (--allow-fallback to override deliberately).


Rules are data, not code

Edit a rule file and what the build enforces changes; no code changes. A rule is either a regex or a
named builtin.

Three layers, general to specific:

Layer Where What it holds
core packaged (rules/core.json) Structural rules. Not one word of any human language: verification table, unsourced number, hashtag count, card word budget, provenance, leftover placeholder
lang.<code> packaged (rules/lang.en.json, lang.tr.json) That language's vocabulary bans and language-specific mechanics
project rules your repo (config.lint.projectRules) Claim bans specific to this product. init creates it empty

The third layer is why the package can ship at all: "do not claim compliance for feature X" is a
sentence about one product, and baking it in would make every adopter inherit it. The UNPROVABLE rows
of your claims.md get written here by hand — a claim there is advice until it appears here, at
which point the build enforces it.

Language packs are not decoration. Turkish letters have no mathematical-bold counterpart, so a
bolded Turkish word renders half-bold and broken; that is a purely mechanical check no other tool
performs. For the same reason rule patterns avoid \w: it does not match ı ş ğ ç ö ü, so a
\w-based pattern misses exactly the Turkish copy it was written for. Exclusion character classes
([^\s,.;:]+) are the portable form.

What the linter deliberately does not check

Out of scope on purpose: whether the limitation sentence is any good (its presence is checked, its
quality is not), whether a claim maps to a WORKS fact, audience coherence, hook strength, archetype
choice. Those need judgment and stay with the skill. Keep the split: putting a non-mechanical rule
in the linter produces false positives, and a linter people stop trusting enforces nothing.


CI

One command is the gate:

node <marketing-machine>/scripts/lint.mjs --warnings-as-errors

Exit 1 on any violation (and on any warning in strict mode). Wire it to whatever runs on changes
under your marketing/ paths. No workflow file ships with the package, because its shape depends on
your CI.


File layout

lib/
  detect.mjs     everything derived from the host codebase — shared by init and scan so they cannot disagree
  config.mjs     config discovery, validation, path resolution, CLI args
  browser.mjs    the ONE Chromium locator and the ONE CDP client
  fonts.mjs      three-tier font resolution (explicit paths -> host node_modules -> this package's)
  html.mjs       the single HTML contract: markers, geometry, role tokens, {{placeholders}}
scripts/         init · scan · lint · render-card · record · doctor · cli
rules/           core.json · lang.en.json · lang.tr.json
templates/       card (four archetypes in one file) + scene — neither contains a hex or a font name
skills/marketing-machine/           the agent skill + the video guide

Font resolution has three tiers so nothing is pinned to one repo: a project that already ships its
brand fonts points config.fonts.nodeModulesRoot at them and installs nothing here; a fresh project
installs them here (npm i @fontsource-variable/<family>; save it, because npm prunes unsaved
packages on the next install and a second font would silently delete the first). A missing font fails the
build
— shipping a card in a fallback font is worse than not shipping it, because the breakage is
invisible until someone looks closely at published artwork.


Configuration reference

Key Notes
product.name / oneLiner / stage Name is detected; the other two are judgments a human writes
language.primary Inferred from the most complete locale dictionary
language.ruleSets Which packaged rule files load, e.g. ["core", "lang.en"]
paths.* Where context, posts, cards, scenes and output live
brand.colors / colorsSource Raw inventory plus the file it came from
brand.roles The six canonical roles — what templates consume
brand.wordmark / footerUrl Identity text for {{...}} placeholders
fonts.sans / mono / nodeModulesRoot / subsets Families, packages, where to resolve them
channels.<name>.card / video / fps Output geometry per channel
scan.* Include/exclude globs and the patterns for flags, limits and outbound calls
lint.projectRules Path to this product's own claim bans
lint.draftLegalPaths Policy routes a post must not link to yet
lint.requireVerificationTable / verifyMarker Both appear inside content, so they follow the content language
provenance.required / allowed Whether every scene must declare how its footage was made

marketing.config.example.json is a filled-in example for a fictional product, there to show what populated fields look like. init never reads it and inherits nothing from it.


Verified state (2026-08-04)

Run end to end on a scratch repo with no relation to the tool's origin (nimbus: purple theme,
Inter + JetBrains Mono, source under app/, en+de localisation):

Step Result
init --agent all Derived from the codebase: stack, product name nimbus, domain nimbus.example.co, 6 colors (app/ui/theme.css), Inter + JetBrains Mono, both font packages, en+de, source root app/**, legal routes /privacy /terms, content language en. All six brand roles mapped. Skill installed for Claude Code, Cursor and AGENTS.md
doctor All green; fonts resolved from this package's own node_modules (the fresh-repo path)
scan 4 files; 1 outbound call, 2 limits, 6 colors, 2 languages — everything under the verify marker
card 1200×1200 PNG entirely in nimbus branding: purple background, purple accent, wordmark from package.json, footer from homepage, both fonts as detected from CSS
unmapped role Setting accent to null refused the render, with the reason and the fix
lint (English copy) 13 violations + 4 warnings; lang.en vocabulary rules fired and draft-legal-link triggered on the /privacy route detected from the code
lint (Turkish copy) 16 violations + 2 warnings on a separate fixture, exit 1
provenance Declared → clean; declaration deleted → violation
record --shot Three distinct frames, 1080×1080
record 450 frames → 15s @ 30fps MP4
language switch init --refresh --lang tr moved the rule pack lang.enlang.tr, the heading to Doğrulama notları and the marker to [DOĞRULA, while preserving a customised hashtagCount and the hand-written one-liner. The Turkish pack then fired on Turkish copy.
third fixture A React repo using Google Fonts and --color-* tokens with no font package: all six roles mapped, @fontsource/manrope inferred from the @import, card rendered in its own palette

Validated against a real production repo

The synthetic fixtures above prove the happy path. A .NET + React monorepo (891 files, three
interface languages, an oklch() design-token system, existing hand-written marketing content) proved
what the fixtures could not, and it found ten defects the fixtures never would have.

Step Outcome
init Derived stack, product name, 59 colours from the app's own token file, both font families and their packages, three languages, source roots, four legal routes. Correctly reported the domain as not found — the repo's SITE_URL falls back to localhost and the real host arrives from a build-time env var, so it genuinely is not a code fact
brand roles Five of six mapped from --c-* tokens; one left null. A palette-based proposal for the sixth was accepted, and the card then rendered in the product's actual light theme
scan 601 files → 51 outbound calls, 60 limits, 3 languages, all with file:line. After the fixes the egress inventory is real product code (OAuth clients, LLM provider, telemetry) rather than boilerplate
write + lint A post whose only factual claim was a constant the scan found (PeekRowLimit = 50) passed with 0 violations, one warning for missing hashtags, and one note holding publication open because a verify marker was unresolved — exactly the intended behaviour
existing content product-facts.md, claims.md and brand-voice.md were already written by hand in that repo. Adoption left them untouched

Real defects found and fixed during that testing:

  1. --only <value> was being mistaken for a filename
  2. \w does not match Turkish letters — the rule-of-three check never fired at all
  3. %94. failed to match the verification table because of the trailing period
  4. A template's doc comment spelled out <!--FONTS-->, which closed the HTML comment early and
    printed the remaining doc text onto the card
  5. Windows holds locks on the Chrome profile; cleanup was crashing the render
  6. Templates hardcoded one project's color names, so another repo fell silently through to the
    fallbacks — this is why the role contract exists
  7. The source-root threshold (3 files) missed small repos and fell back to a src/** that did not
    exist
  8. Templates hardcoded font family names, so a detected font was inlined and then never used
    fixed by making families role variables too
  9. The leftover-placeholder rule flagged config-bound placeholders as unfilled slots, so a correctly
    rendered card failed the lint — it now resolves against config exactly as the renderer does
  10. Role guessing did not understand prefixed token names (--color-bg, --c-primary), which is the
    most common convention in practice — three of six roles came back unmapped on a realistic repo,
    meaning cards could not render at all
  11. doctor truncated failures to their first line, throwing away the fix instruction underneath —
    the adopter saw "cannot resolve font package" without the npm i command that solves it

Found only by running against a real repo:

  1. --force destroyed hand-written context files. It replaced 567 lines of verified product
    facts, decided claims and a tuned voice with empty scaffolds, silently. Git had them, so nothing
    was lost — but the most expensive content in a repo was one mistyped flag away from gone.
    --force now covers config, templates and the skill only; replacing hand-written content needs
    --force-context, and every kept file is named in the report
  2. The locale search took the shallowest match, picking i18n over i18n/locales and finding zero
    languages in a repo that ships three
  3. Only hex colours were recognised. Against an oklch() token system that yielded five stray
    #ffffff values, mapped every role to white, and would have rendered a white-on-white card
  4. Colour ranking picked a marketing brochure stylesheet over the application's own theme, because it
    happened to declare more variables
  5. Source roots included test, tooling and design directories, so the outbound-call inventory — the
    section a data-locality claim depends on — was dominated by exported design-tool boilerplate
  6. Variable-font family names never matched their package: @fontsource-variable/* declares its CSS
    family as " Variable" while the package is named after the plain family
  7. An unresolved [VERIFY] package name was interpolated into an npm i command, producing an
    instruction that could not possibly work
  8. A [VERIFY] value rendered onto the card as literal text. It is a non-null string, so it
    counted as a value; a placeholder reaching published artwork is the exact failure this layer
    exists to prevent
  9. Domain detection ignored site-URL constants (SITE_URL, metadataBase), which is where a web
    product's canonical host actually lives
  10. Comment lines matched the egress and limit patterns, filling the draft with prose a reviewer has
    to reject line by line

Known gaps

Not done Why
No claims.md → rule automation Claim wording is product-specific and gets written into the project rules by hand. Next step.
Copy writing is not in code Writing needs judgment; skills/marketing-machine/SKILL.md runs it, not a script
No Remotion / React-video pipeline The package stops at the plain HTML contract; a project that wants Remotion adds it and reuses the same linter
The verification tier is not implemented Executing what a scene shows is product-specific. The contract is in the video guide; the adapter is yours to write.
No CI workflow file Depends on your CI. lint --warnings-as-errors is the only command needed.
The human-confirmation conversation is untested scan produces the draft and the skill knows to walk it, but nobody has yet sat through the confirm-each-fact pass on a real product. Until then product-facts.md stays empty and no claim is publishable — which is the correct failure, just not a finished workflow.
Video untested on a real product UI Frames and a full MP4 render from the template scene. Reconstructing an actual product interface as a t-pure scene has not been attempted.
Only en and tr rule packs exist A third language gets structural enforcement only until someone writes rules/lang.<code>.json. The skill says so rather than pretending otherwise.

Tests

npm test

node:test only, no dependencies, no browser needed. The suite covers the detection heuristics
(locale layouts, role guessing from prefixed tokens, source-root ranking), the HTML contract
(role/placeholder resolution and its refusal paths) and the CLI edges that would silently weaken
the gate (--only typos, glob overmatch, malformed project rules) — each of the latter found by
running against real repos first, then pinned by a test.

License

MIT.

Yorumlar (0)

Sonuc bulunamadi