brand-intent 0.1.4 → 0.1.6

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 CHANGED
@@ -17,9 +17,9 @@ Brand Intent is a set of five composable file types that encode brand knowledge
17
17
  | Layer | File | Question |
18
18
  |-------|------|----------|
19
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? |
20
+ | Brand | `.brand` | How is it expressed? |
21
+ | Format | `.format` | Where does it appear? |
22
+ | Purpose | `.purpose` | What is this content for? |
23
23
  | Composition | `.composition` | How are elements composed? |
24
24
 
25
25
  ## Get started
@@ -34,7 +34,7 @@ ext install studiostoll.brand-intent-lang
34
34
 
35
35
  ### 2. Run init
36
36
 
37
- The CLI walks you through everything: explore the Krume bakery reference, or create your own brand from scratch. It also offers to install AI skills at the end.
37
+ Installs the Brand Intent skill for your AI agent. The skill handles everything else: onboard your brand through a guided interview, explore the Krume bakery reference, generate and validate on-brand content.
38
38
 
39
39
  ```bash
40
40
  npx brand-intent init
@@ -46,10 +46,6 @@ npx brand-intent init
46
46
  - [Design Decisions](DESIGN.md): reasoning behind the framework
47
47
  - [intent.stoll.studio](https://intent.stoll.studio): project landing page
48
48
 
49
- ## Relationship to Impeccable
50
-
51
- 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.
52
-
53
49
  ## License
54
50
 
55
51
  MIT
package/cli/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, readdirSync, statSync } from 'fs';
4
- import { join, dirname, resolve, basename } from 'path';
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
4
+ import { join, dirname, resolve } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { createInterface } from 'readline';
7
7
 
@@ -11,7 +11,7 @@ const PKG_ROOT = resolve(__dirname, '..');
11
11
 
12
12
  const SKILL_FILE = join(PKG_ROOT, 'skills', 'brand-intent.md');
13
13
 
14
- // ── Harness detection and install paths ──
14
+ // ── Harness detection ──
15
15
 
16
16
  const HARNESSES = [
17
17
  { id: 'claude', label: 'Claude Code', dir: '.claude/commands' },
@@ -27,7 +27,7 @@ function detectHarnesses(cwd) {
27
27
  });
28
28
  }
29
29
 
30
- // ── Readline helpers (single shared instance) ──
30
+ // ── Readline ──
31
31
 
32
32
  let _rl;
33
33
  function rl() {
@@ -44,45 +44,7 @@ function ask(q) {
44
44
  return new Promise((res) => rl().question(q, res));
45
45
  }
46
46
 
47
- async function choose(prompt, options) {
48
- console.log(` ${prompt}\n`);
49
- options.forEach((opt, i) => {
50
- console.log(` ${i + 1}) ${opt.label}`);
51
- });
52
- console.log('');
53
- const answer = await ask(' Choose [1-' + options.length + ']: ');
54
- const idx = parseInt(answer, 10) - 1;
55
- if (idx >= 0 && idx < options.length) return options[idx].value;
56
- return options[0].value;
57
- }
58
-
59
- async function confirm(question, defaultYes = true) {
60
- const hint = defaultYes ? 'Y/n' : 'y/N';
61
- const answer = await ask(` ${question} (${hint}) `);
62
- if (answer.trim() === '') return defaultYes;
63
- return answer.trim().toLowerCase().startsWith('y');
64
- }
65
-
66
- async function input(question, defaultValue) {
67
- const hint = defaultValue ? ` (${defaultValue})` : '';
68
- const answer = await ask(` ${question}${hint}: `);
69
- return answer.trim() || defaultValue || '';
70
- }
71
-
72
- // ── File operations ──
73
-
74
- function copyDirRecursive(src, dest) {
75
- mkdirSync(dest, { recursive: true });
76
- for (const entry of readdirSync(src)) {
77
- const srcPath = join(src, entry);
78
- const destPath = join(dest, entry);
79
- if (statSync(srcPath).isDirectory()) {
80
- copyDirRecursive(srcPath, destPath);
81
- } else {
82
- copyFileSync(srcPath, destPath);
83
- }
84
- }
85
- }
47
+ // ── Install skill ──
86
48
 
87
49
  async function installSkills(cwd) {
88
50
  if (!existsSync(SKILL_FILE)) {
@@ -94,7 +56,6 @@ async function installSkills(cwd) {
94
56
  let harnesses = detectHarnesses(cwd);
95
57
 
96
58
  if (harnesses.length === 0) {
97
- // Nothing detected — ask which to install
98
59
  console.log(' Which AI tools do you use?\n');
99
60
  HARNESSES.forEach((h, i) => {
100
61
  console.log(` ${i + 1}) ${h.label}`);
@@ -119,320 +80,51 @@ async function installSkills(cwd) {
119
80
  writeFileSync(dest, skill, 'utf-8');
120
81
  console.log(` \u2713 ${label} \u2014 ${dir}/brand-intent.md`);
121
82
  }
122
- }
123
-
124
- // ── Explore the example ──
125
-
126
- function scaffoldExample(targetDir) {
127
- const brandSrc = join(PKG_ROOT, 'brands', 'krume');
128
- const exampleDest = join(targetDir, 'examples', 'krume');
129
-
130
- // Copy brand files
131
- copyDirRecursive(brandSrc, exampleDest);
132
- console.log(' \u2713 examples/krume/krume.identity');
133
- console.log(' \u2713 examples/krume/krume.brand');
134
-
135
- // Copy one format, one purpose, one composition as starting examples
136
- const examples = [
137
- { layer: 'formats', file: 'instagram-4-5-feed-portrait.format' },
138
- { layer: 'purposes', file: 'daily-bread.purpose' },
139
- { layer: 'compositions', file: 'editorial.composition' },
140
- ];
141
-
142
- for (const { layer, file } of examples) {
143
- const src = join(PKG_ROOT, layer, file);
144
- if (existsSync(src)) {
145
- const dest = join(exampleDest, layer, file);
146
- mkdirSync(dirname(dest), { recursive: true });
147
- copyFileSync(src, dest);
148
- console.log(` \u2713 examples/krume/${layer}/${file}`);
149
- }
150
- }
151
-
152
- console.log(`
153
- The Krume bakery is a fully commented reference implementation.
154
- Read the .identity file first \u2014 it explains every decision.
155
- Then look at how .brand derives expression intent from identity.
156
- Formats, purposes, and compositions show the remaining three layers.`);
157
- }
158
-
159
- // ── Create your own brand ──
160
-
161
- async function scaffoldOwnBrand(targetDir) {
162
- console.log('\n Let\u2019s define your brand. Answer what you can \u2014 skip the rest.\n');
163
-
164
- const brandName = await input('Brand name');
165
- if (!brandName) {
166
- console.error('\n A brand needs a name. Try again.\n');
167
- process.exit(1);
168
- }
169
-
170
- const brandId = brandName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
171
- const language = await input('Language', 'en');
172
- const locale = await input('Locale', language === 'de' ? 'de-DE' : language === 'en' ? 'en-US' : `${language}-${language.toUpperCase()}`);
173
-
174
- // Identity
175
- const essence = await input('Essence \u2014 one sentence, what this brand is about');
176
- const promise = await input('Promise \u2014 what people experience');
177
- const tagline = await input('Tagline');
178
- const archetype = await input('Archetype (e.g. craftsman, sage, rebel)');
179
-
180
- // Voice
181
- console.log('\n Voice \u2014 how the brand speaks.\n');
182
- const register = await input('Register (e.g. informal/Du, formal/Sie, casual)');
183
- const persona = await input('Persona \u2014 who is speaking?');
184
- const rhythm = await input('Rhythm \u2014 sentence style');
185
-
186
- console.log('\n Always rules \u2014 what the brand always does. One per line, empty line to stop.\n');
187
- const always = await collectList();
188
-
189
- console.log('\n Never rules \u2014 what the brand never does. One per line, empty line to stop.\n');
190
- const never = await collectList();
191
-
192
- // Pillars
193
- console.log('');
194
- const primaryPillars = await input('Primary pillars (comma-separated)');
195
- const secondaryPillars = await input('Secondary pillars (comma-separated)');
196
- const avoidPillars = await input('Avoid topics (comma-separated)');
197
-
198
- // Audiences
199
- console.log('\n Audiences \u2014 who the brand speaks to.\n');
200
- const audiences = await collectAudiences();
201
-
202
- // Colors
203
- console.log('\n Colors \u2014 the brand palette.\n');
204
- const primaryColor = await input('Primary color (hex)', '#1a1a1a');
205
- const secondaryColor = await input('Secondary color (hex)', '#4a4a4a');
206
- const accentColor = await input('Accent color (hex)', '#0066cc');
207
- const whiteColor = await input('Background/white (hex)', '#ffffff');
208
-
209
- // Typography
210
- console.log('\n Typography\n');
211
- const primaryFont = await input('Primary font name', 'Inter');
212
- const primaryFontSource = await input('Font source (google/local)', 'google');
213
-
214
- // ── Write .identity ──
215
-
216
- let identity = `# ${brandId}.identity\n`;
217
- if (essence) identity += `\nessence: ${essence}\n`;
218
- if (promise) identity += `\npromise: ${promise}\n`;
219
- if (tagline) identity += `\ntagline: ${tagline}\n`;
220
- if (archetype) identity += `\narchetype: ${archetype}\n`;
221
-
222
- if (register || persona || rhythm || always.length || never.length) {
223
- identity += `\nvoice\n`;
224
- if (register) identity += ` register: ${register}\n`;
225
- if (persona) identity += ` persona: ${persona}\n`;
226
- if (rhythm) identity += ` rhythm: ${rhythm}\n`;
227
- if (always.length) {
228
- identity += `\n always:\n`;
229
- for (const rule of always) identity += ` - ${rule}\n`;
230
- }
231
- if (never.length) {
232
- identity += `\n never:\n`;
233
- for (const rule of never) identity += ` - ${rule}\n`;
234
- }
235
- }
236
-
237
- if (primaryPillars || secondaryPillars || avoidPillars) {
238
- identity += `\npillars\n`;
239
- if (primaryPillars) identity += ` primary: ${primaryPillars}\n`;
240
- if (secondaryPillars) identity += ` secondary: ${secondaryPillars}\n`;
241
- if (avoidPillars) identity += ` avoid: ${avoidPillars}\n`;
242
- }
243
83
 
244
- for (const aud of audiences) {
245
- identity += `\naudience ${aud.id}\n`;
246
- identity += ` label: ${aud.label}\n`;
247
- if (aud.profile) identity += ` profile: ${aud.profile}\n`;
248
- if (aud.motivation) identity += ` motivation: ${aud.motivation}\n`;
249
- if (aud.language) identity += ` language: ${aud.language}\n`;
250
- }
251
-
252
- writeFileSync(join(targetDir, `${brandId}.identity`), identity, 'utf-8');
253
- console.log(`\n \u2713 ${brandId}.identity`);
254
-
255
- // ── Write .brand ──
256
-
257
- let brand = `# ${brandId}.brand \u2014 expression intent derived from ${brandId}.identity\n\n`;
258
- brand += `id: ${brandId}\n`;
259
- brand += `name: ${brandName}\n`;
260
- brand += `language: ${language}\n`;
261
- brand += `locale: ${locale}\n`;
262
-
263
- brand += `\nbrand-colors\n`;
264
- brand += ` primary: ${primaryColor}\n`;
265
- brand += ` secondary: ${secondaryColor}\n`;
266
- brand += ` accent1: ${accentColor}\n`;
267
- brand += ` white: ${whiteColor}\n`;
268
-
269
- brand += `\ntheme Default\n`;
270
- brand += ` background: $white\n`;
271
- brand += ` text-primary: $primary\n`;
272
- brand += ` text-secondary: $secondary\n`;
273
- brand += ` cta: $accent1\n`;
274
- brand += ` divider: $secondary\n`;
275
-
276
- brand += `\nfont primary\n`;
277
- brand += ` name: ${primaryFont}\n`;
278
- brand += ` fallback: sans-serif\n`;
279
- brand += ` source: ${primaryFontSource}\n`;
280
-
281
- brand += `\ntypography headline\n`;
282
- brand += ` weight: 700\n`;
283
- brand += ` size: 6\n`;
284
- brand += ` lineHeight: 1.15\n`;
285
-
286
- brand += `\ntypography body\n`;
287
- brand += ` weight: 400\n`;
288
- brand += ` size: 2.8\n`;
289
- brand += ` lineHeight: 1.5\n`;
290
-
291
- brand += `\ntypography caption\n`;
292
- brand += ` weight: 400\n`;
293
- brand += ` size: 2.2\n`;
294
- brand += ` lineHeight: 1.4\n`;
295
-
296
- brand += `\ntypography label\n`;
297
- brand += ` weight: 600\n`;
298
- brand += ` size: 2\n`;
299
- brand += ` lineHeight: 1.2\n`;
300
- brand += ` uppercase\n`;
301
-
302
- brand += `\nspacing:\n`;
303
- brand += ` unit: cqmin\n`;
304
- brand += ` xs: 1.5\n`;
305
- brand += ` s: 2\n`;
306
- brand += ` m: 3\n`;
307
- brand += ` l: 5\n`;
308
- brand += ` xl: 6.5\n`;
309
-
310
- writeFileSync(join(targetDir, `${brandId}.brand`), brand, 'utf-8');
311
- console.log(` \u2713 ${brandId}.brand`);
312
-
313
- // ── Write a starter .purpose ──
314
-
315
- let purpose = `id: starter\n`;
316
- purpose += `name: Starter Purpose\n`;
317
- purpose += `density: light\n`;
318
- purpose += `palette: dynamic\n\n`;
319
- purpose += `slot primary\n`;
320
- purpose += ` label: Headline\n`;
321
- purpose += ` typography: headline\n`;
322
- purpose += ` color: text-primary\n`;
323
- purpose += ` maxLength: 60\n`;
324
- purpose += ` samples:\n`;
325
- purpose += ` - Your headline here\n\n`;
326
- purpose += `slot secondary\n`;
327
- purpose += ` label: Subline\n`;
328
- purpose += ` typography: body\n`;
329
- purpose += ` color: text-secondary\n`;
330
- purpose += ` maxLength: 120\n`;
331
- purpose += ` samples:\n`;
332
- purpose += ` - Supporting text goes here\n`;
333
-
334
- const purposesDir = join(targetDir, 'purposes');
335
- mkdirSync(purposesDir, { recursive: true });
336
- writeFileSync(join(purposesDir, 'starter.purpose'), purpose, 'utf-8');
337
- console.log(` \u2713 purposes/starter.purpose`);
338
-
339
- console.log(`
340
- Your brand is scaffolded. Next steps:
341
- - Edit ${brandId}.identity \u2014 add narratives, values, and audiences
342
- - Edit ${brandId}.brand \u2014 refine colors, add themes, typography styles
343
- - Replace purposes/starter.purpose with real content purposes
344
- - Add formats/ and compositions/ when you\u2019re ready for layout`);
345
- }
346
-
347
- async function collectList() {
348
- const items = [];
349
- while (true) {
350
- const line = await ask(' - ');
351
- if (!line.trim()) break;
352
- items.push(line.trim());
353
- }
354
- return items;
355
- }
356
-
357
- async function collectAudiences() {
358
- const audiences = [];
359
- while (true) {
360
- const id = await input('Audience ID (e.g. "regulars", empty to stop)');
361
- if (!id) break;
362
- const label = await input(` Label for "${id}"`);
363
- const profile = await input(' Profile (demographics, behavior)');
364
- const motivation = await input(' Motivation');
365
- const language = await input(' Language style');
366
- audiences.push({ id, label: label || id, profile, motivation, language });
367
- console.log('');
368
- }
369
- return audiences;
370
- }
371
-
372
- // ── Interactive init flow ──
373
-
374
- async function interactiveInit() {
375
- console.log(' Brand Intent \u2014 What a brand means. Authored, structured, shared.\n');
376
-
377
- const folder = await input('Project folder');
378
- if (!folder) {
379
- console.error('\n A project folder is required.\n');
380
- process.exit(1);
381
- }
382
-
383
- const targetDir = resolve(process.cwd(), folder);
384
-
385
- const mode = await choose('How do you want to start?', [
386
- { label: 'Explore the example', value: 'explore' },
387
- { label: 'Create your own brand', value: 'create' },
388
- { label: 'Just install the AI skill', value: 'skill-only' },
389
- ]);
390
-
391
- // Create project directory
392
- mkdirSync(targetDir, { recursive: true });
393
- console.log('');
394
-
395
- if (mode === 'explore') {
396
- scaffoldExample(targetDir);
397
- } else if (mode === 'create') {
398
- await scaffoldOwnBrand(targetDir);
399
- }
400
-
401
- // Always install skills — they're the infrastructure
402
- console.log('');
403
- await installSkills(targetDir);
404
-
405
- closeRl();
406
- console.log('\n Done. Happy branding.\n');
84
+ return harnesses;
407
85
  }
408
86
 
409
87
  // ── CLI entry point ──
410
88
 
411
89
  const args = process.argv.slice(2);
412
90
  const command = args[0];
91
+ const cwd = process.cwd();
413
92
 
414
93
  const pkg = JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf-8'));
415
94
  console.log(`Brand Intent v${pkg.version}\n`);
416
95
 
417
- switch (command) {
418
- case 'install-skills':
419
- await installSkills(process.cwd());
420
- closeRl();
421
- break;
96
+ if (command && command !== 'init' && command !== 'install-skills' && command !== '--help' && command !== '-h') {
97
+ console.log(` Unknown command: ${command}\n`);
98
+ }
422
99
 
423
- case 'init':
424
- await interactiveInit();
425
- break;
100
+ if (!command || command === '--help' || command === '-h') {
101
+ console.log('Usage:');
102
+ console.log(' brand-intent init Install the Brand Intent skill for your AI agent');
103
+ console.log(' brand-intent install-skills Same as init (alias)');
104
+ console.log('');
105
+ console.log('Example:');
106
+ console.log(' npx brand-intent init');
107
+ process.exit(0);
108
+ }
426
109
 
427
- default:
428
- if (command && command !== '--help' && command !== '-h') {
429
- console.log(` Unknown command: ${command}\n`);
430
- }
431
- console.log('Usage:');
432
- console.log(' brand-intent init Guided setup \u2014 explore the example or create your own brand');
433
- console.log(' brand-intent install-skills Install Brand Intent skill for your AI harness');
434
- console.log('');
435
- console.log('Example:');
436
- console.log(' npx brand-intent init');
437
- break;
110
+ // Ask for project folder
111
+ console.log(' Brand Intent \u2014 What a brand means. Authored, structured, shared.\n');
112
+ const folder = await ask(' Project folder (. for current directory): ');
113
+ const targetDir = resolve(cwd, folder.trim() || '.');
114
+ if (targetDir !== cwd) {
115
+ mkdirSync(targetDir, { recursive: true });
438
116
  }
117
+ console.log('');
118
+
119
+ await installSkills(targetDir);
120
+ closeRl();
121
+
122
+ console.log('\n Done. Now open your AI agent and try:\n');
123
+ console.log(' "Onboard my brand"');
124
+ console.log(' "Show me the Krume bakery example"');
125
+ console.log('\n Skill commands:\n');
126
+ console.log(' /brand-intent onboard Create your brand through a guided interview');
127
+ console.log(' /brand-intent example Scaffold the Krume reference implementation');
128
+ console.log(' /brand-intent validate Validate content against your brand rules');
129
+ console.log(' /brand-intent Discover brand files and available purposes');
130
+ console.log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brand-intent",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "An open file format specification for making brand intent machine-readable.",
5
5
  "author": "Christophe Stoll <christophe@stoll.studio>",
6
6
  "license": "MIT",
@@ -6,6 +6,8 @@ description: Generate and validate on-brand content using Brand Intent files
6
6
 
7
7
  **Scope:** `$ARGUMENTS`
8
8
 
9
+ > If `$ARGUMENTS` is "onboard": run the guided brand onboarding interview (see Onboarding section below).
10
+ > If `$ARGUMENTS` is "example": scaffold the Krume bakery reference implementation into `examples/krume/` and explain what each file does.
9
11
  > If `$ARGUMENTS` names a purpose (e.g. "daily-bread", "event", "quote"): compose context from the five layers and generate content for that purpose.
10
12
  > If `$ARGUMENTS` is "validate": read the most recent generated or provided content and run the validation checklist against the brand files.
11
13
  > If `$ARGUMENTS` is empty: discover all `.identity`, `.brand`, `.purpose` files in the project, summarize the brand voice and list available purposes.
@@ -98,9 +100,6 @@ When generating content for a specific purpose, read all layers silently (do not
98
100
 
99
101
  ### Output format:
100
102
 
101
- For each brand that has the requested purpose:
102
-
103
- - If multiple brands exist, show the **brand name** as a heading
104
103
  - Show each **slot** with its generated content — nothing else
105
104
  - After the content, show a **compact validation summary** (pass/fail per rule, only call out failures in detail)
106
105
 
@@ -132,27 +131,115 @@ After generating or reviewing content, validate against these rules in order:
132
131
  Brand Intent files follow this structure:
133
132
 
134
133
  ```
135
- # Single brand
136
134
  brandname.identity
137
135
  brandname.brand
138
136
  formats/*.format
139
137
  purposes/*.purpose
140
138
  compositions/*.composition
139
+ ```
140
+
141
+ To find the active brand, look for the `.identity` and `.brand` files at the project root. There is exactly one of each per project.
142
+
143
+ ## Onboarding: Create a New Brand
144
+
145
+ When `$ARGUMENTS` is "onboard", guide the user through a structured interview to create their brand files. Do NOT jump to file creation — the interview comes first.
146
+
147
+ ### Interview Flow
148
+
149
+ Have a natural conversation. Don't dump all questions at once — adapt based on answers. Group questions into rounds of 2-4.
150
+
151
+ **Round 1: Identity**
152
+ - Brand name? (and brand ID — lowercase, no spaces, used in filenames)
153
+ - What does this brand do? One sentence.
154
+ - Language? (e.g., de, en)
155
+ - Essence — the irreducible truth in 3-8 words?
156
+
157
+ Listen for tone, confidence, specificity. A founder who says "we make artisan coffee" vs "we roast single-origin in a garage in Kreuzberg" gives you very different brands.
158
+
159
+ **Round 2: Voice & Personality**
160
+ - If this brand were a person talking to a customer, who would they be?
161
+ - Formal or informal?
162
+ - What does this brand ALWAYS do in communication? (2-3 concrete rules)
163
+ - What does this brand NEVER do? (2-3 concrete anti-patterns)
164
+
165
+ The user will often describe what the brand is NOT before what it IS. Capture both.
166
+
167
+ **Round 3: Visual Direction**
168
+ - Describe the color palette. Hex values, or a mood? (e.g., "warm earth tones", "cold Nordic blue")
169
+ - What fonts define this brand? (primary + body. If unknown: serif or sans-serif? Heavy or light?)
170
+
171
+ If the user describes a mood: propose a palette of 4-5 colors (primary, secondary, accent1, accent2, white/background) and confirm before proceeding.
172
+
173
+ **Round 4: Content & Audience**
174
+ - Who are the 1-2 main audiences? For each: who are they, what do they want, how should you talk to them?
175
+ - Anti-audience — someone this brand is explicitly NOT for?
176
+ - What content topics are on-brand? (primary pillars)
177
+ - What topics should the brand avoid?
178
+
179
+ ### Synthesis
141
180
 
142
- # Multi-brand
143
- brands/brandname/brandname.identity
144
- brands/brandname/brandname.brand
145
- formats/*.format ← shared
146
- purposes/*.purpose ← shared
147
- compositions/*.composition ← shared
181
+ After the interview, present a Brand Brief for confirmation:
182
+
183
+ ```
184
+ ## Brand Brief: [Brand Name]
185
+ ID: [brandid] | Language: [language]
186
+ Essence: [essence]
187
+
188
+ Voice: [persona], [register]
189
+ Always: [rules]
190
+ Never: [anti-patterns]
191
+
192
+ Colors: primary [hex], secondary [hex], accent [hex], background [hex]
193
+ Font: [primary font name]
194
+
195
+ Audiences: [summaries]
196
+ Pillars: [topics] | Avoid: [topics]
148
197
  ```
149
198
 
150
- To find the active brand, look for `.identity` and `.brand` files. There is exactly one of each per brand.
199
+ Ask the user to confirm or adjust. Iterate until they're happy.
200
+
201
+ ### File Generation
202
+
203
+ Once confirmed, generate these files at the project root:
204
+
205
+ 1. **`[brandid].identity`** — essence, promise, tagline, archetype, voice (register, persona, rhythm, always/never), pillars, audience blocks, anti-audience blocks. Follow the structure of the Krume reference.
151
206
 
152
- ## Relationship to Impeccable
207
+ 2. **`[brandid].brand`** — id, name, language, locale, brand-colors, at least one theme (mapping semantic slots to color references), font blocks, typography blocks (headline, body, caption, label at minimum), spacing, dividers. Use `$name` references for theme colors.
153
208
 
154
- If this project also uses [Impeccable](https://impeccable.style), the two are complementary:
155
- - **Impeccable** teaches you how to design well (general craft)
156
- - **Brand Intent** teaches you what this brand's expression intent is (specific identity)
209
+ 3. **`purposes/starter.purpose`** — a starter purpose with slots that have on-brand sample content derived from the identity. Samples must be in the brand's language and respect voice rules.
210
+
211
+ ### Rules
212
+
213
+ - **Every voice rule, value, and narrative must come from the user's answers.** Don't invent brand personality.
214
+ - **Sample content in purposes must match the brand's language.**
215
+ - **Don't skip the interview.** Even if the user says "just make a brand called X" — ask at least the essentials (essence, voice, colors, one audience).
216
+ - **Typography sizes use `cqmin` units.** Use these sensible defaults unless the user specified otherwise: headline 6, body 2.8, caption 2.2, label 2.
217
+ - **Spacing defaults:** unit cqmin, xs 1.5, s 2, m 3, l 5, xl 6.5.
218
+
219
+ ## Example: Scaffold the Krume Reference
220
+
221
+ When `$ARGUMENTS` is "example", copy the Krume bakery reference files into `examples/krume/` in the current project. The Krume files are distributed with the npm package.
222
+
223
+ Run this shell command to copy them:
224
+
225
+ ```bash
226
+ node -e "
227
+ const pkg = require.resolve('brand-intent/package.json');
228
+ const root = require('path').dirname(pkg);
229
+ const { cpSync, mkdirSync } = require('fs');
230
+ const dest = 'examples/krume';
231
+ mkdirSync(dest, { recursive: true });
232
+ for (const d of ['brands/krume', 'formats', 'purposes', 'compositions']) {
233
+ const src = require('path').join(root, d);
234
+ try { cpSync(src, require('path').join(dest, d.replace('brands/krume', '')), { recursive: true }); } catch {}
235
+ }
236
+ console.log('Krume example scaffolded into examples/krume/');
237
+ "
238
+ ```
157
239
 
158
- Apply both. Impeccable governs execution quality. Brand Intent governs brand specificity.
240
+ Then explain to the user what each file does:
241
+ - **`.identity`** — the strategic layer: essence, voice, values, audiences. Read this first.
242
+ - **`.brand`** — derived expression: colors, themes, typography, spacing. Every decision traces back to identity.
243
+ - **`.format`** — canvas dimensions, safe zones, grid. Where content appears.
244
+ - **`.purpose`** — content type with semantic slots, samples, constraints. What content does.
245
+ - **`.composition`** — spatial arrangement of slots. How elements are placed.