soustack 0.4.0 → 0.4.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 (146) hide show
  1. package/README.md +4 -4
  2. package/dist/cli/index.js +4412 -1275
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/index.d.mts +106 -80
  5. package/dist/index.d.ts +106 -80
  6. package/dist/index.js +4527 -1360
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +4527 -1360
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/scrape/index.d.mts +86 -74
  11. package/dist/scrape/index.d.ts +86 -74
  12. package/dist/scrape/index.js +91 -64
  13. package/dist/scrape/index.js.map +1 -1
  14. package/dist/scrape/index.mjs +91 -64
  15. package/dist/scrape/index.mjs.map +1 -1
  16. package/package.json +15 -6
  17. package/spec/.sync-meta.json +149 -0
  18. package/spec/SOUSTACK_SPEC_VERSION +1 -0
  19. package/spec/defs/common.schema.json +46 -0
  20. package/spec/defs/duration.schema.json +33 -0
  21. package/spec/defs/entities.schema.json +111 -0
  22. package/spec/defs/ingredientQuantified.schema.json +9 -0
  23. package/spec/defs/quantity.schema.json +16 -0
  24. package/spec/defs/scalingRule.schema.json +127 -0
  25. package/spec/defs/temperature.schema.json +63 -0
  26. package/spec/fixtures/content/illustrated-step.valid.json +24 -0
  27. package/spec/fixtures/invalid/equipment-unknown-reference.invalid.json +38 -0
  28. package/spec/fixtures/invalid/mise-en-place-unknown-equipment.invalid.json +37 -0
  29. package/spec/fixtures/invalid/mise-en-place-unknown-input.invalid.json +41 -0
  30. package/spec/fixtures/invalid/storage-leftovers-missing-method.invalid.json +31 -0
  31. package/spec/fixtures/invalid/storage-leftovers-wrong-type.invalid.json +23 -0
  32. package/spec/fixtures/level/base-full.valid.json +162 -0
  33. package/spec/fixtures/level/base-missing-yield.invalid.json +12 -0
  34. package/spec/fixtures/level/lite-min.valid.json +14 -0
  35. package/spec/fixtures/profile/profile-base.valid.json +20 -0
  36. package/spec/fixtures/profile/profile-equipped.valid.json +28 -0
  37. package/spec/fixtures/profile/profile-illustrated.valid.json +28 -0
  38. package/spec/fixtures/profile/profile-lite.valid.json +13 -0
  39. package/spec/fixtures/profile/profile-prepped.valid.json +31 -0
  40. package/spec/fixtures/profile/profile-scalable-missing-scaling.invalid.json +29 -0
  41. package/spec/fixtures/profile/profile-scalable.valid.json +49 -0
  42. package/spec/fixtures/profile/profile-timed-missing-structured.invalid.json +30 -0
  43. package/spec/fixtures/scaling/bakers-percent-missing-ref.invalid.json +41 -0
  44. package/spec/fixtures/scaling/bakers-percent.valid.json +51 -0
  45. package/spec/fixtures/scaling/discrete-range.invalid.json +36 -0
  46. package/spec/fixtures/scaling/missing-quantified.invalid.json +40 -0
  47. package/spec/fixtures/scaling/reject-bakersPercentage.invalid.json +50 -0
  48. package/spec/fixtures/stacks/compute-missing-timed.invalid.json +32 -0
  49. package/spec/fixtures/stacks/dietary-no-signal.invalid.json +16 -0
  50. package/spec/fixtures/stacks/illustrated-empty.invalid.json +13 -0
  51. package/spec/fixtures/stacks/quantified-string.invalid.json +22 -0
  52. package/spec/fixtures/stacks/referenced-missing-input.invalid.json +32 -0
  53. package/spec/fixtures/stacks/storage-min.valid.json +20 -0
  54. package/spec/fixtures/stacks/storage-no-duration.invalid.json +16 -0
  55. package/spec/fixtures/stacks/timed-implies-structured.valid.json +50 -0
  56. package/spec/fixtures/stacks/timed-range.invalid.json +33 -0
  57. package/spec/fixtures/valid/equipment-scaling-rules.valid.json +76 -0
  58. package/spec/fixtures/valid/equipment-strings.valid.json +31 -0
  59. package/spec/fixtures/valid/equipment-structured-uses.valid.json +47 -0
  60. package/spec/fixtures/valid/mise-en-place-basic.valid.json +31 -0
  61. package/spec/fixtures/valid/mise-en-place-referenced-equipment.valid.json +51 -0
  62. package/spec/fixtures/valid/prep-ingredient-strings.valid.json +48 -0
  63. package/spec/fixtures/valid/prep-ingredient-structured.valid.json +45 -0
  64. package/spec/fixtures/valid/profile-equipped.valid.json +29 -0
  65. package/spec/fixtures/valid/profile-prepped.valid.json +32 -0
  66. package/spec/fixtures/valid/quantified-nested-ingredient-sections.valid.json +61 -0
  67. package/spec/fixtures/valid/referenced-scaling.valid.json +67 -0
  68. package/spec/fixtures/valid/storage-leftovers-simple.valid.json +27 -0
  69. package/spec/fixtures/valid/storage-leftovers-structured.valid.json +43 -0
  70. package/spec/fixtures/valid/structured-nested-step-sections.valid.json +84 -0
  71. package/spec/schemas/stacks-registry.schema.json +108 -0
  72. package/spec/soustack.schema.json +2379 -0
  73. package/spec/stacks/compute.schema.json +7 -0
  74. package/spec/stacks/compute@1.md +22 -0
  75. package/spec/stacks/dietary.schema.json +45 -0
  76. package/spec/stacks/dietary@1.md +24 -0
  77. package/spec/stacks/equipment.schema.json +98 -0
  78. package/spec/stacks/equipment@1.md +244 -0
  79. package/spec/stacks/illustrated.schema.json +54 -0
  80. package/spec/stacks/illustrated@1.md +24 -0
  81. package/spec/stacks/prep.schema.json +76 -0
  82. package/spec/stacks/prep@1.md +276 -0
  83. package/spec/stacks/quantified.schema.json +74 -0
  84. package/spec/stacks/quantified@1.md +24 -0
  85. package/spec/stacks/referenced.schema.json +96 -0
  86. package/spec/stacks/referenced@1.md +23 -0
  87. package/spec/stacks/registry.json +112 -0
  88. package/spec/stacks/scaling.schema.json +99 -0
  89. package/spec/stacks/scaling@1.md +238 -0
  90. package/spec/stacks/storage.schema.json +132 -0
  91. package/spec/stacks/storage@1.md +256 -0
  92. package/spec/stacks/structured.schema.json +48 -0
  93. package/spec/stacks/structured@1.md +24 -0
  94. package/spec/stacks/substitutions.schema.json +43 -0
  95. package/spec/stacks/substitutions@1.md +24 -0
  96. package/spec/stacks/techniques.schema.json +28 -0
  97. package/spec/stacks/techniques@1.md +23 -0
  98. package/spec/stacks/timed.schema.json +60 -0
  99. package/spec/stacks/timed@1.md +23 -0
  100. package/src/defs/common.schema.json +46 -0
  101. package/src/defs/duration.schema.json +33 -0
  102. package/src/defs/entities.schema.json +111 -0
  103. package/src/defs/ingredientQuantified.schema.json +9 -0
  104. package/src/defs/quantity.schema.json +16 -0
  105. package/src/defs/scalingRule.schema.json +127 -0
  106. package/src/defs/temperature.schema.json +63 -0
  107. package/src/profiles/base.schema.json +2 -2
  108. package/src/profiles/equipped.schema.json +10 -0
  109. package/src/profiles/illustrated.schema.json +4 -4
  110. package/src/profiles/lite.schema.json +10 -0
  111. package/src/profiles/prepped.schema.json +10 -0
  112. package/src/profiles/scalable.schema.json +6 -6
  113. package/src/profiles/timed.schema.json +10 -0
  114. package/src/schema.json +2271 -248
  115. package/src/schemas/stacks-registry.schema.json +108 -0
  116. package/src/soustack.schema.json +2271 -248
  117. package/src/stacks/compute.schema.json +7 -0
  118. package/src/stacks/compute@1.md +22 -0
  119. package/src/stacks/dietary.schema.json +45 -0
  120. package/src/stacks/dietary@1.md +24 -0
  121. package/src/stacks/equipment.schema.json +98 -0
  122. package/src/stacks/equipment@1.md +244 -0
  123. package/src/stacks/illustrated.schema.json +54 -0
  124. package/src/stacks/illustrated@1.md +24 -0
  125. package/src/stacks/prep.schema.json +76 -0
  126. package/src/stacks/prep@1.md +276 -0
  127. package/src/stacks/quantified.schema.json +74 -0
  128. package/src/stacks/quantified@1.md +24 -0
  129. package/src/stacks/referenced.schema.json +96 -0
  130. package/src/stacks/referenced@1.md +23 -0
  131. package/src/stacks/registry.json +112 -0
  132. package/src/stacks/scaling.schema.json +99 -0
  133. package/src/stacks/scaling@1.md +238 -0
  134. package/src/stacks/storage.schema.json +132 -0
  135. package/src/stacks/storage@1.md +256 -0
  136. package/src/stacks/structured.schema.json +48 -0
  137. package/src/stacks/structured@1.md +24 -0
  138. package/src/stacks/substitutions.schema.json +43 -0
  139. package/src/stacks/substitutions@1.md +24 -0
  140. package/src/stacks/techniques.schema.json +28 -0
  141. package/src/stacks/techniques@1.md +23 -0
  142. package/src/stacks/timed.schema.json +60 -0
  143. package/src/stacks/timed@1.md +23 -0
  144. package/src/profiles/cookable.schema.json +0 -18
  145. package/src/profiles/quantified.schema.json +0 -43
  146. package/src/profiles/schedulable.schema.json +0 -43
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://soustack.spec/stacks/compute.schema.json",
4
+ "title": "Compute Claim Stack",
5
+ "type": "object",
6
+ "additionalProperties": false
7
+ }
@@ -0,0 +1,22 @@
1
+ # compute@1
2
+
3
+ ## Purpose
4
+ The `compute@1` stack declares that a recipe meets prerequisites for computational operations such as scheduling, scaling, and deterministic planning.
5
+
6
+ ## Adds
7
+ - No additional structural fields (claim stack).
8
+
9
+ ## Requires
10
+ - `quantified@1`
11
+ - `timed@1`
12
+
13
+ ## Semantics
14
+ - MUST: Recipe must satisfy all requirements of `quantified@1` and `timed@1`.
15
+ - NOTE: This is a claim stack indicating computational readiness. Tools SHOULD enforce that all prerequisites are met.
16
+
17
+ ## Composition Notes
18
+ - This stack is monotonic: it adds requirements or fields without removing expressiveness.
19
+ - Interaction: Requires `quantified@1` for scaling inputs and `timed@1` for deterministic scheduling. Indicates recipe is ready for computational recipe planning tools.
20
+
21
+
22
+
@@ -0,0 +1,45 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://soustack.spec/stacks/dietary.schema.json",
4
+ "title": "Dietary Stack",
5
+ "type": "object",
6
+ "properties": {
7
+ "dietary": {
8
+ "type": "object",
9
+ "properties": {
10
+ "basis": { "type": "string", "enum": ["perServing", "perRecipe"] },
11
+ "calories": { "type": "number", "minimum": 0 },
12
+ "macros": {
13
+ "type": "object",
14
+ "properties": {
15
+ "protein": { "type": "number", "minimum": 0 },
16
+ "fat": { "type": "number", "minimum": 0 },
17
+ "carbohydrates": { "type": "number", "minimum": 0 },
18
+ "metadata": { "type": "object", "additionalProperties": true }
19
+ },
20
+ "minProperties": 1,
21
+ "additionalProperties": false,
22
+ "patternProperties": {
23
+ "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
24
+ }
25
+ },
26
+ "diets": { "type": "array", "items": { "type": "string" } },
27
+ "allergens": { "type": "array", "items": { "type": "string" } },
28
+ "metadata": { "type": "object", "additionalProperties": true }
29
+ },
30
+ "required": ["basis"],
31
+ "additionalProperties": false,
32
+ "anyOf": [
33
+ { "required": ["calories"] },
34
+ { "required": ["macros"] },
35
+ { "required": ["diets"] },
36
+ { "required": ["allergens"] }
37
+ ],
38
+ "patternProperties": {
39
+ "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
40
+ }
41
+ }
42
+ },
43
+ "required": ["dietary"],
44
+ "additionalProperties": false
45
+ }
@@ -0,0 +1,24 @@
1
+ # dietary@1
2
+
3
+ ## Purpose
4
+ The `dietary@1` stack enables recipes to declare nutritional information, dietary classifications, and allergen information.
5
+
6
+ ## Adds
7
+ - Top-level `dietary` object with `basis` field (perServing or perRecipe).
8
+ - Optional `calories`, `macros` (protein, fat, carbohydrates), `diets`, and `allergens` fields.
9
+ - At least one of calories, macros, diets, or allergens must be present.
10
+
11
+ ## Requires
12
+ - None
13
+
14
+ ## Semantics
15
+ - MUST: The `dietary` object must include a `basis` field.
16
+ - MUST: At least one of `calories`, `macros`, `diets`, or `allergens` must be present.
17
+ - NOTE: Dietary tags and allergen lists are freeform strings in v1 (no controlled vocabulary).
18
+
19
+ ## Composition Notes
20
+ - This stack is monotonic: it adds requirements or fields without removing expressiveness.
21
+ - Interaction: No dependencies on other stacks. Can be combined with any other stack.
22
+
23
+
24
+
@@ -0,0 +1,98 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://soustack.spec/stacks/equipment.schema.json",
4
+ "title": "Equipment Stack",
5
+ "type": "object",
6
+ "properties": {
7
+ "equipment": {
8
+ "type": "array",
9
+ "minItems": 1,
10
+ "items": {
11
+ "anyOf": [
12
+ { "type": "string", "minLength": 1 },
13
+ { "$ref": "#/$defs/equipmentItem" }
14
+ ]
15
+ }
16
+ }
17
+ },
18
+ "required": ["equipment"],
19
+ "$defs": {
20
+ "equipmentItem": {
21
+ "type": "object",
22
+ "properties": {
23
+ "id": {
24
+ "type": "string",
25
+ "pattern": "^[A-Za-z0-9._-]+$",
26
+ "minLength": 1
27
+ },
28
+ "name": {
29
+ "type": "string",
30
+ "minLength": 1
31
+ },
32
+ "count": {
33
+ "type": "integer",
34
+ "minimum": 1
35
+ },
36
+ "countScaling": {
37
+ "oneOf": [
38
+ { "type": "string", "enum": ["fixed", "linear"] },
39
+ {
40
+ "type": "object",
41
+ "properties": {
42
+ "mode": { "const": "threshold" },
43
+ "steps": {
44
+ "type": "array",
45
+ "minItems": 1,
46
+ "items": {
47
+ "type": "object",
48
+ "properties": {
49
+ "maxFactor": {
50
+ "type": "number",
51
+ "exclusiveMinimum": 0
52
+ },
53
+ "count": {
54
+ "type": "integer",
55
+ "minimum": 1
56
+ }
57
+ },
58
+ "required": ["maxFactor", "count"],
59
+ "additionalProperties": false
60
+ }
61
+ }
62
+ },
63
+ "required": ["mode", "steps"],
64
+ "additionalProperties": false
65
+ }
66
+ ]
67
+ },
68
+ "upgrades": {
69
+ "type": "array",
70
+ "minItems": 1,
71
+ "items": {
72
+ "type": "object",
73
+ "properties": {
74
+ "minFactor": {
75
+ "type": "number",
76
+ "exclusiveMinimum": 0
77
+ },
78
+ "use": {
79
+ "type": "string",
80
+ "pattern": "^[A-Za-z0-9._-]+$",
81
+ "minLength": 1
82
+ }
83
+ },
84
+ "required": ["minFactor", "use"],
85
+ "additionalProperties": false
86
+ }
87
+ }
88
+ },
89
+ "required": ["id", "name"],
90
+ "additionalProperties": false,
91
+ "patternProperties": {
92
+ "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
93
+ }
94
+ }
95
+ },
96
+ "additionalProperties": false
97
+ }
98
+
@@ -0,0 +1,244 @@
1
+ #equipment@1
2
+
3
+ ---
4
+
5
+ ## Purpose
6
+
7
+ The `equipment@1` stack enables recipes to declare required equipment, with optional scaling-aware counts and upgrade paths for different scale factors.
8
+
9
+ This stack is adoption-first: equipment can be declared as simple strings for minimal friction, or as structured objects with IDs for cross-referencing from steps.
10
+
11
+ ---
12
+
13
+ ## Requirements
14
+
15
+ A document that declares `equipment@1`:
16
+
17
+ • MUST satisfy all structural rules enabled by this stack.
18
+ • MUST satisfy the semantic rules described below.
19
+
20
+ ---
21
+
22
+ ## Data Model
23
+
24
+ When `equipment@1` is declared, the document MUST include a top-level `equipment` array.
25
+
26
+ ### Equipment Array
27
+
28
+ The `equipment` array contains one or more items. Each item MAY be:
29
+
30
+ • A string (simple tool name)
31
+ • A structured equipment object
32
+
33
+ ### Structured Equipment Object
34
+
35
+ Structured equipment objects support:
36
+
37
+ • `id` (required): unique identifier following pattern `^[A-Za-z0-9._-]+$`
38
+ • `name` (required): human-readable name
39
+ • `count` (optional): integer >= 1, default 1
40
+ • `countScaling` (optional): scaling behavior for count
41
+ • `upgrades` (optional): array of upgrade rules
42
+
43
+ ---
44
+
45
+ ## Scaling Behavior
46
+
47
+ Equipment scaling is defined in terms of a scale factor F. If no scaling factor exists in the document, implementations MUST treat F = 1.
48
+
49
+ ### countScaling
50
+
51
+ The `countScaling` field controls how equipment count changes with scale:
52
+
53
+ #### fixed
54
+
55
+ Count remains unchanged regardless of scale factor.
56
+
57
+ ```json
58
+ {
59
+ "id": "pan",
60
+ "name": "8-inch skillet",
61
+ "count": 1,
62
+ "countScaling": "fixed"
63
+ }
64
+ ```
65
+
66
+ #### linear
67
+
68
+ Effective count = ceil(count × F)
69
+
70
+ ```json
71
+ {
72
+ "id": "mixing_bowl",
73
+ "name": "Mixing bowl",
74
+ "count": 2,
75
+ "countScaling": "linear"
76
+ }
77
+ ```
78
+
79
+ #### threshold
80
+
81
+ Pick the first step where F <= maxFactor, otherwise use the last step.
82
+
83
+ ```json
84
+ {
85
+ "id": "sheet_pan",
86
+ "name": "Baking sheet",
87
+ "count": 1,
88
+ "countScaling": {
89
+ "mode": "threshold",
90
+ "steps": [
91
+ { "maxFactor": 1.0, "count": 1 },
92
+ { "maxFactor": 2.0, "count": 2 },
93
+ { "maxFactor": 4.0, "count": 3 }
94
+ ]
95
+ }
96
+ }
97
+ ```
98
+
99
+ The `steps` array MUST be non-empty. Steps SHOULD be in ascending order by `maxFactor` (semantic validation may enforce this in tooling).
100
+
101
+ ### upgrades
102
+
103
+ The `upgrades` array allows swapping to a different equipment item at higher scale factors.
104
+
105
+ Choose the upgrade with the highest `minFactor` where `minFactor <= F`. If no upgrade matches, use the base equipment item.
106
+
107
+ ```json
108
+ {
109
+ "id": "skillet_small",
110
+ "name": "8-inch skillet",
111
+ "upgrades": [
112
+ { "minFactor": 2.0, "use": "skillet_large" }
113
+ ]
114
+ }
115
+ ```
116
+
117
+ The `use` field MUST reference an equipment id that exists in the equipment array (semantic validation).
118
+
119
+ ---
120
+
121
+ ## Step Usage
122
+
123
+ When `structured@1` is present (steps are objects), steps MAY include an optional `usesEquipment` field.
124
+
125
+ Field
126
+ step.usesEquipment : array of equipment ids
127
+
128
+ Each id in `usesEquipment` MUST exist in the equipment array (semantic validation when both stacks are present).
129
+
130
+ Example:
131
+
132
+ ```json
133
+ {
134
+ "equipment": [
135
+ { "id": "skillet", "name": "8-inch skillet" }
136
+ ],
137
+ "instructions": [
138
+ {
139
+ "id": "sear",
140
+ "text": "Sear the meat",
141
+ "usesEquipment": ["skillet"]
142
+ }
143
+ ]
144
+ }
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Composition
150
+
151
+ The equipment stack is composable with other stacks:
152
+
153
+ • No hard dependency on `scaling@1` (scaling behavior uses factor if present, but does not require scaling stack)
154
+ • Works with `structured@1` to enable step-level equipment references
155
+ • Remains monotonic: does not close objects needed by other stacks
156
+
157
+ ---
158
+
159
+ ## Semantic Validation Rules (Normative)
160
+
161
+ Validators MUST enforce the following rules:
162
+
163
+ 1. Equipment id uniqueness
164
+ All equipment object `id` values MUST be unique within the equipment array.
165
+
166
+ 2. Step equipment references
167
+ If both `equipment@1` and `structured@1` are present, and a step includes `usesEquipment`, all referenced ids MUST exist in the equipment array (as object ids, not string items).
168
+
169
+ 3. Upgrade references
170
+ If an equipment item includes `upgrades`, each `upgrades[].use` MUST reference an existing equipment object id.
171
+
172
+ 4. Threshold steps ordering
173
+ For `countScaling.mode == "threshold"`, the `steps` array SHOULD be in ascending order by `maxFactor` (tooling may warn but not fail validation).
174
+
175
+ ---
176
+
177
+ ## Examples
178
+
179
+ Simple strings
180
+
181
+ ```json
182
+ {
183
+ "equipment": ["mixing bowl", "whisk", "oven"]
184
+ }
185
+ ```
186
+
187
+ Structured with scaling
188
+
189
+ ```json
190
+ {
191
+ "equipment": [
192
+ {
193
+ "id": "skillet",
194
+ "name": "8-inch skillet",
195
+ "count": 1,
196
+ "countScaling": "fixed"
197
+ },
198
+ {
199
+ "id": "bowl",
200
+ "name": "Mixing bowl",
201
+ "count": 2,
202
+ "countScaling": "linear"
203
+ }
204
+ ]
205
+ }
206
+ ```
207
+
208
+ With upgrades
209
+
210
+ ```json
211
+ {
212
+ "equipment": [
213
+ {
214
+ "id": "skillet_small",
215
+ "name": "8-inch skillet",
216
+ "upgrades": [
217
+ { "minFactor": 2.0, "use": "skillet_large" }
218
+ ]
219
+ },
220
+ {
221
+ "id": "skillet_large",
222
+ "name": "12-inch skillet"
223
+ }
224
+ ]
225
+ }
226
+ ```
227
+
228
+ Step usage
229
+
230
+ ```json
231
+ {
232
+ "equipment": [
233
+ { "id": "skillet", "name": "8-inch skillet" }
234
+ ],
235
+ "instructions": [
236
+ {
237
+ "id": "sear",
238
+ "text": "Sear the meat in the skillet",
239
+ "usesEquipment": ["skillet"]
240
+ }
241
+ ]
242
+ }
243
+ ```
244
+
@@ -0,0 +1,54 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://soustack.spec/stacks/illustrated.schema.json",
4
+ "title": "Illustrated Stack",
5
+ "type": "object",
6
+ "properties": {
7
+ "images": {
8
+ "type": "array",
9
+ "items": { "$ref": "../defs/common.schema.json#/properties/uri" }
10
+ },
11
+ "videos": {
12
+ "type": "array",
13
+ "items": { "$ref": "../defs/common.schema.json#/properties/uri" }
14
+ },
15
+ "instructions": {
16
+ "type": "array",
17
+ "items": {
18
+ "anyOf": [
19
+ { "$ref": "#/$defs/illustratedStep" },
20
+ { "$ref": "#/$defs/illustratedSection" }
21
+ ]
22
+ }
23
+ }
24
+ },
25
+ "$defs": {
26
+ "illustratedStep": {
27
+ "allOf": [
28
+ { "$ref": "../defs/entities.schema.json#/$defs/StepBase" },
29
+ { "required": ["id"] }
30
+ ]
31
+ },
32
+ "illustratedSection": {
33
+ "type": "object",
34
+ "properties": {
35
+ "section": { "type": "string" },
36
+ "steps": {
37
+ "type": "array",
38
+ "items": {
39
+ "anyOf": [
40
+ { "$ref": "#/$defs/illustratedStep" },
41
+ { "$ref": "#/$defs/illustratedSection" }
42
+ ]
43
+ }
44
+ },
45
+ "metadata": { "type": "object", "additionalProperties": true }
46
+ },
47
+ "required": ["section", "steps"],
48
+ "additionalProperties": false,
49
+ "patternProperties": {
50
+ "^x-": { "$ref": "../defs/common.schema.json#/properties/extensionLaneValue" }
51
+ }
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,24 @@
1
+ # illustrated@1
2
+
3
+ ## Purpose
4
+ The `illustrated@1` stack enables recipes to include media (images and videos) at the recipe and step levels for visual guidance.
5
+
6
+ ## Adds
7
+ - Top-level `images` and `videos` arrays (optional).
8
+ - Step objects may include `images` and `videos` arrays.
9
+ - Steps are structured objects with `id` fields.
10
+
11
+ ## Requires
12
+ - None
13
+
14
+ ## Semantics
15
+ - MUST: At least one media item (image or video) must be present at the recipe level or in at least one step.
16
+ - MUST: Step objects must include `id` fields (steps are structured).
17
+ - NOTE: Media URIs must be valid according to the URI schema definition.
18
+
19
+ ## Composition Notes
20
+ - This stack is monotonic: it adds requirements or fields without removing expressiveness.
21
+ - Interaction: Works with `structured@1` for step-level media. No dependencies on other stacks.
22
+
23
+
24
+
@@ -0,0 +1,76 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://soustack.spec/stacks/prep.schema.json",
4
+ "title": "Prep Stack",
5
+ "type": "object",
6
+ "properties": {
7
+ "miseEnPlace": {
8
+ "type": "array",
9
+ "minItems": 1,
10
+ "items": {
11
+ "$ref": "#/$defs/miseEnPlaceTask"
12
+ }
13
+ }
14
+ },
15
+ "required": ["miseEnPlace"],
16
+ "$defs": {
17
+ "miseEnPlaceTask": {
18
+ "type": "object",
19
+ "properties": {
20
+ "id": {
21
+ "type": "string",
22
+ "pattern": "^[A-Za-z0-9._-]+$"
23
+ },
24
+ "text": {
25
+ "type": "string",
26
+ "minLength": 1
27
+ },
28
+ "inputs": {
29
+ "type": "array",
30
+ "items": {
31
+ "type": "string"
32
+ },
33
+ "uniqueItems": true,
34
+ "minItems": 1
35
+ },
36
+ "usesEquipment": {
37
+ "type": "array",
38
+ "items": {
39
+ "type": "string",
40
+ "pattern": "^[A-Za-z0-9._-]+$"
41
+ },
42
+ "uniqueItems": true,
43
+ "minItems": 1
44
+ }
45
+ },
46
+ "required": ["text"],
47
+ "additionalProperties": false,
48
+ "patternProperties": {
49
+ "^x-": {
50
+ "$ref": "../defs/common.schema.json#/properties/extensionLaneValue"
51
+ }
52
+ }
53
+ },
54
+ "prepItem": {
55
+ "type": "object",
56
+ "properties": {
57
+ "verb": {
58
+ "type": "string",
59
+ "minLength": 1
60
+ },
61
+ "detail": {
62
+ "type": "string"
63
+ }
64
+ },
65
+ "required": ["verb"],
66
+ "additionalProperties": false,
67
+ "patternProperties": {
68
+ "^x-": {
69
+ "$ref": "../defs/common.schema.json#/properties/extensionLaneValue"
70
+ }
71
+ }
72
+ }
73
+ },
74
+ "additionalProperties": false
75
+ }
76
+