@topogram/cli 0.3.34

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 (257) hide show
  1. package/ARCHITECTURE.md +67 -0
  2. package/CHANGELOG.md +240 -0
  3. package/README.md +223 -0
  4. package/package.json +51 -0
  5. package/src/adoption/index.js +3 -0
  6. package/src/adoption/plan.js +702 -0
  7. package/src/adoption/reporting.js +464 -0
  8. package/src/adoption/review-groups.js +313 -0
  9. package/src/agent-ops/query-builders.js +5012 -0
  10. package/src/archive/archive.js +141 -0
  11. package/src/archive/compact.js +26 -0
  12. package/src/archive/jsonl.js +70 -0
  13. package/src/archive/resolver-bridge.js +82 -0
  14. package/src/archive/schema.js +87 -0
  15. package/src/archive/unarchive.js +108 -0
  16. package/src/catalog.js +752 -0
  17. package/src/cli/catalog-alias.js +166 -0
  18. package/src/cli.js +9738 -0
  19. package/src/component-behavior.js +173 -0
  20. package/src/example-implementation.js +91 -0
  21. package/src/format.js +19 -0
  22. package/src/generator/adapters.d.ts +4 -0
  23. package/src/generator/adapters.js +325 -0
  24. package/src/generator/api.d.ts +1 -0
  25. package/src/generator/api.js +1196 -0
  26. package/src/generator/check.js +355 -0
  27. package/src/generator/component-conformance.js +767 -0
  28. package/src/generator/components.js +39 -0
  29. package/src/generator/context/bundle.js +291 -0
  30. package/src/generator/context/diff.js +256 -0
  31. package/src/generator/context/digest.js +182 -0
  32. package/src/generator/context/domain-coverage.js +94 -0
  33. package/src/generator/context/domain-page.js +137 -0
  34. package/src/generator/context/index.js +42 -0
  35. package/src/generator/context/report.js +121 -0
  36. package/src/generator/context/shared.js +1397 -0
  37. package/src/generator/context/slice.js +703 -0
  38. package/src/generator/context/task-mode.js +466 -0
  39. package/src/generator/docs.js +327 -0
  40. package/src/generator/index.js +161 -0
  41. package/src/generator/native/parity-bundle.js +311 -0
  42. package/src/generator/output.js +300 -0
  43. package/src/generator/registry.js +482 -0
  44. package/src/generator/runtime/app-bundle.js +456 -0
  45. package/src/generator/runtime/bundle-shared.js +166 -0
  46. package/src/generator/runtime/compile-check.js +163 -0
  47. package/src/generator/runtime/deployment.js +287 -0
  48. package/src/generator/runtime/environment.js +635 -0
  49. package/src/generator/runtime/index.js +32 -0
  50. package/src/generator/runtime/runtime-check.js +554 -0
  51. package/src/generator/runtime/shared.js +515 -0
  52. package/src/generator/runtime/smoke.js +219 -0
  53. package/src/generator/schema.js +204 -0
  54. package/src/generator/sdlc/board.js +66 -0
  55. package/src/generator/sdlc/doc-page.js +53 -0
  56. package/src/generator/sdlc/index.js +23 -0
  57. package/src/generator/sdlc/release-notes.js +62 -0
  58. package/src/generator/sdlc/traceability-matrix.js +65 -0
  59. package/src/generator/shared.js +29 -0
  60. package/src/generator/surfaces/contracts.js +146 -0
  61. package/src/generator/surfaces/databases/contract.js +40 -0
  62. package/src/generator/surfaces/databases/index.js +84 -0
  63. package/src/generator/surfaces/databases/lifecycle-shared.d.ts +1 -0
  64. package/src/generator/surfaces/databases/lifecycle-shared.js +612 -0
  65. package/src/generator/surfaces/databases/migration-plan.js +281 -0
  66. package/src/generator/surfaces/databases/postgres/capabilities.js +14 -0
  67. package/src/generator/surfaces/databases/postgres/drizzle.js +99 -0
  68. package/src/generator/surfaces/databases/postgres/index.js +9 -0
  69. package/src/generator/surfaces/databases/postgres/lifecycle.js +16 -0
  70. package/src/generator/surfaces/databases/postgres/prisma.js +159 -0
  71. package/src/generator/surfaces/databases/postgres/sql-migration.js +102 -0
  72. package/src/generator/surfaces/databases/postgres/sql-schema.js +34 -0
  73. package/src/generator/surfaces/databases/shared.d.ts +1 -0
  74. package/src/generator/surfaces/databases/shared.js +350 -0
  75. package/src/generator/surfaces/databases/snapshot.js +96 -0
  76. package/src/generator/surfaces/databases/sqlite/capabilities.js +14 -0
  77. package/src/generator/surfaces/databases/sqlite/index.js +8 -0
  78. package/src/generator/surfaces/databases/sqlite/lifecycle.js +16 -0
  79. package/src/generator/surfaces/databases/sqlite/prisma.js +143 -0
  80. package/src/generator/surfaces/databases/sqlite/sql-migration.js +65 -0
  81. package/src/generator/surfaces/databases/sqlite/sql-schema.js +27 -0
  82. package/src/generator/surfaces/index.js +25 -0
  83. package/src/generator/surfaces/native/swiftui-app.js +38 -0
  84. package/src/generator/surfaces/native/swiftui-templates/Package.swift.txt +20 -0
  85. package/src/generator/surfaces/native/swiftui-templates/README.generated.md +26 -0
  86. package/src/generator/surfaces/native/swiftui-templates/runtime/DynamicScreens.swift +682 -0
  87. package/src/generator/surfaces/native/swiftui-templates/runtime/TodoAPIClient.swift +156 -0
  88. package/src/generator/surfaces/native/swiftui-templates/runtime/TodoSwiftUIApp.swift +44 -0
  89. package/src/generator/surfaces/native/swiftui-templates/runtime/Visibility.swift +183 -0
  90. package/src/generator/surfaces/services/express.d.ts +1 -0
  91. package/src/generator/surfaces/services/express.js +766 -0
  92. package/src/generator/surfaces/services/hono.d.ts +1 -0
  93. package/src/generator/surfaces/services/hono.js +204 -0
  94. package/src/generator/surfaces/services/index.js +42 -0
  95. package/src/generator/surfaces/services/persistence-wiring.js +240 -0
  96. package/src/generator/surfaces/services/runtime-helpers.js +631 -0
  97. package/src/generator/surfaces/services/server-contract.js +80 -0
  98. package/src/generator/surfaces/services/stateless.d.ts +1 -0
  99. package/src/generator/surfaces/services/stateless.js +97 -0
  100. package/src/generator/surfaces/shared.js +64 -0
  101. package/src/generator/surfaces/web/api-client.js +1 -0
  102. package/src/generator/surfaces/web/forms.js +1 -0
  103. package/src/generator/surfaces/web/index.d.ts +2 -0
  104. package/src/generator/surfaces/web/index.js +53 -0
  105. package/src/generator/surfaces/web/react-components.js +248 -0
  106. package/src/generator/surfaces/web/react.js +538 -0
  107. package/src/generator/surfaces/web/routes.js +1 -0
  108. package/src/generator/surfaces/web/screens.js +1 -0
  109. package/src/generator/surfaces/web/shared.js +369 -0
  110. package/src/generator/surfaces/web/sveltekit-actions.js +28 -0
  111. package/src/generator/surfaces/web/sveltekit-components.js +234 -0
  112. package/src/generator/surfaces/web/sveltekit.js +426 -0
  113. package/src/generator/surfaces/web/ui-web-contract.js +65 -0
  114. package/src/generator/surfaces/web/vanilla.js +239 -0
  115. package/src/generator/verification.js +84 -0
  116. package/src/generator.js +1 -0
  117. package/src/import/core/context.js +52 -0
  118. package/src/import/core/contracts.js +23 -0
  119. package/src/import/core/registry.js +81 -0
  120. package/src/import/core/runner.js +646 -0
  121. package/src/import/core/shared.js +910 -0
  122. package/src/import/enrichers/auth-session.js +18 -0
  123. package/src/import/enrichers/django-rest.js +226 -0
  124. package/src/import/enrichers/doc-linking.js +20 -0
  125. package/src/import/enrichers/rails-controllers.js +246 -0
  126. package/src/import/enrichers/rails-models.js +130 -0
  127. package/src/import/enrichers/workflow-target-state.js +10 -0
  128. package/src/import/extractors/api/aspnet-core.js +304 -0
  129. package/src/import/extractors/api/django-routes.js +318 -0
  130. package/src/import/extractors/api/express.js +154 -0
  131. package/src/import/extractors/api/fastify.js +371 -0
  132. package/src/import/extractors/api/flutter-dio.js +135 -0
  133. package/src/import/extractors/api/generic-route-fallback.js +90 -0
  134. package/src/import/extractors/api/graphql-code-first.js +565 -0
  135. package/src/import/extractors/api/graphql-sdl.js +309 -0
  136. package/src/import/extractors/api/jaxrs.js +303 -0
  137. package/src/import/extractors/api/micronaut.js +213 -0
  138. package/src/import/extractors/api/next-route.js +50 -0
  139. package/src/import/extractors/api/next-server-action.js +51 -0
  140. package/src/import/extractors/api/nextauth.js +52 -0
  141. package/src/import/extractors/api/openapi-code.js +242 -0
  142. package/src/import/extractors/api/openapi.js +232 -0
  143. package/src/import/extractors/api/rails-routes.js +230 -0
  144. package/src/import/extractors/api/react-native-repository.js +128 -0
  145. package/src/import/extractors/api/retrofit.js +103 -0
  146. package/src/import/extractors/api/spring-web.js +372 -0
  147. package/src/import/extractors/api/swift-webapi.js +116 -0
  148. package/src/import/extractors/api/trpc.js +212 -0
  149. package/src/import/extractors/db/django-models.js +232 -0
  150. package/src/import/extractors/db/dotnet-models.js +93 -0
  151. package/src/import/extractors/db/drizzle.js +242 -0
  152. package/src/import/extractors/db/ef-core.js +221 -0
  153. package/src/import/extractors/db/flutter-entities.js +120 -0
  154. package/src/import/extractors/db/jpa.js +120 -0
  155. package/src/import/extractors/db/liquibase.js +180 -0
  156. package/src/import/extractors/db/mybatis-xml.js +145 -0
  157. package/src/import/extractors/db/prisma.js +185 -0
  158. package/src/import/extractors/db/rails-schema.js +175 -0
  159. package/src/import/extractors/db/react-native-entities.js +95 -0
  160. package/src/import/extractors/db/room.js +193 -0
  161. package/src/import/extractors/db/snapshot.js +112 -0
  162. package/src/import/extractors/db/sql.js +180 -0
  163. package/src/import/extractors/db/swiftdata.js +137 -0
  164. package/src/import/extractors/ui/android-compose.js +230 -0
  165. package/src/import/extractors/ui/backend-only.js +70 -0
  166. package/src/import/extractors/ui/blazor.js +227 -0
  167. package/src/import/extractors/ui/flutter-screens.js +152 -0
  168. package/src/import/extractors/ui/maui-xaml.js +135 -0
  169. package/src/import/extractors/ui/next-app-router.js +83 -0
  170. package/src/import/extractors/ui/next-pages-router.js +141 -0
  171. package/src/import/extractors/ui/razor-pages.js +181 -0
  172. package/src/import/extractors/ui/react-native-screens.js +166 -0
  173. package/src/import/extractors/ui/react-router.js +139 -0
  174. package/src/import/extractors/ui/sveltekit.js +123 -0
  175. package/src/import/extractors/ui/swiftui.js +193 -0
  176. package/src/import/extractors/ui/uikit.js +175 -0
  177. package/src/import/extractors/verification/generic.js +290 -0
  178. package/src/import/extractors/workflows/generic.js +137 -0
  179. package/src/import/index.js +7 -0
  180. package/src/import/provenance.js +158 -0
  181. package/src/new-project.js +2107 -0
  182. package/src/parser.js +439 -0
  183. package/src/policy/review-boundaries.js +165 -0
  184. package/src/project-config.js +535 -0
  185. package/src/proofs/backend-parity.js +19 -0
  186. package/src/proofs/contract-audit.js +220 -0
  187. package/src/proofs/ios-parity.js +7 -0
  188. package/src/proofs/issues-parity.js +10 -0
  189. package/src/proofs/web-parity.js +50 -0
  190. package/src/realization/api/build-api-realization.js +5 -0
  191. package/src/realization/api/index.js +1 -0
  192. package/src/realization/backend/build-backend-runtime-realization.js +82 -0
  193. package/src/realization/backend/index.d.ts +1 -0
  194. package/src/realization/backend/index.js +4 -0
  195. package/src/realization/db/build-db-realization.js +17 -0
  196. package/src/realization/db/index.js +3 -0
  197. package/src/realization/db/migration-plan.js +5 -0
  198. package/src/realization/db/snapshot.js +5 -0
  199. package/src/realization/ui/build-ui-shared-realization.js +305 -0
  200. package/src/realization/ui/build-web-realization.js +189 -0
  201. package/src/realization/ui/index.js +2 -0
  202. package/src/reconcile/docs.js +280 -0
  203. package/src/reconcile/index.js +3 -0
  204. package/src/reconcile/journeys.js +441 -0
  205. package/src/resolver/docs.js +1 -0
  206. package/src/resolver/enrich/acceptance-criterion.js +14 -0
  207. package/src/resolver/enrich/bug.js +12 -0
  208. package/src/resolver/enrich/component.js +2 -0
  209. package/src/resolver/enrich/index.js +1 -0
  210. package/src/resolver/enrich/pitch.js +18 -0
  211. package/src/resolver/enrich/requirement.js +20 -0
  212. package/src/resolver/enrich/task.js +16 -0
  213. package/src/resolver/expressions.js +1 -0
  214. package/src/resolver/index.js +2422 -0
  215. package/src/resolver/normalize.js +1 -0
  216. package/src/resolver.js +1 -0
  217. package/src/sdlc/adopt.js +65 -0
  218. package/src/sdlc/check.js +86 -0
  219. package/src/sdlc/dod/acceptance-criterion.js +22 -0
  220. package/src/sdlc/dod/bug.js +26 -0
  221. package/src/sdlc/dod/document.js +23 -0
  222. package/src/sdlc/dod/index.js +25 -0
  223. package/src/sdlc/dod/pitch.js +23 -0
  224. package/src/sdlc/dod/requirement.js +34 -0
  225. package/src/sdlc/dod/task.js +39 -0
  226. package/src/sdlc/explain.js +116 -0
  227. package/src/sdlc/history.js +80 -0
  228. package/src/sdlc/paths.js +11 -0
  229. package/src/sdlc/release.js +106 -0
  230. package/src/sdlc/scaffold.js +89 -0
  231. package/src/sdlc/status-filter.js +54 -0
  232. package/src/sdlc/transition.js +112 -0
  233. package/src/sdlc/transitions/acceptance-criterion.js +28 -0
  234. package/src/sdlc/transitions/bug.js +31 -0
  235. package/src/sdlc/transitions/document.js +29 -0
  236. package/src/sdlc/transitions/index.js +56 -0
  237. package/src/sdlc/transitions/pitch.js +34 -0
  238. package/src/sdlc/transitions/requirement.js +31 -0
  239. package/src/sdlc/transitions/task.js +34 -0
  240. package/src/template-trust.js +597 -0
  241. package/src/validator/expressions.js +1 -0
  242. package/src/validator/index.js +3424 -0
  243. package/src/validator/kinds.js +346 -0
  244. package/src/validator/per-kind/acceptance-criterion.js +91 -0
  245. package/src/validator/per-kind/bug.js +77 -0
  246. package/src/validator/per-kind/component.js +274 -0
  247. package/src/validator/per-kind/domain.js +205 -0
  248. package/src/validator/per-kind/pitch.js +101 -0
  249. package/src/validator/per-kind/requirement.js +75 -0
  250. package/src/validator/per-kind/task.js +96 -0
  251. package/src/validator/registry.js +1 -0
  252. package/src/validator/utils.js +12 -0
  253. package/src/validator.js +1 -0
  254. package/src/workflows.js +7597 -0
  255. package/src/workspace-docs.js +265 -0
  256. package/template-helpers/react.js +5 -0
  257. package/template-helpers/sveltekit.js +5 -0
@@ -0,0 +1,274 @@
1
+ import {
2
+ UI_PATTERN_KINDS,
3
+ UI_REGION_KINDS
4
+ } from "../kinds.js";
5
+ import {
6
+ blockEntries,
7
+ getFieldValue,
8
+ pushError,
9
+ symbolValues
10
+ } from "../utils.js";
11
+
12
+ const COMPONENT_CATEGORIES = new Set([
13
+ "collection",
14
+ "form",
15
+ "display",
16
+ "navigation",
17
+ "dialog",
18
+ "feedback",
19
+ "lookup",
20
+ "layout",
21
+ "service"
22
+ ]);
23
+
24
+ const COMPONENT_BEHAVIOR_KINDS = new Set([
25
+ "selection",
26
+ "sorting",
27
+ "filtering",
28
+ "search",
29
+ "pagination",
30
+ "grouping",
31
+ "drag_drop",
32
+ "inline_edit",
33
+ "bulk_action",
34
+ "optimistic_update",
35
+ "realtime_update",
36
+ "keyboard_navigation"
37
+ ]);
38
+
39
+ const COMPONENT_BEHAVIOR_DIRECTIVES = {
40
+ selection: new Set(["mode", "state", "emits"]),
41
+ sorting: new Set(["fields", "default"]),
42
+ filtering: new Set(["fields"]),
43
+ search: new Set(["fields"]),
44
+ pagination: new Set(["mode", "page_size"]),
45
+ grouping: new Set(["fields"]),
46
+ drag_drop: new Set(["axis", "reorder"]),
47
+ inline_edit: new Set(["fields", "submit", "emits"]),
48
+ bulk_action: new Set(["actions", "state", "emits"]),
49
+ optimistic_update: new Set(["actions", "rollback"]),
50
+ realtime_update: new Set(["source", "merge"]),
51
+ keyboard_navigation: new Set(["scope", "shortcuts"])
52
+ };
53
+
54
+ function tokenValue(token) {
55
+ return token?.value ?? null;
56
+ }
57
+
58
+ function tokenValues(token) {
59
+ if (!token) {
60
+ return [];
61
+ }
62
+ if (token.type === "list") {
63
+ return token.items.map((item) => item.value).filter((value) => value != null);
64
+ }
65
+ const value = tokenValue(token);
66
+ return value == null ? [] : [value];
67
+ }
68
+
69
+ function componentPropNames(statement) {
70
+ return new Set(blockEntries(getFieldValue(statement, "props"))
71
+ .map((entry) => entry.items[0])
72
+ .filter((item) => item?.type === "symbol")
73
+ .map((item) => item.value));
74
+ }
75
+
76
+ function componentEventNames(statement) {
77
+ return new Set(blockEntries(getFieldValue(statement, "events"))
78
+ .map((entry) => entry.items[0])
79
+ .filter((item) => item?.type === "symbol")
80
+ .map((item) => item.value));
81
+ }
82
+
83
+ function validateComponentCategory(errors, statement, fieldMap) {
84
+ const field = fieldMap.get("category")?.[0];
85
+ if (!field) {
86
+ return;
87
+ }
88
+
89
+ if (field.value.type !== "symbol") {
90
+ pushError(errors, `Field 'category' on component ${statement.id} must be a symbol`, field.loc);
91
+ return;
92
+ }
93
+
94
+ if (!COMPONENT_CATEGORIES.has(field.value.value)) {
95
+ pushError(errors, `Invalid component category '${field.value.value}' on component ${statement.id}`, field.loc);
96
+ }
97
+ }
98
+
99
+ function validateComponentProps(errors, statement) {
100
+ for (const entry of blockEntries(getFieldValue(statement, "props"))) {
101
+ const [name, type, requiredness, ...rest] = entry.items;
102
+ if (!name || !type || !requiredness) {
103
+ pushError(errors, `Component ${statement.id} props entries must be '<name> <type> <required|optional> [default <value>]'`, entry.loc);
104
+ continue;
105
+ }
106
+
107
+ if (name.type !== "symbol" || type.type !== "symbol" || requiredness.type !== "symbol") {
108
+ pushError(errors, `Component ${statement.id} props entries must start with symbols`, entry.loc);
109
+ continue;
110
+ }
111
+
112
+ if (requiredness.value !== "required" && requiredness.value !== "optional") {
113
+ pushError(errors, `Component ${statement.id} prop '${name.value}' must use required or optional`, requiredness.loc);
114
+ }
115
+
116
+ for (let i = 0; i < rest.length; i += 1) {
117
+ const token = rest[i];
118
+ if (token.type === "symbol" && token.value === "default") {
119
+ if (!rest[i + 1]) {
120
+ pushError(errors, `Component ${statement.id} prop '${name.value}' default is missing a value`, token.loc);
121
+ }
122
+ i += 1;
123
+ continue;
124
+ }
125
+ pushError(errors, `Component ${statement.id} prop '${name.value}' has unsupported directive '${token.value}'`, token.loc);
126
+ }
127
+ }
128
+ }
129
+
130
+ function validateComponentEvents(errors, statement, registry) {
131
+ for (const entry of blockEntries(getFieldValue(statement, "events"))) {
132
+ const [eventName, shapeRef] = entry.items;
133
+ if (!eventName || !shapeRef) {
134
+ pushError(errors, `Component ${statement.id} events entries must be '<event_name> <shape_id>'`, entry.loc);
135
+ continue;
136
+ }
137
+
138
+ if (eventName.type !== "symbol" || shapeRef.type !== "symbol") {
139
+ pushError(errors, `Component ${statement.id} events entries must use symbols`, entry.loc);
140
+ continue;
141
+ }
142
+
143
+ const target = registry.get(shapeRef.value);
144
+ if (!target) {
145
+ pushError(errors, `Component ${statement.id} event '${eventName.value}' references missing shape '${shapeRef.value}'`, shapeRef.loc);
146
+ continue;
147
+ }
148
+ if (target.kind !== "shape") {
149
+ pushError(errors, `Component ${statement.id} event '${eventName.value}' must reference a shape, found ${target.kind} '${target.id}'`, shapeRef.loc);
150
+ }
151
+ }
152
+ }
153
+
154
+ function validateComponentSlots(errors, statement) {
155
+ for (const entry of blockEntries(getFieldValue(statement, "slots"))) {
156
+ const [slotName, description] = entry.items;
157
+ if (!slotName || !description) {
158
+ pushError(errors, `Component ${statement.id} slots entries must be '<slot_name> <description>'`, entry.loc);
159
+ continue;
160
+ }
161
+
162
+ if (slotName.type !== "symbol" || (description.type !== "string" && description.type !== "symbol")) {
163
+ pushError(errors, `Component ${statement.id} slots entries must use a symbol name and string or symbol description`, entry.loc);
164
+ }
165
+ }
166
+ }
167
+
168
+ function validateSymbolList(errors, statement, fieldMap, key, allowed, label) {
169
+ const field = fieldMap.get(key)?.[0];
170
+ if (!field) {
171
+ return;
172
+ }
173
+
174
+ for (const value of symbolValues(field.value)) {
175
+ if (!allowed.has(value)) {
176
+ pushError(errors, `Component ${statement.id} ${label} '${value}' is not supported`, field.loc);
177
+ }
178
+ }
179
+ }
180
+
181
+ function validateBehaviorActionReferences(errors, statement, registry, kind, directive, valueToken, eventNames) {
182
+ for (const actionId of tokenValues(valueToken)) {
183
+ if (eventNames.has(actionId)) {
184
+ continue;
185
+ }
186
+ const target = registry.get(actionId);
187
+ if (target?.kind === "capability") {
188
+ continue;
189
+ }
190
+ pushError(
191
+ errors,
192
+ `Component ${statement.id} behavior '${kind}' references unknown event or capability '${actionId}' for '${directive}'`,
193
+ valueToken.loc
194
+ );
195
+ }
196
+ }
197
+
198
+ function validateComponentBehaviors(errors, statement, fieldMap, registry) {
199
+ validateSymbolList(errors, statement, fieldMap, "behavior", COMPONENT_BEHAVIOR_KINDS, "behavior");
200
+
201
+ const field = fieldMap.get("behaviors")?.[0];
202
+ if (!field || field.value.type !== "block") {
203
+ return;
204
+ }
205
+
206
+ const propNames = componentPropNames(statement);
207
+ const eventNames = componentEventNames(statement);
208
+
209
+ for (const entry of field.value.entries) {
210
+ const [kindToken, ...rest] = entry.items;
211
+ const kind = tokenValue(kindToken);
212
+ if (!kind || kindToken.type !== "symbol") {
213
+ pushError(errors, `Component ${statement.id} behaviors entries must start with a behavior kind`, entry.loc);
214
+ continue;
215
+ }
216
+ if (!COMPONENT_BEHAVIOR_KINDS.has(kind)) {
217
+ pushError(errors, `Component ${statement.id} behavior '${kind}' is not supported`, entry.loc);
218
+ continue;
219
+ }
220
+
221
+ const allowedDirectives = COMPONENT_BEHAVIOR_DIRECTIVES[kind] || new Set();
222
+ for (let i = 0; i < rest.length; i += 2) {
223
+ const directiveToken = rest[i];
224
+ const valueToken = rest[i + 1];
225
+ const directive = tokenValue(directiveToken);
226
+ if (!directive || directiveToken.type !== "symbol") {
227
+ pushError(errors, `Component ${statement.id} behavior '${kind}' directives must use symbol keys`, entry.loc);
228
+ continue;
229
+ }
230
+ if (!valueToken) {
231
+ pushError(errors, `Component ${statement.id} behavior '${kind}' is missing a value for '${directive}'`, directiveToken.loc);
232
+ continue;
233
+ }
234
+ if (!allowedDirectives.has(directive)) {
235
+ pushError(errors, `Component ${statement.id} behavior '${kind}' has unsupported directive '${directive}'`, directiveToken.loc);
236
+ continue;
237
+ }
238
+
239
+ if (directive === "state" && !propNames.has(tokenValue(valueToken))) {
240
+ pushError(errors, `Component ${statement.id} behavior '${kind}' references unknown prop '${tokenValue(valueToken)}' for '${directive}'`, valueToken.loc);
241
+ }
242
+ if (directive === "emits") {
243
+ for (const eventName of tokenValues(valueToken)) {
244
+ if (!eventNames.has(eventName)) {
245
+ pushError(errors, `Component ${statement.id} behavior '${kind}' references unknown event '${eventName}' for '${directive}'`, valueToken.loc);
246
+ }
247
+ }
248
+ }
249
+ if (directive === "actions" || directive === "submit") {
250
+ validateBehaviorActionReferences(errors, statement, registry, kind, directive, valueToken, eventNames);
251
+ }
252
+ if (kind === "selection" && directive === "mode" && !["single", "multi", "none"].includes(tokenValue(valueToken))) {
253
+ pushError(errors, `Component ${statement.id} behavior 'selection' has invalid mode '${tokenValue(valueToken)}'`, valueToken.loc);
254
+ }
255
+ if (kind === "pagination" && directive === "mode" && !["cursor", "paged", "infinite", "none"].includes(tokenValue(valueToken))) {
256
+ pushError(errors, `Component ${statement.id} behavior 'pagination' has invalid mode '${tokenValue(valueToken)}'`, valueToken.loc);
257
+ }
258
+ }
259
+ }
260
+ }
261
+
262
+ export function validateComponent(errors, statement, fieldMap, registry) {
263
+ if (statement.kind !== "component") {
264
+ return;
265
+ }
266
+
267
+ validateComponentCategory(errors, statement, fieldMap);
268
+ validateComponentProps(errors, statement);
269
+ validateComponentEvents(errors, statement, registry);
270
+ validateComponentSlots(errors, statement);
271
+ validateComponentBehaviors(errors, statement, fieldMap, registry);
272
+ validateSymbolList(errors, statement, fieldMap, "patterns", UI_PATTERN_KINDS, "pattern");
273
+ validateSymbolList(errors, statement, fieldMap, "regions", UI_REGION_KINDS, "region");
274
+ }
@@ -0,0 +1,205 @@
1
+ import {
2
+ DOMAIN_IDENTIFIER_PATTERN,
3
+ DOMAIN_TAGGABLE_KINDS
4
+ } from "../kinds.js";
5
+ import {
6
+ blockEntries,
7
+ getFieldValue,
8
+ pushError,
9
+ symbolValue,
10
+ symbolValues,
11
+ valueAsArray
12
+ } from "../utils.js";
13
+
14
+ function isStringOrSymbolList(value) {
15
+ for (const item of valueAsArray(value)) {
16
+ if (item.type !== "string" && item.type !== "symbol") {
17
+ return false;
18
+ }
19
+ }
20
+ return true;
21
+ }
22
+
23
+ function validateDomainIdentifier(errors, statement) {
24
+ if (statement.kind !== "domain") {
25
+ return;
26
+ }
27
+ if (!DOMAIN_IDENTIFIER_PATTERN.test(statement.id)) {
28
+ pushError(
29
+ errors,
30
+ `Domain identifier '${statement.id}' must match ${DOMAIN_IDENTIFIER_PATTERN.source}`,
31
+ statement.loc
32
+ );
33
+ }
34
+ }
35
+
36
+ function validateScopeFields(errors, statement, fieldMap) {
37
+ if (statement.kind !== "domain") {
38
+ return;
39
+ }
40
+ for (const key of ["in_scope", "out_of_scope", "aliases"]) {
41
+ const field = fieldMap.get(key)?.[0];
42
+ if (!field) continue;
43
+ if (!isStringOrSymbolList(field.value)) {
44
+ pushError(
45
+ errors,
46
+ `Field '${key}' on domain ${statement.id} must be a list of strings or symbols`,
47
+ field.loc
48
+ );
49
+ }
50
+ }
51
+ }
52
+
53
+ function validateParentDomain(errors, statement, fieldMap, registry) {
54
+ if (statement.kind !== "domain") {
55
+ return;
56
+ }
57
+ const field = fieldMap.get("parent_domain")?.[0];
58
+ if (!field) return;
59
+
60
+ const parentId = symbolValue(field.value);
61
+ if (!parentId) {
62
+ pushError(
63
+ errors,
64
+ `Field 'parent_domain' on domain ${statement.id} must be a single symbol`,
65
+ field.loc
66
+ );
67
+ return;
68
+ }
69
+
70
+ if (parentId === statement.id) {
71
+ pushError(
72
+ errors,
73
+ `Domain ${statement.id} cannot be its own parent_domain`,
74
+ field.loc
75
+ );
76
+ return;
77
+ }
78
+
79
+ const target = registry.get(parentId);
80
+ if (!target) {
81
+ pushError(
82
+ errors,
83
+ `Domain ${statement.id} parent_domain references missing domain '${parentId}'`,
84
+ field.loc
85
+ );
86
+ return;
87
+ }
88
+ if (target.kind !== "domain") {
89
+ pushError(
90
+ errors,
91
+ `Domain ${statement.id} parent_domain must reference a domain, found ${target.kind} '${target.id}'`,
92
+ field.loc
93
+ );
94
+ }
95
+ }
96
+
97
+ function validateOwners(errors, statement, fieldMap, registry) {
98
+ if (statement.kind !== "domain") {
99
+ return;
100
+ }
101
+ const field = fieldMap.get("owners")?.[0];
102
+ if (!field) return;
103
+
104
+ for (const id of symbolValues(field.value)) {
105
+ const target = registry.get(id);
106
+ if (!target) {
107
+ pushError(
108
+ errors,
109
+ `Domain ${statement.id} owners references missing statement '${id}'`,
110
+ field.loc
111
+ );
112
+ continue;
113
+ }
114
+ if (target.kind !== "actor" && target.kind !== "role") {
115
+ pushError(
116
+ errors,
117
+ `Domain ${statement.id} owners must reference actor or role, found ${target.kind} '${target.id}'`,
118
+ field.loc
119
+ );
120
+ }
121
+ }
122
+ }
123
+
124
+ function detectParentDomainCycle(errors, statement, fieldMap, registry) {
125
+ if (statement.kind !== "domain") {
126
+ return;
127
+ }
128
+ const field = fieldMap.get("parent_domain")?.[0];
129
+ if (!field) return;
130
+
131
+ const seen = new Set([statement.id]);
132
+ let cursor = registry.get(symbolValue(field.value));
133
+ while (cursor && cursor.kind === "domain") {
134
+ if (seen.has(cursor.id)) {
135
+ pushError(
136
+ errors,
137
+ `Domain ${statement.id} parent_domain chain forms a cycle through '${cursor.id}'`,
138
+ field.loc
139
+ );
140
+ return;
141
+ }
142
+ seen.add(cursor.id);
143
+ const parentField = cursor.fields.find((f) => f.key === "parent_domain");
144
+ if (!parentField) break;
145
+ cursor = registry.get(symbolValue(parentField.value));
146
+ }
147
+ }
148
+
149
+ export function validateDomain(errors, statement, fieldMap, registry) {
150
+ if (statement.kind !== "domain") {
151
+ return;
152
+ }
153
+ validateDomainIdentifier(errors, statement);
154
+ validateScopeFields(errors, statement, fieldMap);
155
+ validateParentDomain(errors, statement, fieldMap, registry);
156
+ validateOwners(errors, statement, fieldMap, registry);
157
+ detectParentDomainCycle(errors, statement, fieldMap, registry);
158
+ }
159
+
160
+ // Cross-kind validator: when *any* statement carries a `domain` field, verify
161
+ // it resolves to kind=domain. Skipped on the `domain` kind itself (its own
162
+ // `parent_domain` field handles that case).
163
+ export function validateDomainTag(errors, statement, fieldMap, registry) {
164
+ if (statement.kind === "domain") {
165
+ return;
166
+ }
167
+ const field = fieldMap.get("domain")?.[0];
168
+ if (!field) return;
169
+
170
+ if (!DOMAIN_TAGGABLE_KINDS.has(statement.kind)) {
171
+ pushError(
172
+ errors,
173
+ `Field 'domain' is not allowed on ${statement.kind} ${statement.id}`,
174
+ field.loc
175
+ );
176
+ return;
177
+ }
178
+
179
+ const id = symbolValue(field.value);
180
+ if (!id) {
181
+ pushError(
182
+ errors,
183
+ `Field 'domain' on ${statement.kind} ${statement.id} must be a single symbol`,
184
+ field.loc
185
+ );
186
+ return;
187
+ }
188
+
189
+ const target = registry.get(id);
190
+ if (!target) {
191
+ pushError(
192
+ errors,
193
+ `${statement.kind} ${statement.id} domain references missing domain '${id}'`,
194
+ field.loc
195
+ );
196
+ return;
197
+ }
198
+ if (target.kind !== "domain") {
199
+ pushError(
200
+ errors,
201
+ `${statement.kind} ${statement.id} domain must reference a domain, found ${target.kind} '${target.id}'`,
202
+ field.loc
203
+ );
204
+ }
205
+ }
@@ -0,0 +1,101 @@
1
+ import {
2
+ PITCH_IDENTIFIER_PATTERN,
3
+ PRIORITY_VALUES
4
+ } from "../kinds.js";
5
+ import {
6
+ pushError,
7
+ symbolValue,
8
+ symbolValues
9
+ } from "../utils.js";
10
+
11
+ function validatePitchIdentifier(errors, statement) {
12
+ if (!PITCH_IDENTIFIER_PATTERN.test(statement.id)) {
13
+ pushError(
14
+ errors,
15
+ `Pitch identifier '${statement.id}' must match ${PITCH_IDENTIFIER_PATTERN.source}`,
16
+ statement.loc
17
+ );
18
+ }
19
+ }
20
+
21
+ function validatePriority(errors, statement, fieldMap) {
22
+ const field = fieldMap.get("priority")?.[0];
23
+ if (!field) return;
24
+ if (field.value.type !== "symbol") {
25
+ pushError(errors, `Field 'priority' on pitch ${statement.id} must be a symbol`, field.loc);
26
+ return;
27
+ }
28
+ if (!PRIORITY_VALUES.has(field.value.value)) {
29
+ pushError(
30
+ errors,
31
+ `Invalid priority '${field.value.value}' on pitch ${statement.id}`,
32
+ field.loc
33
+ );
34
+ }
35
+ }
36
+
37
+ function validateAppetite(errors, statement, fieldMap) {
38
+ const field = fieldMap.get("appetite")?.[0];
39
+ if (!field) return;
40
+ if (field.value.type !== "string" && field.value.type !== "symbol") {
41
+ pushError(
42
+ errors,
43
+ `Field 'appetite' on pitch ${statement.id} must be a string or symbol`,
44
+ field.loc
45
+ );
46
+ }
47
+ }
48
+
49
+ function validateShapingFields(errors, statement, fieldMap) {
50
+ // When a pitch enters status `shaped`, the appetite, rabbit_holes, and
51
+ // no_go_areas sections must be filled. This mirrors the Forge pitch
52
+ // shaping discipline.
53
+ const statusField = fieldMap.get("status")?.[0];
54
+ if (!statusField || statusField.value.type !== "symbol") return;
55
+ const status = statusField.value.value;
56
+ if (status !== "shaped" && status !== "submitted" && status !== "approved") return;
57
+
58
+ for (const required of ["appetite", "rabbit_holes", "no_go_areas"]) {
59
+ if (!fieldMap.has(required)) {
60
+ pushError(
61
+ errors,
62
+ `Pitch ${statement.id} status '${status}' requires field '${required}' to be filled`,
63
+ statusField.loc
64
+ );
65
+ }
66
+ }
67
+ }
68
+
69
+ function validateDecisionRefs(errors, statement, fieldMap, registry) {
70
+ const field = fieldMap.get("decisions")?.[0];
71
+ if (!field) return;
72
+ for (const id of symbolValues(field.value)) {
73
+ const target = registry.get(id);
74
+ if (!target) {
75
+ pushError(
76
+ errors,
77
+ `Pitch ${statement.id} decisions references missing decision '${id}'`,
78
+ field.loc
79
+ );
80
+ continue;
81
+ }
82
+ if (target.kind !== "decision") {
83
+ pushError(
84
+ errors,
85
+ `Pitch ${statement.id} decisions must reference decision, found ${target.kind} '${target.id}'`,
86
+ field.loc
87
+ );
88
+ }
89
+ }
90
+ }
91
+
92
+ export function validatePitch(errors, statement, fieldMap, registry) {
93
+ if (statement.kind !== "pitch") {
94
+ return;
95
+ }
96
+ validatePitchIdentifier(errors, statement);
97
+ validatePriority(errors, statement, fieldMap);
98
+ validateAppetite(errors, statement, fieldMap);
99
+ validateShapingFields(errors, statement, fieldMap);
100
+ validateDecisionRefs(errors, statement, fieldMap, registry);
101
+ }
@@ -0,0 +1,75 @@
1
+ import {
2
+ REQUIREMENT_IDENTIFIER_PATTERN,
3
+ PRIORITY_VALUES
4
+ } from "../kinds.js";
5
+ import {
6
+ pushError,
7
+ symbolValue,
8
+ symbolValues
9
+ } from "../utils.js";
10
+
11
+ function validateRequirementIdentifier(errors, statement) {
12
+ if (!REQUIREMENT_IDENTIFIER_PATTERN.test(statement.id)) {
13
+ pushError(
14
+ errors,
15
+ `Requirement identifier '${statement.id}' must match ${REQUIREMENT_IDENTIFIER_PATTERN.source}`,
16
+ statement.loc
17
+ );
18
+ }
19
+ }
20
+
21
+ function validatePriority(errors, statement, fieldMap) {
22
+ const field = fieldMap.get("priority")?.[0];
23
+ if (!field) return;
24
+ if (field.value.type !== "symbol") {
25
+ pushError(errors, `Field 'priority' on requirement ${statement.id} must be a symbol`, field.loc);
26
+ return;
27
+ }
28
+ if (!PRIORITY_VALUES.has(field.value.value)) {
29
+ pushError(
30
+ errors,
31
+ `Invalid priority '${field.value.value}' on requirement ${statement.id}`,
32
+ field.loc
33
+ );
34
+ }
35
+ }
36
+
37
+ function validateSupersedes(errors, statement, fieldMap, registry) {
38
+ const field = fieldMap.get("supersedes")?.[0];
39
+ if (!field) return;
40
+ for (const id of symbolValues(field.value)) {
41
+ if (id === statement.id) {
42
+ pushError(
43
+ errors,
44
+ `Requirement ${statement.id} cannot supersede itself`,
45
+ field.loc
46
+ );
47
+ continue;
48
+ }
49
+ const target = registry.get(id);
50
+ if (!target) {
51
+ pushError(
52
+ errors,
53
+ `Requirement ${statement.id} supersedes references missing requirement '${id}'`,
54
+ field.loc
55
+ );
56
+ continue;
57
+ }
58
+ if (target.kind !== "requirement") {
59
+ pushError(
60
+ errors,
61
+ `Requirement ${statement.id} supersedes must reference requirement, found ${target.kind} '${target.id}'`,
62
+ field.loc
63
+ );
64
+ }
65
+ }
66
+ }
67
+
68
+ export function validateRequirement(errors, statement, fieldMap, registry) {
69
+ if (statement.kind !== "requirement") {
70
+ return;
71
+ }
72
+ validateRequirementIdentifier(errors, statement);
73
+ validatePriority(errors, statement, fieldMap);
74
+ validateSupersedes(errors, statement, fieldMap, registry);
75
+ }