brand-intent 0.1.0

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 ADDED
@@ -0,0 +1,67 @@
1
+ # Brand Intent
2
+
3
+ An open file format specification for making brand intent machine-readable.
4
+
5
+ Five layers. One brand. Any agent.
6
+
7
+ ```
8
+ Identity → Brand → Format → Purpose → Composition
9
+ ```
10
+
11
+ ## What this is
12
+
13
+ Design tokens capture decisions. Brand guidelines capture rules. Neither captures *intent*: the reasoning, the derived expression conditions, the signal-level decisions that make a brand specific.
14
+
15
+ Brand Intent is a set of five composable file types that encode brand knowledge from strategy to composition, in a format any AI agent can read and respect.
16
+
17
+ | Layer | File | Question |
18
+ |-------|------|----------|
19
+ | Identity | `.identity` | Who is this brand? |
20
+ | Brand | `.brand` | What expression intent does it derive? |
21
+ | Format | `.format` | Where does this content appear? |
22
+ | Purpose | `.purpose` | What is this content trying to do? |
23
+ | Composition | `.composition` | How are elements composed? |
24
+
25
+ ## Get started
26
+
27
+ ### 1. Install the VS Code extension
28
+
29
+ Syntax highlighting, autocomplete, and validation for all five file types.
30
+
31
+ ```
32
+ ext install stoll.brand-intent-lang
33
+ ```
34
+
35
+ ### 2. Install skills for your AI agent
36
+
37
+ ```bash
38
+ npx brand-intent install-skills
39
+ ```
40
+
41
+ Works with Claude Code, Cursor, Gemini CLI, VS Code Copilot, Codex CLI, and any MCP-compatible agent.
42
+
43
+ ### 3. Author your brand
44
+
45
+ Start with `.identity` (strategy, voice, audiences), then derive `.brand` (expression intent). Add formats, purposes, and compositions as needed. The Krume bakery brand in `brands/krume/` is a fully commented reference implementation.
46
+
47
+ ```bash
48
+ npx brand-intent init --from krume
49
+ ```
50
+
51
+ ## Documentation
52
+
53
+ - [Specification](SPEC.md): formal grammar for all five file types
54
+ - [Design Decisions](DESIGN.md): reasoning behind the framework
55
+ - [intent.stoll.studio](https://intent.stoll.studio): project landing page
56
+
57
+ ## Relationship to Impeccable
58
+
59
+ Brand Intent is complementary to [Impeccable](https://impeccable.style). Where Impeccable teaches agents *how to design well* (general craft), Brand Intent teaches them *what this brand's expression intent is* (specific identity). Together: execution quality and brand specificity.
60
+
61
+ ## License
62
+
63
+ MIT
64
+
65
+ ---
66
+
67
+ By [Christophe Stoll / Studio Stoll](https://stoll.studio)
@@ -0,0 +1,135 @@
1
+ # krume.brand - expression intent derived from krume.identity
2
+ # Every decision here should be traceable to a statement in the identity file.
3
+ # No strategic reasoning. No audience definitions. No prose.
4
+
5
+ id: krume
6
+ name: Krume
7
+ language: de
8
+ locale: de-DE
9
+
10
+ # ── Color Primitives ──
11
+ # Derived from the bakery's material world: dark roasted crust,
12
+ # warm wood, flour dust, rye. No corporate blue, no lifestyle pastels.
13
+
14
+ brand-colors
15
+ primary: #2C1810 # dark roast - the color of a well-baked crust
16
+ secondary: #5C3A28 # walnut - warm wood of the shop counter
17
+ accent1: #C8A47E # crust - golden, used for dividers and highlights
18
+ accent2: #8B4513 # rye - deep warm brown for CTAs
19
+ white: #FAF6F0 # flour - not pure white, slightly warm
20
+
21
+ # ── Themes ──
22
+ # Laden (shop): light background, dark text. The everyday.
23
+ # Tafel (chalkboard): dark background, light text. Announcements, specials.
24
+
25
+ theme Laden
26
+ background: $white
27
+ text-primary: $primary
28
+ text-secondary: $secondary
29
+ text-tertiary: $accent1
30
+ logo: $primary
31
+ cta: $accent2
32
+ label-bg: $accent1
33
+ label-fg: $primary
34
+ divider: $accent1
35
+
36
+ theme Tafel
37
+ background: $primary
38
+ text-primary: $white
39
+ text-secondary: #D4C4B0
40
+ text-tertiary: #8A7A68
41
+ logo: $accent1
42
+ cta: $accent1
43
+ label-bg: $accent2
44
+ label-fg: $white
45
+ divider: $accent1
46
+
47
+ # ── Typography ──
48
+ # Libre Franklin: utilitarian, no-nonsense, legible.
49
+ # Not a display typeface. Not decorative. Feels like signage.
50
+
51
+ font primary
52
+ name: Libre Franklin
53
+ fallback: sans-serif
54
+ source: google
55
+
56
+ typography headline
57
+ weight: 700
58
+ strong: 800
59
+ size: 6 # cqh - large but not shouting
60
+ lineHeight: 1.15
61
+ autofit-min: 4
62
+ autofit-max: 10
63
+
64
+ typography kicker
65
+ weight: 500
66
+ size: 3.5
67
+ lineHeight: 1.3
68
+
69
+ typography body
70
+ weight: 400
71
+ size: 2.8
72
+ lineHeight: 1.5
73
+
74
+ typography caption
75
+ weight: 400
76
+ size: 2.2
77
+ lineHeight: 1.4
78
+
79
+ typography label
80
+ weight: 600
81
+ size: 2
82
+ lineHeight: 1.2
83
+ uppercase
84
+
85
+ typography number
86
+ weight: 300
87
+ size: 14 # intentionally large - display element
88
+ lineHeight: 0.9
89
+ letterSpacing: -0.03
90
+
91
+ # ── Spacing ──
92
+
93
+ spacing:
94
+ unit: cqmin
95
+ xs: 1.5
96
+ s: 2
97
+ m: 3
98
+ l: 5
99
+ xl: 6.5
100
+
101
+ # ── Dividers ──
102
+
103
+ divider line
104
+ thickness: 0.15
105
+ width: 100%
106
+ color: divider
107
+ align: left
108
+ spacing: xs
109
+
110
+ divider short
111
+ thickness: 0.2
112
+ width: 25%
113
+ color: divider
114
+ align: left
115
+ spacing: s
116
+
117
+ # ── Voice Constraints ──
118
+ # Operationalized from identity voice reasoning.
119
+ # These are enforceable limits, not restated strategy.
120
+ # Derived from: persona is direct, rhythm is short sentences.
121
+
122
+ voice-constraints
123
+ register: informal
124
+ sentence-max: 8 # words - short, direct, like the baker talks
125
+ headline-pattern: noun-first
126
+ number-format: cardinal # "drei", not "3"
127
+
128
+ # ── Content Defaults ──
129
+ # Derived from identity: this is a small bakery, not a media brand.
130
+ # Low density. Natural images. Simple dividers.
131
+
132
+ content-defaults
133
+ density: low
134
+ image-treatment: natural
135
+ divider: line
@@ -0,0 +1,127 @@
1
+ # krume.identity
2
+ # Krume - neighborhood sourdough bakery, Berlin-Neukölln.
3
+ # This file is the reference implementation for Brand Intent.
4
+ # Every decision is commented to explain its derivation.
5
+
6
+ # ── Positioning ──
7
+ # Krume exists because its founder believes bread is craft,
8
+ # not lifestyle. The bakery is small, the hours are early,
9
+ # the sourdough is slow. Nothing about this is "curated."
10
+
11
+ essence: Brot ist Handwerk, nicht Lifestyle. Mehl, Wasser, Zeit.
12
+
13
+ promise: Wer reinkommt, riecht sofort, dass hier seit vier Uhr
14
+ morgens jemand steht.
15
+
16
+ tagline: Mehl, Wasser, Zeit. # also known as "Claim" (DACH)
17
+
18
+ positioning: Krume ist die einzige Bäckerei im Kiez, die jedes
19
+ Brot beim Mehl nennt, nicht beim Marketing.
20
+
21
+ mission: Ehrliches Brot für den Kiez - jeden Tag, aus dem
22
+ gleichen Ofen, mit den gleichen Händen.
23
+
24
+ vision: Eine Nachbarschaft, in der niemand mehr erklären muss,
25
+ warum gutes Brot sechs Euro kostet.
26
+
27
+ archetype: craftsman
28
+
29
+ # ── Narrative ──
30
+ # The origin story. Useful for about pages, editorial, press.
31
+
32
+ narrative
33
+ Gegründet 2019, als die Bäckerin ihren Agenturjob
34
+ kündigte, um das zu tun, was sie jeden Morgen seit
35
+ ihrem sechzehnten Lebensjahr getan hat: Brot backen.
36
+ Der Laden in der Weserstraße war vorher ein Copyshop.
37
+ Die erste Woche war der Ofen zu klein. Die zweite
38
+ Woche war das Brot zu schnell ausverkauft.
39
+ Beides hat sich nicht geändert.
40
+
41
+ # ── Voice ──
42
+ # The voice is the baker herself: direct, dry, proud of the work.
43
+ # Not warm-and-fuzzy. Not educational. Not performative.
44
+ # She talks the way she'd talk to someone across the counter.
45
+
46
+ voice
47
+ register: informal, Du
48
+ persona: Bäckerin hinterm Tresen - direkt, trocken, stolz auf das Handwerk
49
+ rhythm: kurze Sätze, konkrete Angaben, kein Erklären
50
+
51
+ # always/never: concrete, testable rules.
52
+ # An AI agent can evaluate any text against these with a yes/no answer.
53
+
54
+ always:
55
+ - Sauerteig, not "artisan bread"
56
+ - konkrete Mehlsorten (Tipo 1, Roggen 1150) over "beste Zutaten"
57
+ - Backzeit in Stunden, nicht "lange Teigführung"
58
+ - Kiez-Bezug (Neukölln, Weserstraße) over "unser Standort"
59
+ - Gewicht in Gramm bei jedem Brot
60
+
61
+ never:
62
+ - Lifestyle-Sprache (kuratiert, handverlesen, Genussmoment)
63
+ - "Artisan" oder "Craft" - wir sagen Handwerk
64
+ - Englische Begriffe wenn ein deutsches Wort existiert
65
+ - Superlative ohne Substanz (bestes Brot, einzigartiger Geschmack)
66
+ - Wellness- oder Gesundheitsversprechen (gut für dich, clean eating)
67
+
68
+ # ── Values ──
69
+ # Not keywords - behavior statements. Each value describes
70
+ # how it manifests in what we say and do.
71
+
72
+ values
73
+ Handwerk
74
+ Wir benennen den Prozess, nicht das Ergebnis.
75
+ Backzeit in Stunden, Mehlsorte beim Namen.
76
+ Kein Brot ohne nachvollziehbaren Weg vom Korn zum Laib.
77
+
78
+ Ehrlichkeit
79
+ Kein Brot ohne Zutatenliste. Keine Versprechen
80
+ die der Teig nicht halten kann. Wenn etwas
81
+ nicht gelungen ist, sagen wir das.
82
+
83
+ Kiez
84
+ Der Laden gehört zur Weserstraße wie der Späti
85
+ nebenan. Wir kennen die Stammkunden beim Namen
86
+ und die Nachbarn beim Brot.
87
+
88
+ # ── Content Pillars ──
89
+ # What Krume talks about, and what it explicitly avoids.
90
+
91
+ pillars
92
+ primary: Sauerteig, Handwerk, Zutaten, Kiez
93
+ secondary: Saisonales, Nachhaltigkeit, Backwissen
94
+ avoid: Lifestyle-Gastronomie, Wellness-Ernährung, Influencer-Kultur
95
+
96
+ # ── Audiences ──
97
+ # Two audiences with genuinely different language needs.
98
+ # The regulars don't need explanations. The newcomers do -
99
+ # but never in a condescending way.
100
+
101
+ audience regulars
102
+ label: Stammkunden aus dem Kiez
103
+ profile: 25-65, täglich oder wöchentlich, kennen die Sorten
104
+ motivation: ihr Brot, ihr Laden, verlässlicher Rhythmus
105
+ language: knapp, direkt, kein Erklären - die wissen Bescheid
106
+
107
+ audience newcomers
108
+ label: Neugierige und Laufkundschaft
109
+ profile: Touristen, neue Nachbarn, Marktbesucher
110
+ motivation: gutes Brot finden, verstehen was anders ist
111
+ language: einladend, konkret, erklärt ohne belehrend zu sein
112
+
113
+ # ── Anti-Audiences ──
114
+ # Explicitly not-our-people. Gives the AI a concrete
115
+ # "never sound like you're talking to this person" signal.
116
+
117
+ anti-audience influencers
118
+ label: Lifestyle-Influencer
119
+ description: Menschen die Brot als Aesthetic posten, nicht
120
+ als Lebensmittel essen. Wir sind nicht ihr Content.
121
+
122
+ anti-audience discount-hunters
123
+ label: Schnäppchenjäger
124
+ description: Wer den Preis eines Sauerteigbrots mit dem
125
+ Supermarkt-Toastbrot vergleicht, ist hier falsch.
126
+ Wir erklären gerne warum es kostet was es kostet -
127
+ aber wir rechtfertigen uns nicht.
package/cli/index.mjs ADDED
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, readdirSync, statSync } from 'fs';
4
+ import { join, dirname, resolve, basename } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ const PKG_ROOT = resolve(__dirname, '..');
10
+
11
+ const SKILL_FILE = join(PKG_ROOT, 'skills', 'brand-intent.md');
12
+
13
+ // ── Harness detection and install paths ──
14
+
15
+ const HARNESS_DIRS = {
16
+ claude: '.claude/commands',
17
+ cursor: '.cursor/rules',
18
+ gemini: '.gemini',
19
+ copilot: '.github/copilot-instructions',
20
+ };
21
+
22
+ function detectHarnesses(cwd) {
23
+ const found = [];
24
+ for (const [name, dir] of Object.entries(HARNESS_DIRS)) {
25
+ // Check if the harness config directory exists (or parent does)
26
+ const parent = dir.split('/')[0];
27
+ if (existsSync(join(cwd, parent))) {
28
+ found.push({ name, dir });
29
+ }
30
+ }
31
+ return found;
32
+ }
33
+
34
+ // ── Commands ──
35
+
36
+ function installSkills(cwd) {
37
+ if (!existsSync(SKILL_FILE)) {
38
+ console.error('Error: skill file not found at', SKILL_FILE);
39
+ process.exit(1);
40
+ }
41
+
42
+ const skill = readFileSync(SKILL_FILE, 'utf-8');
43
+ const harnesses = detectHarnesses(cwd);
44
+
45
+ if (harnesses.length === 0) {
46
+ // No harness detected — install to .claude/ by default
47
+ harnesses.push({ name: 'claude', dir: HARNESS_DIRS.claude });
48
+ }
49
+
50
+ let installed = 0;
51
+ for (const { name, dir } of harnesses) {
52
+ const target = join(cwd, dir);
53
+ mkdirSync(target, { recursive: true });
54
+ const dest = join(target, 'brand-intent.md');
55
+ writeFileSync(dest, skill, 'utf-8');
56
+ console.log(` ✓ ${name} — ${dir}/brand-intent.md`);
57
+ installed++;
58
+ }
59
+
60
+ console.log(`\nInstalled Brand Intent skill to ${installed} harness${installed > 1 ? 'es' : ''}.`);
61
+ }
62
+
63
+ function copyDirRecursive(src, dest) {
64
+ mkdirSync(dest, { recursive: true });
65
+ for (const entry of readdirSync(src)) {
66
+ const srcPath = join(src, entry);
67
+ const destPath = join(dest, entry);
68
+ if (statSync(srcPath).isDirectory()) {
69
+ copyDirRecursive(srcPath, destPath);
70
+ } else {
71
+ copyFileSync(srcPath, destPath);
72
+ }
73
+ }
74
+ }
75
+
76
+ function initFromBrand(brandName, cwd) {
77
+ const brandSrc = join(PKG_ROOT, 'brands', brandName);
78
+ if (!existsSync(brandSrc)) {
79
+ console.error(`Error: brand "${brandName}" not found.`);
80
+ console.error(`Available brands: ${readdirSync(join(PKG_ROOT, 'brands')).join(', ')}`);
81
+ process.exit(1);
82
+ }
83
+
84
+ // Copy brand files
85
+ const brandDest = join(cwd, `brands/${brandName}`);
86
+ copyDirRecursive(brandSrc, brandDest);
87
+ console.log(` ✓ brand files → brands/${brandName}/`);
88
+
89
+ // Copy shared layers (formats, purposes, compositions)
90
+ for (const layer of ['formats', 'purposes', 'compositions']) {
91
+ const layerSrc = join(PKG_ROOT, layer);
92
+ if (existsSync(layerSrc) && readdirSync(layerSrc).length > 0) {
93
+ const layerDest = join(cwd, layer);
94
+ copyDirRecursive(layerSrc, layerDest);
95
+ console.log(` ✓ ${layer}/ → ${layer}/`);
96
+ }
97
+ }
98
+
99
+ console.log(`\nScaffolded from "${brandName}". Edit the .identity and .brand files to make it yours.`);
100
+ }
101
+
102
+ function addBrand(brandRef, cwd) {
103
+ // brandRef is "brandName/brand" — extract the brand name
104
+ const brandName = brandRef.split('/')[0];
105
+ const brandSrc = join(PKG_ROOT, 'brands', brandName);
106
+ if (!existsSync(brandSrc)) {
107
+ console.error(`Error: brand "${brandName}" not found.`);
108
+ process.exit(1);
109
+ }
110
+
111
+ const brandDest = join(cwd, `brands/${brandName}`);
112
+ copyDirRecursive(brandSrc, brandDest);
113
+ console.log(` ✓ brand files → brands/${brandName}/`);
114
+ console.log(`\nAdded "${brandName}" brand files to your project.`);
115
+ }
116
+
117
+ // ── CLI entry point ──
118
+
119
+ const args = process.argv.slice(2);
120
+ const command = args[0];
121
+ const cwd = process.cwd();
122
+
123
+ console.log('Brand Intent v0.1.0\n');
124
+
125
+ switch (command) {
126
+ case 'install-skills':
127
+ installSkills(cwd);
128
+ break;
129
+
130
+ case 'init': {
131
+ const fromIdx = args.indexOf('--from');
132
+ if (fromIdx === -1 || !args[fromIdx + 1]) {
133
+ console.error('Usage: brand-intent init --from <brand-name>');
134
+ console.error('Example: brand-intent init --from krume');
135
+ process.exit(1);
136
+ }
137
+ initFromBrand(args[fromIdx + 1], cwd);
138
+ break;
139
+ }
140
+
141
+ case 'add': {
142
+ if (!args[1]) {
143
+ console.error('Usage: brand-intent add <brand-name>/brand');
144
+ console.error('Example: brand-intent add krume/brand');
145
+ process.exit(1);
146
+ }
147
+ addBrand(args[1], cwd);
148
+ break;
149
+ }
150
+
151
+ default:
152
+ console.log('Usage:');
153
+ console.log(' brand-intent install-skills Install Brand Intent skill for your AI harness');
154
+ console.log(' brand-intent init --from <brand> Scaffold a project from a reference brand');
155
+ console.log(' brand-intent add <brand>/brand Add a brand to an existing project');
156
+ console.log('');
157
+ console.log('Examples:');
158
+ console.log(' npx brand-intent install-skills');
159
+ console.log(' npx brand-intent init --from krume');
160
+ console.log(' npx brand-intent add krume/brand');
161
+ break;
162
+ }
@@ -0,0 +1,15 @@
1
+ # editorial.composition - grid mode
2
+ # Secondary above primary (newspaper style).
3
+ # Label badge top-right, info bottom-left, CTA bottom-right.
4
+ # This file contains zero brand-specific information.
5
+
6
+ id: editorial
7
+ mode: grid
8
+ image: full
9
+ logo: top left
10
+
11
+ label row 1 cols 3--4 right
12
+ secondary row 2 cols 1--4 left center
13
+ primary row 3 cols 1--4 left
14
+ meta row 5 cols 1--3 left bottom
15
+ cta row 5 cols 4--4 right bottom
@@ -0,0 +1,21 @@
1
+ # simple-stack.composition - flow mode
2
+ # Vertical stack in a bottom panel over a full-bleed image.
3
+ # Logo sticky at bottom. Content flows top-to-bottom.
4
+ # This file contains zero brand-specific information.
5
+
6
+ id: simple-stack
7
+ mode: flow
8
+ image: full
9
+
10
+ sticky: bottom s
11
+ | logo right
12
+
13
+ flow: panel bottom-left 2/3 1/1
14
+ | spacer-auto
15
+ | label left
16
+ | primary left
17
+ | divider-line
18
+ | secondary left
19
+ | spacer-s
20
+ | meta left
21
+ | spacer-auto
@@ -0,0 +1,21 @@
1
+ # instagram-4-5-feed-portrait.format
2
+ # Instagram feed post, portrait orientation.
3
+ # This format knows nothing about what content means -
4
+ # only where it appears and what fits.
5
+
6
+ id: 4:5
7
+ label: 4:5
8
+ sublabel: Feed Portrait
9
+ category: instagram
10
+
11
+ size: 1080 1350
12
+ grid: 5 4 # 5 rows × 4 columns
13
+
14
+ # Safe zones - platform-specific, not brand decisions.
15
+ danger: 0 0 0 0 # px - nothing always cropped on feed posts
16
+ crop: 0 34 0 34 # px - profile grid crops top/bottom
17
+ comfort: 40 # px - safe reading margin
18
+
19
+ # Which purposes are editorially valid here.
20
+ # This is a brand decision, not a technical constraint.
21
+ purposes: daily-bread, seasonal-special, baking-tip
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "brand-intent",
3
+ "version": "0.1.0",
4
+ "description": "An open file format specification for making brand intent machine-readable.",
5
+ "author": "Christophe Stoll <christophe@stoll.studio>",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/studiostoll/brand-intent.git"
10
+ },
11
+ "homepage": "https://intent.stoll.studio",
12
+ "bin": {
13
+ "brand-intent": "./cli/index.mjs"
14
+ },
15
+ "keywords": [
16
+ "brand",
17
+ "design-tokens",
18
+ "ai",
19
+ "specification",
20
+ "brand-identity",
21
+ "design-system"
22
+ ],
23
+ "files": [
24
+ "cli/",
25
+ "skills/",
26
+ "brands/",
27
+ "formats/",
28
+ "purposes/",
29
+ "compositions/"
30
+ ]
31
+ }
@@ -0,0 +1,78 @@
1
+ # daily-bread.purpose
2
+ # A single bread announcement - today's bread, name and facts.
3
+ # Used for daily Instagram posts: what's fresh, what's available.
4
+
5
+ id: daily-bread
6
+ name: Tagesbrot
7
+
8
+ # Which compositions can render this purpose.
9
+ compositions: editorial, simple-stack
10
+
11
+ density: light
12
+ palette: dynamic
13
+
14
+ # ── Voice ──
15
+ # Purpose-specific writing rules for AI text generation.
16
+
17
+ voice
18
+ headline-pattern: noun-first
19
+ number-format: digit
20
+ sentence-max: 8
21
+
22
+ # ── Identity Composition ──
23
+ # Filter the composed upstream: both audiences, primary pillars only.
24
+
25
+ scope
26
+ audience: regulars, newcomers
27
+ pillars: primary
28
+
29
+ # Context: only what is genuinely specific to this content type.
30
+ # These are slot-level instructions - not voice, not production params.
31
+ context
32
+ primary is the bread name, 1-3 words (e.g. "Roggenlaib").
33
+ secondary is the weight and price ("750 g · 5,80 €").
34
+ meta is one sentence: what makes this bread specific today.
35
+ Never describe the bread as "delicious" or "special" -
36
+ state a fact (flour type, fermentation time, seasonal ingredient).
37
+
38
+ # ── Slots ──
39
+
40
+ slot primary
41
+ label: Brotname
42
+ typography: headline
43
+ color: text-primary
44
+ maxLength: 30
45
+ samples:
46
+ - Roggenlaib
47
+ - Weizenmisch
48
+ - Dinkelvollkorn
49
+
50
+ slot secondary
51
+ label: Gewicht & Preis
52
+ typography: kicker
53
+ color: text-secondary
54
+ maxLength: 25
55
+ samples:
56
+ - 750 g · 5,80 €
57
+ - 500 g · 4,20 €
58
+ - 1 kg · 7,50 €
59
+
60
+ slot meta
61
+ label: Beschreibung
62
+ typography: caption
63
+ color: text-secondary
64
+ maxLength: 100
65
+ samples:
66
+ - Roggen 1150, 18 Stunden Teigführung, Kümmel aus dem Wendland.
67
+ - Tipo 1 aus der Drax-Mühle, 72 % Hydration.
68
+ - Saisonale Walnüsse vom Wochenmarkt, Emmer-Vollkorn.
69
+
70
+ slot label
71
+ label: Badge
72
+ typography: label
73
+ color: label-fg
74
+ maxLength: 20
75
+ samples:
76
+ - Heute frisch
77
+ - Nur samstags
78
+ - Neu im Laden
@@ -0,0 +1,168 @@
1
+ # Brand Intent / AI Skill
2
+
3
+ You are working in a project that uses **Brand Intent**, an open file format for encoding brand identity, expression intent, and content composition rules across five composable layers. This skill teaches you how to read, compose, and validate Brand Intent files.
4
+
5
+ ## The Five Layers
6
+
7
+ Brand Intent distributes brand knowledge across five independent file types. Each answers one question:
8
+
9
+ | Layer | File | Question | Changes |
10
+ |-------|------|----------|---------|
11
+ | **Identity** | `.identity` | Who is this brand? | Rarely (strategy) |
12
+ | **Brand** | `.brand` | What expression intent does it derive? | Occasionally (design) |
13
+ | **Format** | `.format` | Where does this content appear? | When platforms change |
14
+ | **Purpose** | `.purpose` | What is this content trying to do? | When content types evolve |
15
+ | **Composition** | `.composition` | How are elements composed? | Reusable across brands |
16
+
17
+ The arrow between layers is a **derivation arrow**: Identity contains reasoning. Brand derives expression intent from that reasoning. Format, Purpose, and Composition condition specific acts of expression without repeating what upstream layers establish.
18
+
19
+ ```
20
+ Identity → Brand → Format → Purpose → Composition
21
+ ```
22
+
23
+ ## How to Read Each Layer
24
+
25
+ ### `.identity`: Strategy (no visual properties)
26
+
27
+ **Required:** essence, promise, voice (register, persona, rhythm, always/never), pillars, audience segments.
28
+
29
+ **Optional:** tagline, positioning, mission, vision, archetype, narrative, values (with behavior statements), anti-audience segments.
30
+
31
+ **The `always`/`never` construct is your primary guardrail.** These are concrete, testable rules. When generating or reviewing content, check every item in `never:` and confirm every item in `always:`. They are not suggestions; they are constraints.
32
+
33
+ **`values`** are not just keywords; each has a behavior statement that describes how it manifests. Use these to inform decisions when voice rules don't cover a situation.
34
+
35
+ **`anti-audience`** segments define who the brand is explicitly *not* for. Never generate content that sounds like it's addressing an anti-audience. This is as important as `voice.never`.
36
+
37
+ **`positioning`** gives the competitive frame: what the brand is the only one of. Use this when the content needs to differentiate.
38
+
39
+ **Does NOT contain:** hex values, font names, type sizes, spacing, composition rules, or any visual properties.
40
+
41
+ ### `.brand`: Derived Expression Intent
42
+
43
+ Contains: color primitives, semantic themes, font definitions, named typography styles, `voice-constraints` (operationalized voice parameters), `content-defaults`, spacing, divider styles.
44
+
45
+ **`voice-constraints`** are production-enforceable limits derived from identity voice reasoning:
46
+ - `sentence-max:` maximum words per sentence. Enforce this.
47
+ - `headline-pattern:` structural pattern for headlines (e.g., `noun-first`, `verb-first`).
48
+ - `number-format:` `cardinal` means write "drei" not "3".
49
+ - `register:` must match the identity register.
50
+
51
+ **Does NOT contain:** identity reasoning, audience definitions, strategic prose, composition rules.
52
+
53
+ ### `.format`: Canvas Definition
54
+
55
+ Contains: platform, dimensions, safe zones (danger/crop/comfort), grid, allowed purposes.
56
+
57
+ **You rarely need this directly** unless generating content that must fit specific dimensions or validating whether a purpose is allowed on a given platform.
58
+
59
+ ### `.purpose`: Content Type Definition
60
+
61
+ Contains: semantic slot definitions (primary, secondary, detail, meta, cta, label), typography and color per slot, sample content, length constraints, `scope`, `context`.
62
+
63
+ **Semantic slots are roles, not positions:**
64
+ - `primary` = main communicative act (headline, key message)
65
+ - `secondary` = supporting signal (subline, number)
66
+ - `detail` = extended content (body, list)
67
+ - `meta` = contextual info (date, caption)
68
+ - `cta` = call to action
69
+ - `label` = orientation marker (badge, tag)
70
+
71
+ ### `.composition`: Spatial Arrangement
72
+
73
+ Contains: slot-to-position mappings in grid or flow mode.
74
+
75
+ **You rarely need to read this** unless debugging layout issues. Compositions contain zero brand-specific information.
76
+
77
+ ## Composing Context for Content Generation
78
+
79
+ When generating content for a specific purpose, compose your context by reading layers in order:
80
+
81
+ ### Step 1: Read `.identity`
82
+
83
+ Load the full identity. Pay special attention to:
84
+ - `voice.always` and `voice.never`: these are your hard constraints
85
+ - `voice.persona`: adopt this voice
86
+ - `voice.register`: match this formality level
87
+ - `pillars.avoid`: never generate content in these topic areas
88
+ - `anti-audience`: never sound like you're addressing these people
89
+ - `values`: use behavior statements to guide decisions when voice rules don't cover a situation
90
+ - `positioning`: use when content needs to differentiate
91
+
92
+ ### Step 2: Read `.brand`
93
+
94
+ Load voice-constraints and content-defaults:
95
+ - Enforce `sentence-max` on all generated text
96
+ - Follow `headline-pattern` for headlines
97
+ - Use `number-format` for numbers
98
+ - Apply `density` from content-defaults
99
+
100
+ ### Step 3: Apply `scope` from `.purpose`
101
+
102
+ The purpose file's `scope` narrows the composed upstream:
103
+ - `audience:` only use language guidance from these audience blocks
104
+ - `pillars:` restrict to this pillar scope (`primary`, `secondary`, or `all`)
105
+
106
+ If no filter is specified, include everything.
107
+
108
+ ### Step 4: Apply `context` from `.purpose`
109
+
110
+ Read the `context` block. This contains slot-specific instructions that cannot be derived from identity or brand. Follow them literally; they are precise, not general.
111
+
112
+ ### Step 5: Generate per-slot content
113
+
114
+ For each slot in the purpose:
115
+ - Respect `maxLength` (hard character limit)
116
+ - Use `samples` as structural reference (match the pattern, not the words)
117
+ - Follow the `context` instructions for this slot
118
+
119
+ ## Validation Checklist
120
+
121
+ After generating or reviewing content, validate against these rules in order:
122
+
123
+ 1. **`voice.never`:** Does any generated text contain a phrase, pattern, or concept from the never list? If yes, rewrite.
124
+ 2. **`voice.always`:** Does the text follow every rule in the always list? If not, fix.
125
+ 3. **`anti-audience`:** Could this content be read as addressing an anti-audience? If yes, rewrite.
126
+ 4. **`pillars.avoid`:** Does the content touch an avoided topic area? If yes, reject.
127
+ 5. **`values`:** Does the content contradict any value's behavior statement? If yes, fix.
128
+ 6. **`sentence-max`:** Count words in every sentence. Does any exceed the limit? If yes, split.
129
+ 7. **`headline-pattern`:** Does the headline follow the specified pattern? If not, restructure.
130
+ 8. **`maxLength`:** Does each slot's content fit within its character limit? If not, shorten.
131
+ 9. **`register`:** Does the formality level match? (e.g., if `informal, Du`, no formal address)
132
+
133
+ ## What NOT to Do
134
+
135
+ - **Do not restate identity in purpose-level work.** If a purpose's `context` block says "keep it warm and friendly," that belongs in `.identity`, not here. Flag it.
136
+ - **Do not invent voice rules.** Only apply constraints from the actual files. Your interpretation of the brand is less reliable than the authored rules.
137
+ - **Do not mix layers.** If asked to suggest a color, look in `.brand`. If asked about audience, look in `.identity`. Never guess from the wrong layer.
138
+ - **Do not ignore `never` rules.** These exist because the brand has been burned by generic AI output before. The `never` list is the most important part of the identity file.
139
+
140
+ ## File Discovery
141
+
142
+ Brand Intent files follow this structure:
143
+
144
+ ```
145
+ # Single brand
146
+ brandname.identity
147
+ brandname.brand
148
+ formats/*.format
149
+ purposes/*.purpose
150
+ compositions/*.composition
151
+
152
+ # Multi-brand
153
+ brands/brandname/brandname.identity
154
+ brands/brandname/brandname.brand
155
+ formats/*.format ← shared
156
+ purposes/*.purpose ← shared
157
+ compositions/*.composition ← shared
158
+ ```
159
+
160
+ To find the active brand, look for `.identity` and `.brand` files. There is exactly one of each per brand.
161
+
162
+ ## Relationship to Impeccable
163
+
164
+ If this project also uses [Impeccable](https://impeccable.style), the two are complementary:
165
+ - **Impeccable** teaches you how to design well (general craft)
166
+ - **Brand Intent** teaches you what this brand's expression intent is (specific identity)
167
+
168
+ Apply both. Impeccable governs execution quality. Brand Intent governs brand specificity.