@owlmeans/viable-common 0.0.33 → 0.0.35

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 (97) hide show
  1. package/README.md +3 -3
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/agent-presentation/SKILL.md +1 -1
  4. package/agent-meta/skills/viable-common/SKILL.md +93 -62
  5. package/build/ba/index.d.ts +0 -1
  6. package/build/ba/index.d.ts.map +1 -1
  7. package/build/ba/index.js +0 -1
  8. package/build/ba/index.js.map +1 -1
  9. package/build/blueprint/consts.d.ts +38 -0
  10. package/build/blueprint/consts.d.ts.map +1 -1
  11. package/build/blueprint/consts.js +40 -0
  12. package/build/blueprint/consts.js.map +1 -1
  13. package/build/blueprint/index.d.ts +0 -3
  14. package/build/blueprint/index.d.ts.map +1 -1
  15. package/build/blueprint/index.js +0 -3
  16. package/build/blueprint/index.js.map +1 -1
  17. package/build/blueprint/types.d.ts +5 -5
  18. package/build/intent/consts.d.ts +57 -0
  19. package/build/intent/consts.d.ts.map +1 -0
  20. package/build/intent/consts.js +58 -0
  21. package/build/intent/consts.js.map +1 -0
  22. package/build/intent/entrypoints.d.ts +27 -0
  23. package/build/intent/entrypoints.d.ts.map +1 -0
  24. package/build/intent/entrypoints.js +28 -0
  25. package/build/intent/entrypoints.js.map +1 -0
  26. package/build/intent/errors.d.ts +20 -0
  27. package/build/intent/errors.d.ts.map +1 -0
  28. package/build/intent/errors.js +28 -0
  29. package/build/intent/errors.js.map +1 -0
  30. package/build/intent/flow.d.ts +17 -0
  31. package/build/intent/flow.d.ts.map +1 -0
  32. package/build/intent/flow.js +65 -0
  33. package/build/intent/flow.js.map +1 -0
  34. package/build/intent/index.d.ts +7 -0
  35. package/build/intent/index.d.ts.map +1 -0
  36. package/build/intent/index.js +6 -0
  37. package/build/intent/index.js.map +1 -0
  38. package/build/intent/schemas.d.ts +10 -0
  39. package/build/intent/schemas.d.ts.map +1 -0
  40. package/build/intent/schemas.js +24 -0
  41. package/build/intent/schemas.js.map +1 -0
  42. package/build/intent/types.d.ts +40 -0
  43. package/build/intent/types.d.ts.map +1 -0
  44. package/build/intent/types.js +2 -0
  45. package/build/intent/types.js.map +1 -0
  46. package/build/scaffold/consts.d.ts +14 -1
  47. package/build/scaffold/consts.d.ts.map +1 -1
  48. package/build/scaffold/consts.js +10 -0
  49. package/build/scaffold/consts.js.map +1 -1
  50. package/build/scaffold/schemas.d.ts +7 -0
  51. package/build/scaffold/schemas.d.ts.map +1 -1
  52. package/build/scaffold/schemas.js +119 -68
  53. package/build/scaffold/schemas.js.map +1 -1
  54. package/build/scaffold/types.d.ts +62 -24
  55. package/build/scaffold/types.d.ts.map +1 -1
  56. package/build/skills/consts.d.ts +0 -34
  57. package/build/skills/consts.d.ts.map +1 -1
  58. package/build/skills/consts.js +0 -106
  59. package/build/skills/consts.js.map +1 -1
  60. package/build/skills/index.d.ts +0 -1
  61. package/build/skills/index.d.ts.map +1 -1
  62. package/build/skills/index.js +0 -1
  63. package/build/skills/index.js.map +1 -1
  64. package/build/skills/roles.d.ts +0 -9
  65. package/build/skills/roles.d.ts.map +1 -1
  66. package/build/skills/roles.js +0 -226
  67. package/build/skills/roles.js.map +1 -1
  68. package/build/ux/schemas.js +2 -2
  69. package/build/ux/schemas.js.map +1 -1
  70. package/package.json +17 -7
  71. package/src/ba/index.ts +0 -1
  72. package/src/blueprint/consts.ts +40 -0
  73. package/src/blueprint/index.ts +0 -3
  74. package/src/blueprint/types.ts +5 -5
  75. package/src/intent/consts.ts +66 -0
  76. package/src/intent/entrypoints.ts +43 -0
  77. package/src/intent/errors.ts +33 -0
  78. package/src/intent/flow.ts +67 -0
  79. package/src/intent/index.ts +6 -0
  80. package/src/intent/schemas.ts +27 -0
  81. package/src/intent/types.ts +45 -0
  82. package/src/scaffold/consts.ts +14 -1
  83. package/src/scaffold/schemas.ts +125 -69
  84. package/src/scaffold/types.ts +64 -23
  85. package/src/skills/consts.ts +0 -112
  86. package/src/skills/index.ts +0 -1
  87. package/src/skills/roles.ts +0 -267
  88. package/src/ux/schemas.ts +2 -2
  89. package/tests/blueprint.spec.ts +2 -66
  90. package/tests/intent.spec.ts +121 -0
  91. package/tests/scaffold.spec.ts +73 -13
  92. package/src/ba/schemas.ts +0 -196
  93. package/src/blueprint/case.ts +0 -320
  94. package/src/blueprint/resolve.ts +0 -136
  95. package/src/blueprint/schemas.ts +0 -44
  96. package/src/skills/catalogue.ts +0 -1864
  97. package/tests/skills.spec.ts +0 -125
@@ -0,0 +1,27 @@
1
+ import type { JSONSchemaType } from 'ajv'
2
+ import { INTENT_PROMPT_MAX, INTENT_REF_LENGTH, INTENT_REF_PATTERN } from './consts.js'
3
+ import type { IntentPickupBody, IntentStashBody } from './types.js'
4
+
5
+ /**
6
+ * The stash body. `consent` is a constant `true`, not a boolean: the data-processing confirmation is
7
+ * a precondition of the request existing, so the wire refuses its absence instead of the handler
8
+ * having to remember to look.
9
+ */
10
+ export const IntentStashBodySchema: JSONSchemaType<IntentStashBody> = {
11
+ type: 'object',
12
+ properties: {
13
+ prompt: { type: 'string', minLength: 1, maxLength: INTENT_PROMPT_MAX },
14
+ consent: { type: 'boolean', const: true },
15
+ },
16
+ required: ['prompt', 'consent'],
17
+ additionalProperties: false,
18
+ }
19
+
20
+ export const IntentPickupBodySchema: JSONSchemaType<IntentPickupBody> = {
21
+ type: 'object',
22
+ properties: {
23
+ ref: { type: 'string', minLength: INTENT_REF_LENGTH, maxLength: INTENT_REF_LENGTH, pattern: INTENT_REF_PATTERN },
24
+ },
25
+ required: ['ref'],
26
+ additionalProperties: false,
27
+ }
@@ -0,0 +1,45 @@
1
+ import type { ResourceRecord } from '@owlmeans/resource'
2
+
3
+ /** What the public site sends: the prompt and the person's confirmation of its processing. */
4
+ export interface IntentStashBody {
5
+ prompt: string
6
+ /** Always `true` — the site's checkbox; a request without it is refused by the schema. */
7
+ consent: true
8
+ }
9
+
10
+ /** The reference the platform answers with, and the moment its record expires. */
11
+ export interface IntentStashResult {
12
+ ref: string
13
+ /** Epoch milliseconds. */
14
+ expiresAt: number
15
+ }
16
+
17
+ export interface IntentPickupBody {
18
+ ref: string
19
+ }
20
+
21
+ export interface IntentPickupResult {
22
+ prompt: string
23
+ }
24
+
25
+ /**
26
+ * The prompt as it waits in the visitor's browser between pickup and the decision on the home
27
+ * screen. `expiresAt` is checked on read — browser storage has no TTL of its own.
28
+ */
29
+ export interface IntentDraft extends ResourceRecord {
30
+ id: string
31
+ /** The reference it was picked up with; a reload of the landing reuses the draft of that ref. */
32
+ ref: string
33
+ prompt: string
34
+ expiresAt: number
35
+ }
36
+
37
+ /**
38
+ * Options of {@link makeIntentProtocols}.
39
+ */
40
+ export interface IntentProtocolOptions {
41
+ /** Path prefix of the guest API; defaults to `/public/intent`. */
42
+ path?: string
43
+ /** Path of the landing screen; defaults to `/start`. */
44
+ landing?: string
45
+ }
@@ -1,4 +1,4 @@
1
- import type { BentoFragmentKind } from './types.js'
1
+ import type { BentoFragmentKind, LandingGateFieldKind } from './types.js'
2
2
 
3
3
  /**
4
4
  * The shapes a drawn widget can take.
@@ -34,3 +34,16 @@ export const AREA_SECTION_CAP = 6
34
34
  * value nothing can draw is a tile with an empty bottom.
35
35
  */
36
36
  export const BENTO_FRAGMENT_KINDS: BentoFragmentKind[] = ['list', 'note', 'people', 'steps']
37
+
38
+ /**
39
+ * The controls a landing-gate field can be — the simplest native inputs, and no picker of any
40
+ * kind. A closed set for the same reason as {@link BENTO_FRAGMENT_KINDS}: each value is one plain
41
+ * element the seed's gate knows how to draw.
42
+ */
43
+ export const LANDING_GATE_FIELD_KINDS: LandingGateFieldKind[] = ['text', 'select', 'number', 'date']
44
+
45
+ /** The most fields a landing gate carries. A form longer than this is a screen, not an entry. */
46
+ export const LANDING_GATE_MAX_FIELDS = 3
47
+
48
+ /** The options a `select` field carries: fewer is no choice, more is a list to browse. */
49
+ export const LANDING_GATE_OPTIONS = { min: 2, max: 6 } as const
@@ -1,9 +1,9 @@
1
1
  import type { JSONSchemaType } from 'ajv'
2
2
 
3
3
  import { ProjectArea } from '../areas/consts.js'
4
- import { BENTO_FRAGMENT_KINDS, WidgetKind } from './consts.js'
4
+ import { BENTO_FRAGMENT_KINDS, LANDING_GATE_FIELD_KINDS, WidgetKind } from './consts.js'
5
5
  import type {
6
- BentoFragment, GuestHomePlan, LandingGatePlan, ProductIdentity,
6
+ BentoFragment, GuestHomePlan, LandingGateAnswer, LandingGateField, LandingGatePlan, ProductIdentity,
7
7
  ScaffoldAreaPlan, ScaffoldPlan, ScaffoldStoryPlan, SketchContent,
8
8
  } from './types.js'
9
9
 
@@ -83,7 +83,8 @@ const ScaffoldStoryPlanSchema: JSONSchemaType<ScaffoldStoryPlan> = {
83
83
  screen: {
84
84
  type: 'string',
85
85
  description: 'The main screen this story will need, named "{section}/{purpose}" -'
86
- + ' e.g. "Requests/Submit a request". The section part must equal the section above'
86
+ + ' e.g. "Requests/Submit request". The purpose is one or two words, never a sentence.'
87
+ + ' The section part must equal the section above'
87
88
  },
88
89
  widget: {
89
90
  type: 'object',
@@ -225,99 +226,129 @@ const BentoFragmentSchema: JSONSchemaType<BentoFragment> = {
225
226
  additionalProperties: false,
226
227
  }
227
228
 
228
- const LandingGatePlanSchema: JSONSchemaType<LandingGatePlan> = {
229
+ const LandingGateFieldSchema: JSONSchemaType<LandingGateField> = {
229
230
  type: 'object',
230
- title: 'LandingGatePlan',
231
- description: 'The working entry into the key end-user workflow, drawn in the hero instead of the'
232
- + ' call-to-action buttons: a guest makes a few picks, sees sample results ranked against them,'
233
- + ' and signs in to continue with the picks carried over. Every text is in the end user\'s words',
231
+ title: 'LandingGateField',
232
+ description: 'One simple, non-sensitive field of the landing gate',
234
233
  properties: {
235
- story: {
234
+ name: {
236
235
  type: 'string',
237
- description: 'The code of the landing story, copied EXACTLY as given to you. Never shown'
236
+ description: 'The camelCase key its value travels under, one word - e.g. "dish"'
238
237
  },
239
- target: {
240
- type: 'string', nullable: true,
241
- description: 'Leave it out - the platform fills it'
242
- },
243
- question: {
238
+ label: {
244
239
  type: 'string',
245
- description: 'The question the gate asks, 3-6 words, in the end user\'s words - e.g. "What\'s'
246
- + ' in your pantry?"'
240
+ description: 'The visible label in the end user\'s words, 2-5 words - e.g. "What are you baking?"'
247
241
  },
248
- hint: { type: 'string', description: 'A short reassurance beside it, e.g. "No account needed"' },
249
- label: {
242
+ kind: {
250
243
  type: 'string',
251
- description: 'The accessible name of the group of choices, 2-3 words - e.g. "Your pantry"'
244
+ enum: LANDING_GATE_FIELD_KINDS,
245
+ description: 'text (a short line, the default), select (ONE choice out of 2-6 plain options),'
246
+ + ' number, or date. Nothing else exists: no chips, no toggles, no multiple choice'
252
247
  },
253
- inputs: {
254
- type: 'array',
255
- items: { type: 'string' },
256
- description: '5 to 8 things a guest can pick, 1-3 words each, from the story\'s own domain.'
257
- + ' Nothing personal, nothing sensitive, nothing that needs an account'
248
+ placeholder: {
249
+ type: 'string', nullable: true,
250
+ description: 'text or number only: an example value, e.g. "Rye sourdough". Never personal'
258
251
  },
259
- selected: {
260
- type: 'array',
252
+ options: {
253
+ type: 'array', nullable: true,
261
254
  items: { type: 'string' },
262
- description: '4 or 5 of the inputs above, copied exactly, pre-selected so the first view'
263
- + ' already shows results'
255
+ description: 'select only: 2 to 6 plain options, 1-3 words each'
264
256
  },
257
+ },
258
+ required: ['name', 'label', 'kind'],
259
+ additionalProperties: false,
260
+ }
261
+
262
+ /** What the gate's texts tell the planning model — one place, shared by the answer and stored schemas. */
263
+ const GATE_TEXT = {
264
+ description: 'The working entry into the key end-user workflow, drawn in the hero instead of the'
265
+ + ' call-to-action buttons: ONE SMALL FORM - a heading, one to three simple non-sensitive'
266
+ + ' fields, one button. A guest fills it in, signs in, and continues with what they entered.'
267
+ + ' No choices to browse, no sample results, no counts, no previews. Every text is in the end'
268
+ + ' user\'s words',
269
+ story: 'The code of the landing story, copied EXACTLY as given to you. Never shown',
270
+ target: 'Leave it out - the platform fills it',
271
+ question: 'The heading of the form, 3-6 words, in the end user\'s words - e.g. "What are you'
272
+ + ' baking?"',
273
+ fields: 'The fields, AS FEW AS POSSIBLE: one whenever one is enough, never more than three. They'
274
+ + ' are the first things the story\'s own screen asks for, in the same words',
275
+ cta: 'The button label, a verb first, 2-3 words and an arrow - e.g. "Start my post →"',
276
+ note: 'One short muted line under the form: no account is needed yet and what was entered comes'
277
+ + ' along - e.g. "No account needed. What you enter comes with you."',
278
+ }
279
+
280
+ /** The pre-form keys of a stored gate: optional, described as history, never offered to the model. */
281
+ const LEGACY_GATE_NOTE = 'Pre-form shape, never written'
282
+
283
+ /**
284
+ * What the planning MODEL answers for the gate: the form and nothing older. The stored schema below
285
+ * adds the pre-form keys as optional, so a plan written before the gate became a form still
286
+ * validates; the model is never offered them.
287
+ */
288
+ const LandingGateAnswerSchema: JSONSchemaType<LandingGateAnswer> = {
289
+ type: 'object',
290
+ title: 'LandingGatePlan',
291
+ description: GATE_TEXT.description,
292
+ properties: {
293
+ story: { type: 'string', description: GATE_TEXT.story },
294
+ target: { type: 'string', nullable: true, description: GATE_TEXT.target },
295
+ question: { type: 'string', description: GATE_TEXT.question },
296
+ fields: { type: 'array', items: LandingGateFieldSchema, description: GATE_TEXT.fields },
297
+ cta: { type: 'string', description: GATE_TEXT.cta },
298
+ note: { type: 'string', description: GATE_TEXT.note },
299
+ },
300
+ required: ['story', 'question', 'fields', 'cta', 'note'],
301
+ additionalProperties: false,
302
+ }
303
+
304
+ /**
305
+ * The gate as a plan may STORE it: the form, plus the pre-form keys (chips, sample records, a live
306
+ * count) as optional. A schema that validates a stored document only ever grows optional.
307
+ */
308
+ const LandingGatePlanSchema: JSONSchemaType<LandingGatePlan> = {
309
+ type: 'object',
310
+ title: 'LandingGatePlan',
311
+ description: GATE_TEXT.description,
312
+ properties: {
313
+ story: { type: 'string', description: GATE_TEXT.story },
314
+ target: { type: 'string', nullable: true, description: GATE_TEXT.target },
315
+ question: { type: 'string', description: GATE_TEXT.question },
316
+ fields: { type: 'array', nullable: true, items: LandingGateFieldSchema, description: GATE_TEXT.fields },
317
+ cta: { type: 'string', description: GATE_TEXT.cta },
318
+ note: { type: 'string', description: GATE_TEXT.note },
319
+ hint: { type: 'string', nullable: true, description: LEGACY_GATE_NOTE },
320
+ label: { type: 'string', nullable: true, description: LEGACY_GATE_NOTE },
321
+ inputs: { type: 'array', nullable: true, items: { type: 'string' }, description: LEGACY_GATE_NOTE },
322
+ selected: { type: 'array', nullable: true, items: { type: 'string' }, description: LEGACY_GATE_NOTE },
265
323
  results: {
266
- type: 'array',
324
+ type: 'array', nullable: true,
267
325
  items: {
268
326
  type: 'object',
269
327
  properties: {
270
- title: { type: 'string', description: 'A realistic record title, 2-4 words' },
271
- meta: {
272
- type: 'string',
273
- description: 'One short line: how well it fits and one fact, e.g. "Uses all 3 · 3 h 20 min"'
274
- },
275
- needs: {
276
- type: 'array',
277
- items: { type: 'string' },
278
- description: '2-4 of the inputs above, copied exactly, that this record uses'
279
- },
328
+ title: { type: 'string' },
329
+ meta: { type: 'string' },
330
+ needs: { type: 'array', items: { type: 'string' } },
280
331
  },
281
332
  required: ['title', 'meta', 'needs'],
282
333
  additionalProperties: false,
283
334
  },
284
- description: '3 or 4 sample records the picks are ranked against - illustrative, never real data'
335
+ description: LEGACY_GATE_NOTE
285
336
  },
286
337
  count: {
287
- type: 'object',
338
+ type: 'object', nullable: true,
288
339
  properties: {
289
- one: { type: 'string', description: 'The count for one match, with {n}: "{n} bake matches"' },
290
- many: { type: 'string', description: 'The count for several, with {n}: "{n} bakes match"' },
291
- none: { type: 'string', description: 'The count for none, e.g. "No matches yet"' },
340
+ one: { type: 'string' },
341
+ many: { type: 'string' },
342
+ none: { type: 'string' },
292
343
  },
293
344
  required: ['one', 'many', 'none'],
294
345
  additionalProperties: false,
295
- description: 'The live count label; write the literal token {n} where the number goes'
296
- },
297
- empty: {
298
- type: 'string',
299
- description: 'One line shown when nothing matches, saying what to pick - e.g. "Pick two or more'
300
- + ' ingredients to see what you can bake."'
301
- },
302
- note: {
303
- type: 'string',
304
- description: 'One short line beside the button: signing in is by email and the picks come'
305
- + ' along - e.g. "Sign in with your email. Your picks come with you."'
306
- },
307
- cta: {
308
- type: 'string',
309
- description: 'The button label, a verb first, 2-3 words and an arrow - e.g. "Open recipes →"'
310
- },
311
- lock: {
312
- type: 'string',
313
- description: 'What unlocks after sign-in, as the accessible name of a lock icon - e.g. "Full'
314
- + ' method after sign-in"'
346
+ description: LEGACY_GATE_NOTE
315
347
  },
348
+ empty: { type: 'string', nullable: true, description: LEGACY_GATE_NOTE },
349
+ lock: { type: 'string', nullable: true, description: LEGACY_GATE_NOTE },
316
350
  },
317
- required: [
318
- 'story', 'question', 'hint', 'label', 'inputs', 'selected', 'results', 'count', 'empty',
319
- 'note', 'cta', 'lock',
320
- ],
351
+ required: ['story', 'question', 'cta', 'note'],
321
352
  additionalProperties: false,
322
353
  }
323
354
 
@@ -535,3 +566,28 @@ export const ScaffoldPlanSchema: JSONSchemaType<ScaffoldPlan> = {
535
566
  required: ['identity', 'guestHome', 'areas', 'stories', 'motifs'],
536
567
  additionalProperties: false,
537
568
  }
569
+
570
+ /**
571
+ * The plan as the planning MODEL answers it — {@link ScaffoldPlanSchema} with the gate held to the
572
+ * form. The stored schema keeps the pre-form gate keys optional so an old plan still validates;
573
+ * offering them to the model would let it write a chip picker again. Typed as the stored plan
574
+ * because every answer is one: the answer's gate is a subset of the stored gate.
575
+ */
576
+ export const ScaffoldPlanAnswerSchema = {
577
+ ...ScaffoldPlanSchema,
578
+ properties: {
579
+ ...ScaffoldPlanSchema.properties,
580
+ guestHome: {
581
+ ...GuestHomePlanSchema,
582
+ properties: {
583
+ ...GuestHomePlanSchema.properties,
584
+ gate: {
585
+ ...LandingGateAnswerSchema,
586
+ nullable: true,
587
+ description: 'ONLY when you were given a landing story; omit it otherwise. '
588
+ + LandingGateAnswerSchema.description,
589
+ },
590
+ },
591
+ },
592
+ },
593
+ } as unknown as JSONSchemaType<ScaffoldPlan>
@@ -119,42 +119,83 @@ export interface BentoFragment {
119
119
  rows: { label: string, value?: string }[]
120
120
  }
121
121
 
122
+ /**
123
+ * What kind of control a landing-gate field is. A CLOSED set of the simplest native inputs: each
124
+ * value is one plain element, so there is nothing here a visitor has to learn, and nothing that
125
+ * can grow into a picker, a chip group or a preview.
126
+ */
127
+ export type LandingGateFieldKind = 'text' | 'select' | 'number' | 'date'
128
+
129
+ /** One simple, non-sensitive field of the landing gate. */
130
+ export interface LandingGateField {
131
+ /** The camelCase key the entered value travels under in the landing handoff: "dish". */
132
+ name: string
133
+ /** The visible label, in the end user's words: "What are you baking?". */
134
+ label: string
135
+ kind: LandingGateFieldKind
136
+ /** An example value shown inside an empty text or number field — never a personal one. */
137
+ placeholder?: string
138
+ /** `select` only: 2–6 plain options. */
139
+ options?: string[]
140
+ }
141
+
122
142
  /**
123
143
  * The landing gate — the working entry into the product's key END-USER workflow, drawn in the
124
144
  * hero where the call-to-action pills would otherwise be.
125
145
  *
126
- * A guest makes a few non-sensitive choices on the landing page, sees sample results ranked
127
- * against them, and signs in to continue on the story's full-scale screen with those choices
128
- * carried over. Every field is copy or sample data in the end user's own words; nothing here is
129
- * an address the model invented — `target` is filled by code.
146
+ * A guest fills in one to three simple non-sensitive fields on the landing page and signs in to
147
+ * continue on the story's full-scale screen with what they entered already in place. It is a small
148
+ * form and nothing more: no choices to browse, no sample results, no counts. Every text is copy in
149
+ * the end user's own words; nothing here is an address the model invented — `target` is filled by
150
+ * code.
151
+ *
152
+ * The keys after `note` are the shape a plan had before the gate became a form (chips, sample
153
+ * records, a live count). They stay so a plan STORED under the old shape still validates and
154
+ * still reads; nothing writes them and nothing draws from them, and a gate that carries no
155
+ * `fields` is drawn as no gate at all.
130
156
  */
131
157
  export interface LandingGatePlan {
132
158
  /** The key end-user story's code — never rendered. */
133
159
  story: string
134
160
  /** Entrypoint ALIAS of the story's full-scale screen in the user area (filled by code, not the model). */
135
161
  target?: string
136
- /** The question the gate asks, in the end user's words: "What's in your pantry?". */
162
+ /** The heading of the form, in the end user's words: "What are you baking?". */
137
163
  question: string
138
- /** The reassurance beside it: "No account needed". */
139
- hint: string
140
- /** The accessible name of the chip group: "Your pantry". */
141
- label: string
142
- /** 5–8 chip labels the guest picks from. */
143
- inputs: string[]
144
- /** 4–5 of `inputs`, pre-selected so the first view already shows results. */
145
- selected: string[]
146
- /** 3–4 sample records; `needs` is the subset of `inputs` each one uses. */
147
- results: { title: string, meta: string, needs: string[] }[]
148
- /** The live count label; `{n}` is replaced by the number ("{n} bakes match"). */
149
- count: { one: string, many: string, none: string }
150
- /** The line shown when nothing matches the picks. */
151
- empty: string
152
- /** The line beside the CTA: "Sign in with your email. Your picks come with you.". */
164
+ /** The fields, one to three — one whenever one is enough. Absent only on a plan stored under the old shape. */
165
+ fields?: LandingGateField[]
166
+ /** The one button's label: "Start my post →". */
167
+ cta: string
168
+ /** The muted line under the form: "No account needed. What you enter comes with you.". */
153
169
  note: string
154
- /** The CTA label: "Open recipes →". */
170
+ /** @deprecated Pre-form shape, kept for stored plans only. */
171
+ hint?: string
172
+ /** @deprecated Pre-form shape, kept for stored plans only. */
173
+ label?: string
174
+ /** @deprecated Pre-form shape, kept for stored plans only. */
175
+ inputs?: string[]
176
+ /** @deprecated Pre-form shape, kept for stored plans only. */
177
+ selected?: string[]
178
+ /** @deprecated Pre-form shape, kept for stored plans only. */
179
+ results?: { title: string, meta: string, needs: string[] }[]
180
+ /** @deprecated Pre-form shape, kept for stored plans only. */
181
+ count?: { one: string, many: string, none: string }
182
+ /** @deprecated Pre-form shape, kept for stored plans only. */
183
+ empty?: string
184
+ /** @deprecated Pre-form shape, kept for stored plans only. */
185
+ lock?: string
186
+ }
187
+
188
+ /**
189
+ * The gate as the planning MODEL answers it: the form, and none of the pre-form keys. Assignable
190
+ * to {@link LandingGatePlan}, which is what a plan stores.
191
+ */
192
+ export interface LandingGateAnswer {
193
+ story: string
194
+ target?: string
195
+ question: string
196
+ fields: LandingGateField[]
155
197
  cta: string
156
- /** The accessible name of a result row's lock icon: "Full method after sign-in". */
157
- lock: string
198
+ note: string
158
199
  }
159
200
 
160
201
  /** The guest area's landing page — the product's public face. */
@@ -85,115 +85,3 @@ export enum ViableSkill {
85
85
  LibrariesAi = 'libraries-ai',
86
86
  LibrariesGame = 'libraries-game',
87
87
  }
88
-
89
- /**
90
- * Sort weights. Skills render in this order inside the cached block, so the sequence is
91
- * part of the cache key — reordering them invalidates every prefix at once. Grouped in
92
- * tens to leave room for a consumer's own skills between them.
93
- */
94
- export const SKILL_ORDER: Record<ViableSkill, number> = {
95
- [ViableSkill.OutputSourceOnly]: 10,
96
- [ViableSkill.OutputTextOnly]: 10,
97
- [ViableSkill.ScopeDiscipline]: 15,
98
- [ViableSkill.MainFlowFocus]: 12,
99
- [ViableSkill.NonTypescriptOutput]: 18,
100
- [ViableSkill.TsStyle]: 20,
101
- [ViableSkill.TsImports]: 30,
102
- [ViableSkill.ReactComponents]: 40,
103
- [ViableSkill.OwlMeansEntrypoints]: 41,
104
- [ViableSkill.OwlMeansNav]: 42,
105
- [ViableSkill.PermissionModel]: 43,
106
- [ViableSkill.ShadcnUi]: 44,
107
- [ViableSkill.OwlmeansState]: 50,
108
- [ViableSkill.FormFeedback]: 51,
109
- [ViableSkill.StoreAccess]: 52,
110
- [ViableSkill.ViewModelNaming]: 55,
111
- // After the frontend rules it builds on (entrypoints, nav, shadcn, form feedback, the store), and
112
- // before the backend block — it is only ever carried by analysis and frontend personas.
113
- [ViableSkill.LandingGate]: 56,
114
- // Analysis and frontend read it the same way LandingGate is read — after the frontend rules it
115
- // references (nav, shadcn) — and a backend coder reads it as a server-side check, so it sits
116
- // just ahead of the backend block for either audience.
117
- [ViableSkill.MarketingConsent]: 57,
118
- [ViableSkill.ResourceLayer]: 60,
119
- [ViableSkill.ResourceResults]: 62,
120
- [ViableSkill.ResourceMigrations]: 64,
121
- [ViableSkill.OwlMeansServer]: 70,
122
- [ViableSkill.OwlmeansContext]: 65,
123
- [ViableSkill.OwlMeansServices]: 66,
124
- [ViableSkill.FixerHeuristics]: 75,
125
- [ViableSkill.ProjectLayout]: 5,
126
- [ViableSkill.AgentTooling]: 8,
127
- [ViableSkill.QueueDiscipline]: 67,
128
- [ViableSkill.WorkerJobs]: 68,
129
- [ViableSkill.TargetAgents]: 69,
130
- [ViableSkill.AgenticChoice]: 71,
131
- [ViableSkill.TargetLlm]: 72,
132
- [ViableSkill.TargetAgentTools]: 73,
133
- [ViableSkill.GameDesign]: 45,
134
- [ViableSkill.GameScene]: 46,
135
- [ViableSkill.GameUi]: 47,
136
- [ViableSkill.GameNetworking]: 48,
137
- [ViableSkill.LayoutDefinition]: 80,
138
- [ViableSkill.ScreenDefinition]: 82,
139
- [ViableSkill.LibrariesCommon]: 90,
140
- [ViableSkill.LibrariesUiState]: 90,
141
- [ViableSkill.LibrariesUi]: 90,
142
- [ViableSkill.LibrariesBackend]: 90,
143
- [ViableSkill.LibrariesAi]: 90,
144
- [ViableSkill.LibrariesGame]: 90,
145
- }
146
-
147
- /**
148
- * The catalogue entries whose subject a PUBLISHED `@owlmeans` package already documents.
149
- *
150
- * The catalogue and `agent-meta/skills/*` state the same contract twice — the P1 duplication —
151
- * and the duplication is not symmetric: a package skill is versioned with the package and reaches
152
- * a prompt through `owlmeansPackagesPlugin` whenever the request names that package, while a
153
- * catalogue entry is a constant here that has to be edited by hand every time the framework moves.
154
- *
155
- * Naming the overlap is what makes it a DATA decision instead of a code one. A blueprint lists
156
- * which of these its prompts carry, and a persona's skills are filtered against that list — so
157
- * dropping one is a line in a blueprint, and restoring it is the same line. Everything NOT in this
158
- * set is viable's own (output discipline, scope, the view-model convention, the fixer heuristics,
159
- * where files live) and always loads, because no published package documents it.
160
- *
161
- * The default blueprint currently lists every one of them: the reduction is a real change to what
162
- * a coder is told, and it is measured against a green end-to-end baseline rather than guessed.
163
- */
164
- export const FRAMEWORK_OWNED_SKILLS: ViableSkill[] = [
165
- ViableSkill.ResourceLayer,
166
- ViableSkill.ResourceResults,
167
- ViableSkill.ResourceMigrations,
168
- ViableSkill.OwlmeansContext,
169
- ViableSkill.OwlMeansServices,
170
- ViableSkill.OwlMeansServer,
171
- ViableSkill.OwlMeansEntrypoints,
172
- ViableSkill.OwlMeansNav,
173
- ViableSkill.PermissionModel,
174
- ViableSkill.OwlmeansState,
175
- ViableSkill.StoreAccess,
176
- ViableSkill.ShadcnUi,
177
- ViableSkill.ReactComponents,
178
- ViableSkill.FormFeedback,
179
- ]
180
-
181
- const FRAMEWORK_OWNED = new Set<ViableSkill>(FRAMEWORK_OWNED_SKILLS)
182
-
183
- /**
184
- * Filter a persona's skills against what a blueprint asks for.
185
- *
186
- * A skill nobody owns always survives. A framework-owned one survives only while the blueprint
187
- * lists it — so a blueprint built on another persistence layer stops teaching the Postgres
188
- * resource contract without anybody editing a persona, and a blueprint that lists nothing at all
189
- * (the shape a test builds) is read as "everything", because an empty allow-list almost always
190
- * means "not stated" rather than "none".
191
- */
192
- export const skillsForBlueprint = (
193
- skills: ViableSkill[], allowed?: ViableSkill[]
194
- ): ViableSkill[] => {
195
- if (allowed == null || allowed.length === 0) return skills
196
- const permitted = new Set(allowed)
197
-
198
- return skills.filter(skill => !FRAMEWORK_OWNED.has(skill) || permitted.has(skill))
199
- }
@@ -1,4 +1,3 @@
1
1
 
2
2
  export * from './consts.js'
3
- export * from './catalogue.js'
4
3
  export * from './roles.js'