@protoyard/blueprint 0.0.0-stage → 1.1.1

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 (168) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1145 -2
  3. package/dist/aggregate.d.ts +83 -0
  4. package/dist/aggregate.js +268 -0
  5. package/dist/app-model.d.ts +76 -0
  6. package/dist/app-model.js +287 -0
  7. package/dist/archetypes/index.d.ts +1 -0
  8. package/dist/archetypes/index.js +1 -0
  9. package/dist/archetypes/schema.d.ts +126 -0
  10. package/dist/archetypes/schema.js +77 -0
  11. package/dist/audit.d.ts +52 -0
  12. package/dist/audit.js +149 -0
  13. package/dist/authoring/deck-lint.d.ts +32 -0
  14. package/dist/authoring/deck-lint.js +567 -0
  15. package/dist/authoring/index.d.ts +1 -0
  16. package/dist/authoring/index.js +1 -0
  17. package/dist/authoring/select-deck-layouts.d.ts +50 -0
  18. package/dist/authoring/select-deck-layouts.js +213 -0
  19. package/dist/authoring-apis.d.ts +13 -0
  20. package/dist/authoring-apis.js +13 -0
  21. package/dist/authoring-validation.d.ts +65 -0
  22. package/dist/authoring-validation.js +267 -0
  23. package/dist/avatar.d.ts +25 -0
  24. package/dist/avatar.js +30 -0
  25. package/dist/brand.d.ts +70 -0
  26. package/dist/brand.js +294 -0
  27. package/dist/brief/archetypes.d.ts +28 -0
  28. package/dist/brief/archetypes.js +798 -0
  29. package/dist/brief/content-synthesize.d.ts +31 -0
  30. package/dist/brief/content-synthesize.js +210 -0
  31. package/dist/brief/deck-outlines.d.ts +96 -0
  32. package/dist/brief/deck-outlines.js +561 -0
  33. package/dist/brief/draft-bodies.d.ts +29 -0
  34. package/dist/brief/draft-bodies.js +224 -0
  35. package/dist/brief/fields.d.ts +19 -0
  36. package/dist/brief/fields.js +229 -0
  37. package/dist/brief/index.d.ts +8 -0
  38. package/dist/brief/index.js +8 -0
  39. package/dist/brief/kind-detection.d.ts +69 -0
  40. package/dist/brief/kind-detection.js +353 -0
  41. package/dist/brief/limits.d.ts +117 -0
  42. package/dist/brief/limits.js +217 -0
  43. package/dist/brief/page-doc-outlines.d.ts +80 -0
  44. package/dist/brief/page-doc-outlines.js +485 -0
  45. package/dist/brief/synthesize.d.ts +59 -0
  46. package/dist/brief/synthesize.js +511 -0
  47. package/dist/brief/text.d.ts +13 -0
  48. package/dist/brief/text.js +59 -0
  49. package/dist/canvas-screen.d.ts +31 -0
  50. package/dist/canvas-screen.js +33 -0
  51. package/dist/canvas-seed.d.ts +58 -0
  52. package/dist/canvas-seed.js +82 -0
  53. package/dist/computed/evaluate.d.ts +11 -0
  54. package/dist/computed/evaluate.js +101 -0
  55. package/dist/computed/index.d.ts +4 -0
  56. package/dist/computed/index.js +3 -0
  57. package/dist/computed/public.d.ts +14 -0
  58. package/dist/computed/public.js +19 -0
  59. package/dist/computed/schema.d.ts +10 -0
  60. package/dist/computed/schema.js +140 -0
  61. package/dist/computed/types.d.ts +70 -0
  62. package/dist/computed/types.js +1 -0
  63. package/dist/computed/validate.d.ts +6 -0
  64. package/dist/computed/validate.js +146 -0
  65. package/dist/csv-guard.d.ts +15 -0
  66. package/dist/csv-guard.js +29 -0
  67. package/dist/csv.d.ts +85 -0
  68. package/dist/csv.js +288 -0
  69. package/dist/files.d.ts +35 -0
  70. package/dist/files.js +246 -0
  71. package/dist/form-schema.d.ts +124 -0
  72. package/dist/form-schema.js +300 -0
  73. package/dist/generator-version.d.ts +11 -0
  74. package/dist/generator-version.js +51 -0
  75. package/dist/generator.d.ts +34 -0
  76. package/dist/generator.js +445 -0
  77. package/dist/identity.d.ts +34 -0
  78. package/dist/identity.js +91 -0
  79. package/dist/import/csv-shape.d.ts +40 -0
  80. package/dist/import/csv-shape.js +265 -0
  81. package/dist/import/json-shape.d.ts +55 -0
  82. package/dist/import/json-shape.js +491 -0
  83. package/dist/import/limits.d.ts +13 -0
  84. package/dist/import/limits.js +14 -0
  85. package/dist/import/sqlite-schema.d.ts +36 -0
  86. package/dist/import/sqlite-schema.js +729 -0
  87. package/dist/index.d.ts +60 -0
  88. package/dist/index.js +58 -0
  89. package/dist/kinds/anchors.d.ts +18 -0
  90. package/dist/kinds/anchors.js +46 -0
  91. package/dist/kinds/bodies.d.ts +6 -0
  92. package/dist/kinds/bodies.js +32 -0
  93. package/dist/kinds/brand-runtime.d.ts +19 -0
  94. package/dist/kinds/brand-runtime.js +35 -0
  95. package/dist/kinds/deck.d.ts +8 -0
  96. package/dist/kinds/deck.js +34 -0
  97. package/dist/kinds/doc.d.ts +19 -0
  98. package/dist/kinds/doc.js +41 -0
  99. package/dist/kinds/embed/index.d.ts +39 -0
  100. package/dist/kinds/embed/index.js +91 -0
  101. package/dist/kinds/envelope.d.ts +159 -0
  102. package/dist/kinds/envelope.js +78 -0
  103. package/dist/kinds/index.d.ts +11 -0
  104. package/dist/kinds/index.js +11 -0
  105. package/dist/kinds/kind.d.ts +36 -0
  106. package/dist/kinds/kind.js +55 -0
  107. package/dist/kinds/limits.d.ts +64 -0
  108. package/dist/kinds/limits.js +190 -0
  109. package/dist/kinds/page-doc-runtime.d.ts +37 -0
  110. package/dist/kinds/page-doc-runtime.js +55 -0
  111. package/dist/kinds/page.d.ts +16 -0
  112. package/dist/kinds/page.js +67 -0
  113. package/dist/kinds/parse.d.ts +18 -0
  114. package/dist/kinds/parse.js +27 -0
  115. package/dist/kinds/runtime.d.ts +128 -0
  116. package/dist/kinds/runtime.js +206 -0
  117. package/dist/kinds/services.d.ts +49 -0
  118. package/dist/kinds/services.js +249 -0
  119. package/dist/kinds/testing.d.ts +9 -0
  120. package/dist/kinds/testing.js +16 -0
  121. package/dist/migrations.d.ts +97 -0
  122. package/dist/migrations.js +351 -0
  123. package/dist/motion.d.ts +50 -0
  124. package/dist/motion.js +52 -0
  125. package/dist/notifications.d.ts +19 -0
  126. package/dist/notifications.js +177 -0
  127. package/dist/patch.d.ts +122 -0
  128. package/dist/patch.js +322 -0
  129. package/dist/policy.d.ts +23 -0
  130. package/dist/policy.js +105 -0
  131. package/dist/runtime-definition.d.ts +39 -0
  132. package/dist/runtime-definition.js +74 -0
  133. package/dist/scenarios.d.ts +9 -0
  134. package/dist/scenarios.js +49 -0
  135. package/dist/schema/blueprint.schema.json +10191 -0
  136. package/dist/schema-json.d.ts +6 -0
  137. package/dist/schema-json.js +36 -0
  138. package/dist/schema.d.ts +6348 -0
  139. package/dist/schema.js +1269 -0
  140. package/dist/search.d.ts +16 -0
  141. package/dist/search.js +96 -0
  142. package/dist/services.d.ts +89 -0
  143. package/dist/services.js +898 -0
  144. package/dist/share.d.ts +78 -0
  145. package/dist/share.js +234 -0
  146. package/dist/sheet-screen.d.ts +49 -0
  147. package/dist/sheet-screen.js +91 -0
  148. package/dist/storage.d.ts +43 -0
  149. package/dist/storage.js +179 -0
  150. package/dist/stream.d.ts +26 -0
  151. package/dist/stream.js +173 -0
  152. package/dist/untrusted.d.ts +63 -0
  153. package/dist/untrusted.js +502 -0
  154. package/dist/validation-tasks.d.ts +178 -0
  155. package/dist/validation-tasks.js +199 -0
  156. package/dist/validation.d.ts +35 -0
  157. package/dist/validation.js +312 -0
  158. package/dist/variants.d.ts +112 -0
  159. package/dist/variants.js +283 -0
  160. package/dist/workflows/index.d.ts +3 -0
  161. package/dist/workflows/index.js +3 -0
  162. package/dist/workflows/schema.d.ts +50 -0
  163. package/dist/workflows/schema.js +34 -0
  164. package/dist/workflows/transition.d.ts +15 -0
  165. package/dist/workflows/transition.js +25 -0
  166. package/dist/workflows/validate.d.ts +17 -0
  167. package/dist/workflows/validate.js +140 -0
  168. package/package.json +120 -3
@@ -0,0 +1,224 @@
1
+ const PAGE_ROLES = new Set(['hero', 'body', 'footer', 'cta', 'features', 'pricing', 'faq']);
2
+ /** Lowercase kebab id for document/section anchors (BlockIdSchema). */
3
+ export function draftSlugId(value, fallback) {
4
+ const slug = value
5
+ .toLowerCase()
6
+ .replace(/[^a-z0-9]+/g, '-')
7
+ .replace(/^-+|-+$/g, '')
8
+ .slice(0, 48);
9
+ if (slug && /^[a-z]/.test(slug))
10
+ return slug;
11
+ return fallback;
12
+ }
13
+ function mapPageRole(role) {
14
+ if (PAGE_ROLES.has(role))
15
+ return role;
16
+ return 'body';
17
+ }
18
+ /**
19
+ * Convert outline draft blocks into strict block objects the pages schema accepts.
20
+ * `defaultHref` is a resolved in-document fragment (`#section-id`) for CTA actions.
21
+ */
22
+ export function outlineBlocksToSchema(blocks, defaultHref = '#section-hero') {
23
+ const out = [];
24
+ const safeDefault = defaultHref.startsWith('#') ? defaultHref : `#${defaultHref}`;
25
+ for (const block of blocks) {
26
+ switch (block.type) {
27
+ case 'heading':
28
+ out.push({
29
+ type: 'heading',
30
+ text: block.text.slice(0, 200),
31
+ level: block.level ?? 2,
32
+ });
33
+ break;
34
+ case 'text':
35
+ out.push({ type: 'text', markdown: block.markdown.slice(0, 4_000) });
36
+ break;
37
+ case 'list':
38
+ out.push({
39
+ type: 'list',
40
+ style: block.style === 'check' ? 'check' : block.style === 'number' ? 'number' : undefined,
41
+ items: block.items
42
+ .slice(0, 12)
43
+ .map((item) => ({ text: String(item).slice(0, 600) }))
44
+ .filter((item) => item.text.length > 0),
45
+ });
46
+ break;
47
+ case 'cta': {
48
+ const raw = block.href && block.href.length > 0 ? block.href : safeDefault;
49
+ // Only keep in-page fragments; external http(s) is fine for schema fragment checks.
50
+ const href = raw.startsWith('#') || /^https?:\/\//i.test(raw) || raw.startsWith('mailto:')
51
+ ? raw
52
+ : safeDefault;
53
+ out.push({
54
+ type: 'cta',
55
+ actions: [
56
+ {
57
+ label: block.label.slice(0, 60),
58
+ href: href.slice(0, 512),
59
+ },
60
+ ],
61
+ });
62
+ break;
63
+ }
64
+ case 'stat':
65
+ out.push({
66
+ type: 'stat',
67
+ items: block.items.slice(0, 6).map((item) => ({
68
+ value: String(item.value).slice(0, 32),
69
+ label: String(item.label).slice(0, 80),
70
+ })),
71
+ });
72
+ break;
73
+ case 'quote':
74
+ out.push({
75
+ type: 'quote',
76
+ quote: block.quote.slice(0, 280),
77
+ ...(block.cite ? { cite: block.cite.slice(0, 120) } : {}),
78
+ });
79
+ break;
80
+ case 'callout':
81
+ out.push({
82
+ type: 'callout',
83
+ body: block.body.slice(0, 1_000),
84
+ ...(block.title ? { title: block.title.slice(0, 80) } : {}),
85
+ });
86
+ break;
87
+ case 'divider':
88
+ out.push({
89
+ type: 'divider',
90
+ ...(block.label ? { label: block.label.slice(0, 80) } : {}),
91
+ });
92
+ break;
93
+ default:
94
+ break;
95
+ }
96
+ }
97
+ return out.filter((block) => {
98
+ if (block.type === 'list') {
99
+ const items = block.items;
100
+ return Array.isArray(items) && items.length > 0;
101
+ }
102
+ if (block.type === 'stat') {
103
+ const items = block.items;
104
+ return Array.isArray(items) && items.length > 0;
105
+ }
106
+ if (block.type === 'cta') {
107
+ const actions = block.actions;
108
+ return Array.isArray(actions) && actions.length > 0;
109
+ }
110
+ return true;
111
+ });
112
+ }
113
+ /** Page body: id + title + sections of valid blocks (no draft-only keys). */
114
+ export function pageBodyFromOutline(input) {
115
+ const used = new Set();
116
+ const sectionIds = input.sections.map((section, index) => {
117
+ let id = draftSlugId(section.id || section.title, `section-${index + 1}`);
118
+ if (used.has(id))
119
+ id = `${id}-${index + 1}`;
120
+ used.add(id);
121
+ return id;
122
+ });
123
+ const firstHref = `#${sectionIds[0] ?? 'section-1'}`;
124
+ const sections = input.sections.map((section, index) => {
125
+ const id = sectionIds[index] ?? `section-${index + 1}`;
126
+ const blocks = outlineBlocksToSchema(section.blocks, firstHref);
127
+ // Rewrite unknown in-page fragments to the first section so fragment lint passes.
128
+ for (const block of blocks) {
129
+ if (block.type !== 'cta' || !Array.isArray(block.actions))
130
+ continue;
131
+ for (const action of block.actions) {
132
+ if (typeof action.href === 'string' && action.href.startsWith('#')) {
133
+ const target = action.href.slice(1);
134
+ if (!used.has(target))
135
+ action.href = firstHref;
136
+ }
137
+ }
138
+ }
139
+ const role = mapPageRole(section.role);
140
+ return {
141
+ id,
142
+ ...(section.title ? { title: section.title.slice(0, 120) } : {}),
143
+ ...(role ? { role } : {}),
144
+ blocks: blocks.length > 0 ? blocks : [{ type: 'text', markdown: section.body.slice(0, 600) }],
145
+ };
146
+ });
147
+ return {
148
+ id: draftSlugId(input.id, 'page-draft'),
149
+ title: input.title.slice(0, 120) || 'Page draft',
150
+ ...(input.description ? { description: input.description.slice(0, 500) } : {}),
151
+ sections,
152
+ };
153
+ }
154
+ /**
155
+ * Doc body: continuous block flow. Section titles become headings with stable ids
156
+ * so a TOC has entries and fragment targets stay unique.
157
+ */
158
+ export function docBodyFromOutline(input) {
159
+ const blocks = [];
160
+ const used = new Set();
161
+ for (const section of input.sections) {
162
+ let headingId = draftSlugId(section.id || section.title, 'section');
163
+ if (used.has(headingId))
164
+ headingId = `${headingId}-${used.size + 1}`;
165
+ used.add(headingId);
166
+ const level = section.role === 'title' ? 1 : 2;
167
+ blocks.push({
168
+ type: 'heading',
169
+ id: headingId,
170
+ level,
171
+ text: section.title.slice(0, 200) || headingId,
172
+ });
173
+ for (const block of outlineBlocksToSchema(section.blocks, `#${headingId}`)) {
174
+ if (block.type === 'heading' &&
175
+ typeof block.text === 'string' &&
176
+ block.text === section.title &&
177
+ (block.level === 1 || block.level === 2)) {
178
+ continue;
179
+ }
180
+ if (block.type === 'cta' && Array.isArray(block.actions)) {
181
+ for (const action of block.actions) {
182
+ if (typeof action.href === 'string' && action.href.startsWith('#')) {
183
+ const target = action.href.slice(1);
184
+ if (!used.has(target))
185
+ action.href = `#${headingId}`;
186
+ }
187
+ }
188
+ }
189
+ if (block.type === 'heading' && typeof block.id === 'string') {
190
+ let id = block.id;
191
+ if (used.has(id))
192
+ id = `${id}-${used.size + 1}`;
193
+ used.add(id);
194
+ blocks.push({ ...block, id });
195
+ }
196
+ else {
197
+ blocks.push(block);
198
+ }
199
+ }
200
+ }
201
+ if (blocks.length === 0) {
202
+ blocks.push({ type: 'heading', id: 'title', level: 1, text: input.title.slice(0, 200) });
203
+ blocks.push({ type: 'text', markdown: 'Add the document body here.' });
204
+ }
205
+ const hasTocHeading = blocks.some((block) => block.type === 'heading' &&
206
+ typeof block.level === 'number' &&
207
+ block.level >= 2 &&
208
+ block.level <= 3);
209
+ if (!hasTocHeading) {
210
+ blocks.push({
211
+ type: 'heading',
212
+ id: 'summary',
213
+ level: 2,
214
+ text: 'Summary',
215
+ });
216
+ }
217
+ return {
218
+ id: draftSlugId(input.id, 'doc-draft'),
219
+ title: input.title.slice(0, 120) || 'Document draft',
220
+ ...(input.description ? { description: input.description.slice(0, 500) } : {}),
221
+ toc: { depth: 3, sticky: true },
222
+ blocks,
223
+ };
224
+ }
@@ -0,0 +1,19 @@
1
+ import type { FieldInput } from './archetypes.ts';
2
+ /** A field for a phrase from a brief, or null when nothing usable remains. */
3
+ export declare function fieldFromPhrase(phrase: string): {
4
+ field: FieldInput;
5
+ cue: string | null;
6
+ } | null;
7
+ /** Brief-wide cues that add a field to the focus entity when it lacks that kind. */
8
+ export declare const GLOBAL_CUES: readonly {
9
+ kind: string;
10
+ cues: readonly string[];
11
+ field: FieldInput;
12
+ }[];
13
+ /**
14
+ * Entity names that describe people. They get an `avatar` field (a face from
15
+ * the avatar library), so generated lists and details show real-looking people.
16
+ */
17
+ export declare const PEOPLE_ENTITIES: ReadonlySet<string>;
18
+ /** The avatar field added to people-like entities: `avatar`, or `photo` when the name is taken. */
19
+ export declare function avatarFieldFor(fieldNames: readonly string[]): FieldInput | null;
@@ -0,0 +1,229 @@
1
+ import { camel } from "./text.js";
2
+ const enumField = (name, values) => ({ name, kind: 'enum', values, default: values[0], required: true });
3
+ const ENUM_VALUES = {
4
+ status: ['new', 'active', 'done'],
5
+ state: ['new', 'active', 'done'],
6
+ stage: ['lead', 'qualified', 'won', 'lost'],
7
+ priority: ['low', 'normal', 'high', 'urgent'],
8
+ severity: ['low', 'medium', 'high', 'critical'],
9
+ level: ['beginner', 'intermediate', 'advanced'],
10
+ type: ['standard', 'premium', 'other'],
11
+ category: ['general', 'special', 'other'],
12
+ kind: ['standard', 'premium', 'other'],
13
+ };
14
+ const RULES = [
15
+ {
16
+ cues: ['location', 'address', 'place', 'coordinates', 'map', 'site', 'gps'],
17
+ build: (name) => ({ name, kind: 'geo' }),
18
+ },
19
+ {
20
+ cues: [
21
+ 'owner',
22
+ 'assignee',
23
+ 'assigned',
24
+ 'manager',
25
+ 'responsible',
26
+ 'agent',
27
+ 'author',
28
+ 'reviewer',
29
+ ],
30
+ build: (name) => ({ name, kind: 'persona' }),
31
+ },
32
+ {
33
+ cues: ['time', 'at', 'hour', 'timestamp'],
34
+ build: (name) => ({ name, kind: 'datetime', window: 'around' }),
35
+ },
36
+ {
37
+ cues: [
38
+ 'date',
39
+ 'day',
40
+ 'deadline',
41
+ 'due',
42
+ 'when',
43
+ 'start',
44
+ 'end',
45
+ 'birthday',
46
+ 'expiry',
47
+ 'expires',
48
+ ],
49
+ build: (name) => ({ name, kind: 'date', window: 'around' }),
50
+ },
51
+ {
52
+ cues: [
53
+ 'price',
54
+ 'amount',
55
+ 'cost',
56
+ 'total',
57
+ 'budget',
58
+ 'fee',
59
+ 'revenue',
60
+ 'salary',
61
+ 'payment',
62
+ 'balance',
63
+ 'value',
64
+ ],
65
+ build: (name) => ({ name, kind: 'money', min: 0, max: 1_000_000 }),
66
+ },
67
+ {
68
+ cues: ['rating', 'score', 'percent', 'percentage', 'weight', 'distance', 'temperature'],
69
+ build: (name) => ({ name, kind: 'number', min: 0, max: 100 }),
70
+ },
71
+ {
72
+ cues: [
73
+ 'count',
74
+ 'quantity',
75
+ 'qty',
76
+ 'hours',
77
+ 'size',
78
+ 'capacity',
79
+ 'stock',
80
+ 'seats',
81
+ 'guests',
82
+ 'number',
83
+ ],
84
+ build: (name) => ({ name, kind: 'integer', min: 0, max: 500 }),
85
+ },
86
+ {
87
+ cues: Object.keys(ENUM_VALUES),
88
+ build: (name) => {
89
+ const cue = Object.keys(ENUM_VALUES).find((key) => name.toLowerCase().endsWith(key));
90
+ return enumField(name, ENUM_VALUES[cue ?? 'status']);
91
+ },
92
+ },
93
+ { cues: ['email'], build: (name) => ({ name, kind: 'email' }) },
94
+ { cues: ['phone', 'mobile'], build: (name) => ({ name, kind: 'phone' }) },
95
+ { cues: ['website', 'url', 'link'], build: (name) => ({ name, kind: 'url' }) },
96
+ { cues: ['tags', 'labels'], build: (name) => ({ name, kind: 'tags' }) },
97
+ {
98
+ cues: ['notes', 'description', 'body', 'details', 'summary', 'comments'],
99
+ build: (name) => ({ name, kind: 'longText', fake: 'lorem.paragraph' }),
100
+ },
101
+ {
102
+ cues: ['paid', 'active', 'archived', 'approved', 'verified', 'flagged'],
103
+ build: (name) => ({ name, kind: 'boolean' }),
104
+ },
105
+ ];
106
+ /** Words dropped from field phrases ("the due date" → `dueDate`). */
107
+ const FILLER = new Set([
108
+ 'the',
109
+ 'a',
110
+ 'an',
111
+ 'their',
112
+ 'its',
113
+ 'his',
114
+ 'her',
115
+ 'our',
116
+ 'your',
117
+ 'each',
118
+ 'every',
119
+ 'some',
120
+ 'of',
121
+ 'any',
122
+ ]);
123
+ /** Names the platform manages; a brief asking for one gets a suffixed name instead. */
124
+ const RESERVED = new Set(['id', 'createdAt', 'updatedAt', 'deletedAt', 'deletedBy']);
125
+ /** A field for a phrase from a brief, or null when nothing usable remains. */
126
+ export function fieldFromPhrase(phrase) {
127
+ const all = phrase.toLowerCase().match(/[a-z]+/g) ?? [];
128
+ // "a live map of delivery locations" describes a screen, not a field.
129
+ if (all.includes('of'))
130
+ return null;
131
+ const parts = all.filter((w) => !FILLER.has(w));
132
+ if (parts.length === 0 || parts.length > 3)
133
+ return null;
134
+ let name = camel(parts);
135
+ if (RESERVED.has(name))
136
+ name = `${name}Value`;
137
+ if (name.length > 40)
138
+ return null;
139
+ const rule = RULES.find((r) => r.cues.some((cue) => parts.includes(cue)));
140
+ const cue = rule ? (rule.cues.find((c) => parts.includes(c)) ?? null) : null;
141
+ return { field: rule ? rule.build(name) : { name, kind: 'text' }, cue };
142
+ }
143
+ /** Brief-wide cues that add a field to the focus entity when it lacks that kind. */
144
+ export const GLOBAL_CUES = [
145
+ {
146
+ kind: 'geo',
147
+ cues: ['location', 'locations', 'address', 'addresses', 'map', 'maps', 'nearby', 'gps'],
148
+ field: { name: 'location', kind: 'geo' },
149
+ },
150
+ {
151
+ kind: 'money',
152
+ cues: [
153
+ 'price',
154
+ 'prices',
155
+ 'pricing',
156
+ 'revenue',
157
+ 'cost',
158
+ 'costs',
159
+ 'budget',
160
+ 'invoice',
161
+ 'invoices',
162
+ 'payment',
163
+ 'payments',
164
+ 'amount',
165
+ ],
166
+ field: {
167
+ name: 'amount',
168
+ kind: 'money',
169
+ min: 0,
170
+ max: 1_000_000,
171
+ trend: 'growth',
172
+ },
173
+ },
174
+ {
175
+ kind: 'date',
176
+ cues: ['deadline', 'deadlines', 'due', 'calendar', 'schedule', 'scheduled', 'dates'],
177
+ field: { name: 'dueOn', kind: 'date', window: 'around' },
178
+ },
179
+ {
180
+ kind: 'persona',
181
+ cues: ['assign', 'assigns', 'assigned', 'assignee', 'owner', 'owners', 'team'],
182
+ field: { name: 'owner', kind: 'persona' },
183
+ },
184
+ ];
185
+ /**
186
+ * Entity names that describe people. They get an `avatar` field (a face from
187
+ * the avatar library), so generated lists and details show real-looking people.
188
+ */
189
+ export const PEOPLE_ENTITIES = new Set([
190
+ 'agent',
191
+ 'athlete',
192
+ 'attendee',
193
+ 'author',
194
+ 'candidate',
195
+ 'client',
196
+ 'coach',
197
+ 'contact',
198
+ 'courier',
199
+ 'customer',
200
+ 'doctor',
201
+ 'driver',
202
+ 'employee',
203
+ 'follower',
204
+ 'groomer',
205
+ 'guest',
206
+ 'instructor',
207
+ 'lead',
208
+ 'member',
209
+ 'mentor',
210
+ 'nurse',
211
+ 'patient',
212
+ 'player',
213
+ 'resident',
214
+ 'rider',
215
+ 'student',
216
+ 'subscriber',
217
+ 'teacher',
218
+ 'technician',
219
+ 'tenant',
220
+ 'trainer',
221
+ 'tutor',
222
+ 'volunteer',
223
+ 'worker',
224
+ ]);
225
+ /** The avatar field added to people-like entities: `avatar`, or `photo` when the name is taken. */
226
+ export function avatarFieldFor(fieldNames) {
227
+ const name = ['avatar', 'photo', 'face'].find((candidate) => !fieldNames.includes(candidate));
228
+ return name ? { name, kind: 'avatar', label: 'Photo' } : null;
229
+ }
@@ -0,0 +1,8 @@
1
+ export { ARCHETYPE_IDS, ARCHETYPES, type Archetype, GENERIC_ARCHETYPE } from './archetypes.ts';
2
+ export { type ContentBriefOptions, type ContentBriefResult, MAX_CONTENT_BRIEF_LENGTH, synthesizeContentFromBrief, synthesizeDeckFromBrief, synthesizeDocFromBrief, synthesizePageFromBrief, } from './content-synthesize.ts';
3
+ export { chooseDeckOutline, DECK_OUTLINE_IDS, DECK_OUTLINE_LIMITS, DECK_OUTLINES, type DeckOutlineId, type DeckOutlineOptions, type DeckOutlineResult, type DeckOutlineSlide, type DeckPacing, type DeckSlidePlan, deckOutlineRange, deckTitleFromBrief, requestedSlideCount, synthesizeDeckOutline, } from './deck-outlines.ts';
4
+ export { fieldFromPhrase, PEOPLE_ENTITIES } from './fields.ts';
5
+ export { APP_CUES, type BriefKindDetection, type CueStrength, type DetectKindOptions, detectBriefKind, KIND_CUES, type KindConfidence, type KindCue, } from './kind-detection.ts';
6
+ export { type ApplyOutlineBoundsResult, applyOutlineBounds, type BoundBriefInput, type BriefLimitCode, type BriefLimitIssue, boundBriefInput, checkOutlineBounds, OUTLINE_LIMITS, type OutlineBoundsInput, type OutlineLimits, type OutlineUnitInput, prepareBriefForSynthesis, } from './limits.ts';
7
+ export { buildDocOutline, buildPageOutline, DOC_OUTLINE_IDS, type DocOutlineId, type DocOutlineResult, detectPageDocOmissions, type OutlineBlock, type OutlineBuildOptions, type OutlineSection, PAGE_OUTLINE_IDS, type PageOutlineId, type PageOutlineResult, sectionIds, selectDocOutline, selectPageOutline, } from './page-doc-outlines.ts';
8
+ export { type AppBriefResult, type BriefOptions, type BriefResult, blueprintFromBrief, isAppBriefResult, isContentBriefResult, MAX_BRIEF_LENGTH, type SuggestedLayout, synthesizeAppFromBrief, } from './synthesize.ts';
@@ -0,0 +1,8 @@
1
+ export { ARCHETYPE_IDS, ARCHETYPES, GENERIC_ARCHETYPE } from "./archetypes.js";
2
+ export { MAX_CONTENT_BRIEF_LENGTH, synthesizeContentFromBrief, synthesizeDeckFromBrief, synthesizeDocFromBrief, synthesizePageFromBrief, } from "./content-synthesize.js";
3
+ export { chooseDeckOutline, DECK_OUTLINE_IDS, DECK_OUTLINE_LIMITS, DECK_OUTLINES, deckOutlineRange, deckTitleFromBrief, requestedSlideCount, synthesizeDeckOutline, } from "./deck-outlines.js";
4
+ export { fieldFromPhrase, PEOPLE_ENTITIES } from "./fields.js";
5
+ export { APP_CUES, detectBriefKind, KIND_CUES, } from "./kind-detection.js";
6
+ export { applyOutlineBounds, boundBriefInput, checkOutlineBounds, OUTLINE_LIMITS, prepareBriefForSynthesis, } from "./limits.js";
7
+ export { buildDocOutline, buildPageOutline, DOC_OUTLINE_IDS, detectPageDocOmissions, PAGE_OUTLINE_IDS, sectionIds, selectDocOutline, selectPageOutline, } from "./page-doc-outlines.js";
8
+ export { blueprintFromBrief, isAppBriefResult, isContentBriefResult, MAX_BRIEF_LENGTH, synthesizeAppFromBrief, } from "./synthesize.js";
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Brief kind detection (ADR-036): decides whether a prose brief asks for an
3
+ * app, a deck, a page or a doc before any app archetype is scored.
4
+ *
5
+ * The rules are deliberately conservative, because `app` is what every brief
6
+ * meant before kinds existed:
7
+ *
8
+ * 1. An explicit kind always wins.
9
+ * 2. A content kind is chosen only on a strong cue ("slides", "keynote",
10
+ * "landing page", "report", "one-pager"…). Weak cues ("talk", "hero",
11
+ * "article") are reported as a suggestion but never switch the kind.
12
+ * 3. A cue followed by a tool noun ("slide deck builder", "report generator")
13
+ * describes an app that makes that content, so it counts for `app`.
14
+ * 4. Strong cues for two content kinds, or an app noun ("app", "dashboard",
15
+ * "platform"…) before the first strong cue, make the brief ambiguous: the
16
+ * result is `app`, with a reason that names the conflict and how to pick.
17
+ *
18
+ * Matching is on whole lowercase words (`normalize`), longest phrase first,
19
+ * so the same brief always gives the same result.
20
+ */
21
+ import { type BlueprintKind, type ContentKind } from '../kinds/kind.ts';
22
+ export type CueStrength = 'strong' | 'weak';
23
+ /**
24
+ * - `explicit`: the caller chose the kind.
25
+ * - `high`: strong cues for one content kind and no app cue at all.
26
+ * - `medium`: strong cues for one content kind, with app words only after them
27
+ * ("slides about our CRM app").
28
+ * - `fallback`: no deciding cue, or an ambiguous brief; the kind is `app`.
29
+ */
30
+ export type KindConfidence = 'explicit' | 'high' | 'medium' | 'fallback';
31
+ export interface KindCue {
32
+ kind: BlueprintKind;
33
+ /** The phrase as matched, in normalized words (`"one pager"`). */
34
+ phrase: string;
35
+ strength: CueStrength;
36
+ /** Word offset in the normalized brief, for ordering and highlighting. */
37
+ index: number;
38
+ }
39
+ export interface BriefKindDetection {
40
+ kind: BlueprintKind;
41
+ confidence: KindConfidence;
42
+ /** True when cues pointed to more than one kind and `app` was kept. */
43
+ ambiguous: boolean;
44
+ /** One sentence a CLI or UI can show as is. */
45
+ reason: string;
46
+ /** Every cue found, in brief order. */
47
+ cues: KindCue[];
48
+ /** The content kind the cues lean to when they were not enough to switch. */
49
+ suggestion: ContentKind | null;
50
+ }
51
+ export interface DetectKindOptions {
52
+ /** An explicit kind (for example from `--kind`); overrides every cue. */
53
+ kind?: BlueprintKind;
54
+ }
55
+ interface CueList {
56
+ strong: readonly string[];
57
+ weak: readonly string[];
58
+ }
59
+ /** Content cues per kind, in normalized words ("one-pager" normalizes to "one pager"). */
60
+ export declare const KIND_CUES: Readonly<Record<ContentKind, CueList>>;
61
+ /** Nouns that name an app; before the first content cue, they keep the brief an app. */
62
+ export declare const APP_CUES: readonly string[];
63
+ /**
64
+ * Detects the kind a brief asks for. Deterministic: the same brief and options
65
+ * always give the same result. Throws a `RangeError` for an explicit kind this
66
+ * version does not know.
67
+ */
68
+ export declare function detectBriefKind(brief: string, options?: DetectKindOptions): BriefKindDetection;
69
+ export {};