siesa-agents 2.1.90 → 2.1.92

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.
Files changed (81) hide show
  1. package/bmad/_config/bmad-help.csv +45 -0
  2. package/bmad/_config/files-manifest.csv +272 -0
  3. package/bmad/_config/manifest.yaml +23 -0
  4. package/bmad/_config/skill-manifest.csv +43 -0
  5. package/bmad/bmm/module-help.csv +33 -0
  6. package/bmad/config.toml +69 -0
  7. package/bmad/config.user.toml +17 -0
  8. package/bmad/core/config.yaml +9 -0
  9. package/bmad/core/module-help.csv +13 -0
  10. package/bmad/custom/config.toml +7 -0
  11. package/bmad/scripts/resolve_config.py +176 -0
  12. package/bmad/scripts/resolve_customization.py +230 -0
  13. package/claude/skills/sa-wds-visual-proposals/SKILL.md +6 -0
  14. package/package.json +1 -1
  15. package/siesa-agents/bmm/workflows/3-solutioning/wds-visual-proposals/data/component-mapping.csv +47 -0
  16. package/siesa-agents/bmm/workflows/3-solutioning/wds-visual-proposals/steps/step-01-init.md +148 -0
  17. package/siesa-agents/bmm/workflows/3-solutioning/wds-visual-proposals/steps/step-01b-continue.md +79 -0
  18. package/siesa-agents/bmm/workflows/3-solutioning/wds-visual-proposals/steps/step-02-bridge.md +114 -0
  19. package/siesa-agents/bmm/workflows/3-solutioning/wds-visual-proposals/steps/step-02b-kit-warmup.md +96 -0
  20. package/siesa-agents/bmm/workflows/3-solutioning/wds-visual-proposals/steps/step-03-scenarios.md +102 -0
  21. package/siesa-agents/bmm/workflows/3-solutioning/wds-visual-proposals/steps/step-04-specs.md +97 -0
  22. package/siesa-agents/bmm/workflows/3-solutioning/wds-visual-proposals/steps/step-05-prototype.md +102 -0
  23. package/siesa-agents/bmm/workflows/3-solutioning/wds-visual-proposals/steps/step-06-handoff.md +96 -0
  24. package/siesa-agents/bmm/workflows/3-solutioning/wds-visual-proposals/templates/product-brief-bridge.md +68 -0
  25. package/siesa-agents/bmm/workflows/3-solutioning/wds-visual-proposals/templates/proposal-index.md +46 -0
  26. package/siesa-agents/bmm/workflows/3-solutioning/wds-visual-proposals/templates/trigger-map-bridge.md +54 -0
  27. package/siesa-agents/bmm/workflows/3-solutioning/wds-visual-proposals/workflow.md +82 -0
  28. package/siesa-agents/bmm/workflows/3-solutioning/wds-visual-proposals/workflow_ext.md +195 -0
  29. package/siesa-agents/scripts/phases/phase1.js +42 -4
  30. package/siesa-agents/scripts/phases/phase2.js +42 -4
  31. package/siesa-agents/scripts/phases/phase3.js +42 -4
  32. package/siesa-agents/wds/config.yaml +20 -0
  33. package/siesa-agents/wds/data/agent-contracts.md +72 -0
  34. package/siesa-agents/wds/data/agent-guides/freya/agentic-development.md +223 -0
  35. package/siesa-agents/wds/data/agent-guides/freya/content-creation.md +270 -0
  36. package/siesa-agents/wds/data/agent-guides/freya/design-system.md +333 -0
  37. package/siesa-agents/wds/data/agent-guides/freya/meta-content-guide.md +495 -0
  38. package/siesa-agents/wds/data/agent-guides/freya/specification-quality.md +262 -0
  39. package/siesa-agents/wds/data/agent-guides/freya/strategic-design.md +116 -0
  40. package/siesa-agents/wds/data/agent-guides/saga/content-structure-principles.md +190 -0
  41. package/siesa-agents/wds/data/agent-guides/saga/conversational-followups.md +372 -0
  42. package/siesa-agents/wds/data/agent-guides/saga/discovery-conversation.md +265 -0
  43. package/siesa-agents/wds/data/agent-guides/saga/dream-up-approach.md +1034 -0
  44. package/siesa-agents/wds/data/agent-guides/saga/inspiration-analysis.md +215 -0
  45. package/siesa-agents/wds/data/agent-guides/saga/resources/project-brief.template.md +187 -0
  46. package/siesa-agents/wds/data/agent-guides/saga/seo-strategy-guide.md +391 -0
  47. package/siesa-agents/wds/data/agent-guides/saga/strategic-documentation.md +454 -0
  48. package/siesa-agents/wds/data/agent-guides/saga/trigger-mapping.md +653 -0
  49. package/siesa-agents/wds/data/agent-guides/saga/working-with-existing-materials.md +172 -0
  50. package/siesa-agents/wds/data/design-system/component-boundaries.md +318 -0
  51. package/siesa-agents/wds/data/design-system/figma-component-structure.md +697 -0
  52. package/siesa-agents/wds/data/design-system/naming-conventions.md +200 -0
  53. package/siesa-agents/wds/data/design-system/state-management.md +93 -0
  54. package/siesa-agents/wds/data/design-system/token-architecture.md +474 -0
  55. package/siesa-agents/wds/data/design-system/validation-patterns.md +74 -0
  56. package/siesa-agents/wds/data/presentations/freya-how-i-help.md +63 -0
  57. package/siesa-agents/wds/data/presentations/freya-intro.md +269 -0
  58. package/siesa-agents/wds/data/presentations/freya-presentation.md +77 -0
  59. package/siesa-agents/wds/data/presentations/freya-workflows-guide.md +51 -0
  60. package/siesa-agents/wds/data/presentations/mimir-agents-overview.md +66 -0
  61. package/siesa-agents/wds/data/presentations/mimir-tone-setting.md +48 -0
  62. package/siesa-agents/wds/data/presentations/saga-how-i-help.md +62 -0
  63. package/siesa-agents/wds/data/presentations/saga-intro.md +285 -0
  64. package/siesa-agents/wds/data/presentations/saga-presentation.md +74 -0
  65. package/siesa-agents/wds/data/presentations/saga-workflows-guide.md +48 -0
  66. package/siesa-agents/wds/data/shared-activation.md +49 -0
  67. package/siesa-agents/wds/data/wds-glossary.md +98 -0
  68. package/siesa-agents/wds/module-help.csv +19 -0
  69. package/siesa-agents/wds/scripts/README.md +155 -0
  70. package/siesa-agents/wds/scripts/wds-add-object.js +207 -0
  71. package/siesa-agents/wds/scripts/wds-add-spacing.js +158 -0
  72. package/siesa-agents/wds/scripts/wds-init-page.js +234 -0
  73. package/siesa-agents/wds/scripts/wds-init-scenario.js +125 -0
  74. package/siesa-agents/wds/scripts/wds-nav.js +206 -0
  75. package/siesa-agents/wds/scripts/wds-validate.js +306 -0
  76. package/siesa-agents/wds/skills/freya.activation.md +204 -0
  77. package/siesa-agents/wds/skills/handoff.md +91 -0
  78. package/siesa-agents/wds/skills/saga.activation.md +169 -0
  79. package/siesa-agents/wds/skills/shared/git.md +55 -0
  80. package/siesa-agents/wds/skills/start.md +99 -0
  81. package/siesa-agents/wds/skills/wrap.md +198 -0
@@ -0,0 +1,155 @@
1
+ # WDS Scaffold Scripts
2
+
3
+ Node.js scripts that enforce deterministic output from AI agents. Agents provide content via CLI flags; scripts produce structure.
4
+
5
+ All scripts use only Node.js stdlib (no external dependencies). Run from the project root.
6
+
7
+ ---
8
+
9
+ ## Scripts
10
+
11
+ ### `wds-init-scenario.js` — Initialize a scenario
12
+
13
+ Creates the scenario folder and a README index file.
14
+
15
+ ```bash
16
+ node src/scripts/wds-init-scenario.js \
17
+ --scenario "01 New User Onboarding" \
18
+ --description "New user first visit to account creation"
19
+ ```
20
+
21
+ Output: `C-UX-Scenarios/01-new-user-onboarding/README.md`
22
+
23
+ ---
24
+
25
+ ### `wds-init-page.js` — Initialize a page spec
26
+
27
+ Creates a new page spec file with all required sections pre-filled with placeholders.
28
+
29
+ ```bash
30
+ node src/scripts/wds-init-page.js \
31
+ --page "01 Start" \
32
+ --scenario "01 New User Onboarding" \
33
+ --platform "Mobile web" \
34
+ --visibility "Public"
35
+ ```
36
+
37
+ Output:
38
+ - `C-UX-Scenarios/01-new-user-onboarding/01-start/01-start.md`
39
+ - `C-UX-Scenarios/01-new-user-onboarding/01-start/sketches/`
40
+
41
+ After creating all pages in a scenario, run `wds-nav.js` to wire up navigation links.
42
+
43
+ ---
44
+
45
+ ### `wds-nav.js` — Update navigation links
46
+
47
+ Scans pages in a scenario (sorted by name) and writes correct prev/next navigation rows into each page spec.
48
+
49
+ ```bash
50
+ # One scenario
51
+ node src/scripts/wds-nav.js --scenario "01 New User Onboarding"
52
+
53
+ # All scenarios
54
+ node src/scripts/wds-nav.js --all
55
+ ```
56
+
57
+ Run this after adding or removing pages, or after reordering page numbers.
58
+
59
+ ---
60
+
61
+ ### `wds-add-object.js` — Append an object spec
62
+
63
+ Appends a structured object spec block to a page spec under a named section.
64
+
65
+ ```bash
66
+ node src/scripts/wds-add-object.js \
67
+ --page "C-UX-Scenarios/01-new-user-onboarding/01-start/01-start.md" \
68
+ --section "Hero" \
69
+ --object "Primary Headline" \
70
+ --component "H1 heading" \
71
+ --se "Välkommen" \
72
+ --en "Welcome" \
73
+ --behavior "Static display"
74
+ ```
75
+
76
+ Object ID is auto-derived: `start-hero-primary-headline`
77
+
78
+ The section heading (`### Section: Hero`) is created if it doesn't already exist.
79
+
80
+ ---
81
+
82
+ ### `wds-add-spacing.js` — Append a spacing object
83
+
84
+ Appends a spacing notation entry to the `## Spacing` section of a page spec.
85
+
86
+ ```bash
87
+ node src/scripts/wds-add-spacing.js \
88
+ --page "C-UX-Scenarios/01-new-user-onboarding/01-start/01-start.md" \
89
+ --direction v \
90
+ --type space \
91
+ --size xl \
92
+ --reason "major section boundary between hero and features"
93
+ ```
94
+
95
+ Valid directions: `v` (vertical), `h` (horizontal)
96
+ Valid types: `space`, `separator`, `line`
97
+ Valid sizes: `zero`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl`, `flex`
98
+
99
+ Spacing ID is auto-derived: `start-v-space-xl`
100
+
101
+ ---
102
+
103
+ ### `wds-validate.js` — Validate page specs
104
+
105
+ Checks page spec files for structural correctness.
106
+
107
+ ```bash
108
+ # Single page
109
+ node src/scripts/wds-validate.js \
110
+ --page "C-UX-Scenarios/01-new-user-onboarding/01-start/01-start.md"
111
+
112
+ # All pages in a scenario
113
+ node src/scripts/wds-validate.js --scenario "01 New User Onboarding"
114
+
115
+ # All scenarios
116
+ node src/scripts/wds-validate.js --all
117
+ ```
118
+
119
+ Validates:
120
+ - Required sections present
121
+ - Object IDs are kebab-case with correct page prefix
122
+ - No duplicate Object IDs
123
+ - Navigation rows (3 expected)
124
+ - Metadata table has all required properties
125
+ - Sketches folder exists
126
+ - SE + EN content present for each object
127
+
128
+ ---
129
+
130
+ ## How agents use these scripts
131
+
132
+ 1. Agent calls `wds-init-scenario.js` with scenario name and description
133
+ 2. Agent calls `wds-init-page.js` for each page in the scenario
134
+ 3. Agent calls `wds-nav.js` to wire navigation after all pages exist
135
+ 4. Agent calls `wds-add-object.js` for each UI object, providing Swedish and English content
136
+ 5. Agent calls `wds-add-spacing.js` for each spacing decision
137
+ 6. Agent calls `wds-validate.js` to confirm the spec is structurally correct before handoff
138
+
139
+ The agent never writes raw markdown — it only supplies content as flag values. The scripts own all structural decisions.
140
+
141
+ ---
142
+
143
+ ## File location convention
144
+
145
+ ```
146
+ C-UX-Scenarios/
147
+ {scenario-slug}/
148
+ README.md
149
+ {page-slug}/
150
+ {page-slug}.md
151
+ sketches/
152
+ {page-slug}-concept.jpg
153
+ ```
154
+
155
+ Example: `C-UX-Scenarios/01-new-user-onboarding/02-signup/02-signup.md`
@@ -0,0 +1,207 @@
1
+ // wds-add-object.js — WDS scaffold: append an object spec block to a page spec file
2
+ // Usage: node src/scripts/wds-add-object.js --page "C-UX-Scenarios/01-onboarding/01-start/01-start.md" \
3
+ // --section "Hero" --object "Primary Headline" --component "H1 heading" \
4
+ // --se "Välkommen" --en "Welcome"
5
+
6
+ 'use strict';
7
+
8
+ const fs = require('node:fs');
9
+ const path = require('node:path');
10
+
11
+ function parseArgs(argv) {
12
+ const args = {};
13
+ for (let i = 0; i < argv.length; i++) {
14
+ if (argv[i].startsWith('--')) {
15
+ const key = argv[i].slice(2);
16
+ const value = argv[i + 1] && !argv[i + 1].startsWith('--') ? argv[i + 1] : true;
17
+ args[key] = value;
18
+ if (value !== true) i++;
19
+ }
20
+ }
21
+ return args;
22
+ }
23
+
24
+ function toSlug(str) {
25
+ return str
26
+ .normalize('NFD')
27
+ .replace(/\p{Diacritic}/gu, '') // strip accents: á→a, ó→o, ñ→n, ü→u
28
+ .toLowerCase()
29
+ .replaceAll(/[^a-z0-9]+/g, '-') // any run of non-alphanumeric → single dash
30
+ .replace(/^-+|-+$/g, ''); // trim leading/trailing dashes
31
+ }
32
+
33
+ function printUsage() {
34
+ process.stdout.write(
35
+ [
36
+ 'Usage: node src/scripts/wds-add-object.js --page <path> --section <name> --object <name> [options]',
37
+ '',
38
+ 'Required:',
39
+ ' --page Path to the page spec .md file',
40
+ ' --section Section name (e.g. "Hero")',
41
+ ' --object Object name (e.g. "Primary Headline")',
42
+ '',
43
+ 'Optional:',
44
+ ' --component Component name (default: "—")',
45
+ ' --translation Translation key (auto-derived if omitted)',
46
+ ' --se Swedish text content',
47
+ ' --en English text content',
48
+ ' --behavior Behavior description (e.g. "onClick: submit form")',
49
+ ' --component-path Path to component file (default: "—")',
50
+ '',
51
+ ].join('\n'),
52
+ );
53
+ }
54
+
55
+ // Derive page slug from file path: "01-start/01-start.md" -> "01-start"
56
+ function pageSlugFromPath(filePath) {
57
+ const base = path.basename(filePath, '.md');
58
+ return base;
59
+ }
60
+
61
+ // Derive Object ID: pageSlug + sectionSlug + objectSlug
62
+ // e.g. page=01-start, section=Hero, object=Primary Headline -> 01-start-hero-primary-headline
63
+ function deriveObjectId(pageSlug, sectionName, objectName) {
64
+ // Strip leading page number from pageSlug for ID prefix
65
+ // "01-start" -> "start", "02-signup" -> "signup"
66
+ const slugParts = pageSlug.split('-');
67
+ const pagePrefix = slugParts.length > 1 ? slugParts.slice(1).join('-') : pageSlug;
68
+ const sectionSlug = toSlug(sectionName);
69
+ const objectSlug = toSlug(objectName);
70
+ return `${pagePrefix}-${sectionSlug}-${objectSlug}`;
71
+ }
72
+
73
+ function buildObjectBlock({ objectName, objectId, component, componentPath, translationKey, se, en, behavior }) {
74
+ const compDisplay = componentPath && componentPath !== '—' ? `[${component}](${componentPath})` : component || '—';
75
+
76
+ const lines = [
77
+ `#### ${objectName}`,
78
+ '',
79
+ `**OBJECT ID:** \`${objectId}\``,
80
+ '',
81
+ '| Property | Value |',
82
+ '|----------|-------|',
83
+ `| Component | ${compDisplay} |`,
84
+ `| Translation Key | \`${translationKey}\` |`,
85
+ `| SE | "${se || '—'}" |`,
86
+ `| EN | "${en || '—'}" |`,
87
+ `| Behavior | ${behavior || '—'} |`,
88
+ '',
89
+ ];
90
+
91
+ return lines.join('\n');
92
+ }
93
+
94
+ // Insert content after a section heading. Creates the section heading if it doesn't exist.
95
+ function insertUnderSection(content, sectionHeading, objectBlock) {
96
+ const lines = content.split('\n');
97
+ const headingLine = `### Section: ${sectionHeading}`;
98
+ const headingIdx = lines.findIndex((l) => l.trim() === headingLine);
99
+
100
+ if (headingIdx === -1) {
101
+ // Section doesn't exist — append it before the next ## heading after ## Page Sections
102
+ const pageSectionsIdx = lines.findIndex((l) => l.trim() === '## Page Sections');
103
+ if (pageSectionsIdx === -1) {
104
+ // Just append at end before last nav row
105
+ return content + `\n${headingLine}\n\n${objectBlock}\n`;
106
+ }
107
+
108
+ // Find end of ## Page Sections block
109
+ let insertIdx = pageSectionsIdx + 1;
110
+ while (insertIdx < lines.length) {
111
+ const t = lines[insertIdx].trim();
112
+ if (t.startsWith('## ') || t === '---') break;
113
+ insertIdx++;
114
+ }
115
+
116
+ const before = lines.slice(0, insertIdx);
117
+ const after = lines.slice(insertIdx);
118
+ return [...before, '', headingLine, '', objectBlock, ...after].join('\n');
119
+ } else {
120
+ // Find the end of this section (next ### or ## or end of file)
121
+ let insertIdx = headingIdx + 1;
122
+ // Skip blank lines after heading
123
+ while (insertIdx < lines.length && lines[insertIdx].trim() === '') insertIdx++;
124
+ // Skip comment lines
125
+ while (insertIdx < lines.length && lines[insertIdx].trim().startsWith('<!--')) insertIdx++;
126
+
127
+ // Find end of section
128
+ let endIdx = insertIdx;
129
+ while (endIdx < lines.length) {
130
+ const t = lines[endIdx].trim();
131
+ if (t.startsWith('### ') || t.startsWith('## ') || t === '---') break;
132
+ endIdx++;
133
+ }
134
+
135
+ // Insert object block before end of section
136
+ const before = lines.slice(0, endIdx);
137
+ const after = lines.slice(endIdx);
138
+ return [...before, '', objectBlock, ...after].join('\n');
139
+ }
140
+ }
141
+
142
+ function main() {
143
+ const args = parseArgs(process.argv.slice(2));
144
+
145
+ if (args.help) {
146
+ printUsage();
147
+ process.exit(0);
148
+ }
149
+
150
+ if (!args.page || !args.section || !args.object) {
151
+ process.stderr.write('Error: --page, --section, and --object are required.\n\n');
152
+ printUsage();
153
+ process.exit(1);
154
+ }
155
+
156
+ const filePath = path.resolve(args.page);
157
+
158
+ if (!fs.existsSync(filePath)) {
159
+ process.stderr.write(`Error: File not found: ${filePath}\n`);
160
+ process.exit(1);
161
+ }
162
+
163
+ const pageSlug = pageSlugFromPath(filePath);
164
+ const objectId = deriveObjectId(pageSlug, args.section, args.object);
165
+
166
+ // Auto-derive translation key from objectId
167
+ const translationKey = args.translation || objectId.replaceAll('-', '.');
168
+
169
+ const objectBlock = buildObjectBlock({
170
+ objectName: args.object,
171
+ objectId,
172
+ component: args.component || '—',
173
+ componentPath: args['component-path'] || '—',
174
+ translationKey,
175
+ se: args.se || '',
176
+ en: args.en || '',
177
+ behavior: args.behavior || '—',
178
+ });
179
+
180
+ let content;
181
+ try {
182
+ content = fs.readFileSync(filePath, 'utf8');
183
+ } catch (error) {
184
+ process.stderr.write(`Error reading file: ${error.message}\n`);
185
+ process.exit(1);
186
+ }
187
+
188
+ // Check for duplicate object ID
189
+ if (content.includes(`\`${objectId}\``)) {
190
+ process.stderr.write(`Error: Object ID already exists in file: ${objectId}\n`);
191
+ process.exit(1);
192
+ }
193
+
194
+ const updated = insertUnderSection(content, args.section, objectBlock);
195
+
196
+ try {
197
+ fs.writeFileSync(filePath, updated, 'utf8');
198
+ } catch (error) {
199
+ process.stderr.write(`Error writing file: ${error.message}\n`);
200
+ process.exit(1);
201
+ }
202
+
203
+ process.stdout.write(`✓ Added object ${objectId}\n`);
204
+ process.stdout.write(` File: ${filePath}\n`);
205
+ }
206
+
207
+ main();
@@ -0,0 +1,158 @@
1
+ // wds-add-spacing.js — WDS scaffold: append a spacing object to a page spec file
2
+ // Usage: node src/scripts/wds-add-spacing.js --page "C-UX-Scenarios/01-onboarding/01-start/01-start.md" \
3
+ // --direction v --type space --size xl --reason "major section boundary between hero and features"
4
+
5
+ 'use strict';
6
+
7
+ const fs = require('node:fs');
8
+ const path = require('node:path');
9
+
10
+ function parseArgs(argv) {
11
+ const args = {};
12
+ for (let i = 0; i < argv.length; i++) {
13
+ if (argv[i].startsWith('--')) {
14
+ const key = argv[i].slice(2);
15
+ const value = argv[i + 1] && !argv[i + 1].startsWith('--') ? argv[i + 1] : true;
16
+ args[key] = value;
17
+ if (value !== true) i++;
18
+ }
19
+ }
20
+ return args;
21
+ }
22
+
23
+ function printUsage() {
24
+ process.stdout.write(
25
+ [
26
+ 'Usage: node src/scripts/wds-add-spacing.js --page <path> --direction <v|h> --type <type> --size <size> [options]',
27
+ '',
28
+ 'Required:',
29
+ ' --page Path to the page spec .md file',
30
+ ' --direction v (vertical) or h (horizontal)',
31
+ ' --type space | separator | line',
32
+ ' --size zero | sm | md | lg | xl | 2xl | 3xl | flex',
33
+ '',
34
+ 'Optional:',
35
+ ' --reason Why this spacing exists',
36
+ '',
37
+ 'Valid directions: v, h',
38
+ 'Valid types: space, separator, line',
39
+ 'Valid sizes: zero, sm, md, lg, xl, 2xl, 3xl, flex',
40
+ '',
41
+ ].join('\n'),
42
+ );
43
+ }
44
+
45
+ const VALID_DIRECTIONS = ['v', 'h'];
46
+ const VALID_TYPES = ['space', 'separator', 'line'];
47
+ const VALID_SIZES = ['zero', 'sm', 'md', 'lg', 'xl', '2xl', '3xl', 'flex'];
48
+
49
+ // Derive page prefix from slug: "01-start" -> "start"
50
+ function pagePrefix(slug) {
51
+ const parts = slug.split('-');
52
+ return parts.length > 1 ? parts.slice(1).join('-') : slug;
53
+ }
54
+
55
+ function pageSlugFromPath(filePath) {
56
+ return path.basename(filePath, '.md');
57
+ }
58
+
59
+ function buildSpacingBlock(spacingId, reason) {
60
+ const icon = '↕';
61
+ const reasonText = reason ? ` — ${reason}` : '';
62
+ return `#### ${icon} \`${spacingId}\`${reasonText}\n`;
63
+ }
64
+
65
+ function appendToSpacingSection(content, spacingBlock) {
66
+ const lines = content.split('\n');
67
+ const spacingIdx = lines.findIndex((l) => l.trim() === '## Spacing');
68
+
69
+ if (spacingIdx === -1) {
70
+ // No spacing section — append before first ## after metadata
71
+ return content + `\n## Spacing\n\n${spacingBlock}\n`;
72
+ }
73
+
74
+ // Find end of spacing section (next ## or ---)
75
+ let endIdx = spacingIdx + 1;
76
+ while (endIdx < lines.length) {
77
+ const t = lines[endIdx].trim();
78
+ if ((t.startsWith('## ') && t !== '## Spacing') || t === '---') break;
79
+ endIdx++;
80
+ }
81
+
82
+ // Insert just before the end marker
83
+ const before = lines.slice(0, endIdx);
84
+ const after = lines.slice(endIdx);
85
+ return [...before, spacingBlock, ...after].join('\n');
86
+ }
87
+
88
+ function main() {
89
+ const args = parseArgs(process.argv.slice(2));
90
+
91
+ if (args.help) {
92
+ printUsage();
93
+ process.exit(0);
94
+ }
95
+
96
+ if (!args.page || !args.direction || !args.type || args.size === 0) {
97
+ process.stderr.write('Error: --page, --direction, --type, and --size are required.\n\n');
98
+ printUsage();
99
+ process.exit(1);
100
+ }
101
+
102
+ if (!VALID_DIRECTIONS.includes(args.direction)) {
103
+ process.stderr.write(`Error: Invalid direction "${args.direction}". Must be: ${VALID_DIRECTIONS.join(', ')}\n`);
104
+ process.exit(1);
105
+ }
106
+
107
+ if (!VALID_TYPES.includes(args.type)) {
108
+ process.stderr.write(`Error: Invalid type "${args.type}". Must be: ${VALID_TYPES.join(', ')}\n`);
109
+ process.exit(1);
110
+ }
111
+
112
+ if (!VALID_SIZES.includes(args.size)) {
113
+ process.stderr.write(`Error: Invalid size "${args.size}". Must be: ${VALID_SIZES.join(', ')}\n`);
114
+ process.exit(1);
115
+ }
116
+
117
+ const filePath = path.resolve(args.page);
118
+
119
+ if (!fs.existsSync(filePath)) {
120
+ process.stderr.write(`Error: File not found: ${filePath}\n`);
121
+ process.exit(1);
122
+ }
123
+
124
+ const slug = pageSlugFromPath(filePath);
125
+ const prefix = pagePrefix(slug);
126
+ const spacingId = `${prefix}-${args.direction}-${args.type}-${args.size}`;
127
+ const reason = args.reason || '';
128
+
129
+ let content;
130
+ try {
131
+ content = fs.readFileSync(filePath, 'utf8');
132
+ } catch (error) {
133
+ process.stderr.write(`Error reading file: ${error.message}\n`);
134
+ process.exit(1);
135
+ }
136
+
137
+ // Check for duplicate spacing ID
138
+ if (content.includes(`\`${spacingId}\``)) {
139
+ process.stderr.write(`Error: Spacing ID already exists in file: ${spacingId}\n`);
140
+ process.stderr.write('Use a different combination of direction/type/size or manually edit the file.\n');
141
+ process.exit(1);
142
+ }
143
+
144
+ const spacingBlock = buildSpacingBlock(spacingId, reason);
145
+ const updated = appendToSpacingSection(content, spacingBlock);
146
+
147
+ try {
148
+ fs.writeFileSync(filePath, updated, 'utf8');
149
+ } catch (error) {
150
+ process.stderr.write(`Error writing file: ${error.message}\n`);
151
+ process.exit(1);
152
+ }
153
+
154
+ process.stdout.write(`✓ Added spacing object ${spacingId}\n`);
155
+ process.stdout.write(` File: ${filePath}\n`);
156
+ }
157
+
158
+ main();