@topogram/cli 0.3.64 → 0.3.66

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 (278) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +716 -0
  3. package/src/adoption/plan.js +12 -703
  4. package/src/adoption/reporting.js +1 -1
  5. package/src/agent-brief.js +7 -21
  6. package/src/agent-ops/query-builders/auth.js +375 -0
  7. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  8. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  9. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  10. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  11. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  12. package/src/agent-ops/query-builders/change-risk.js +25 -0
  13. package/src/agent-ops/query-builders/common.js +149 -0
  14. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  15. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  16. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  17. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  18. package/src/agent-ops/query-builders/work-packets.js +417 -0
  19. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  20. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  21. package/src/agent-ops/query-builders/workflow-presets-core.js +677 -0
  22. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  23. package/src/agent-ops/query-builders.d.ts +26 -26
  24. package/src/agent-ops/query-builders.js +42 -5021
  25. package/src/archive/jsonl.js +2 -2
  26. package/src/archive/resolver-bridge.js +1 -1
  27. package/src/archive/unarchive.js +2 -1
  28. package/src/catalog/constants.js +10 -0
  29. package/src/catalog/copy.js +65 -0
  30. package/src/catalog/diagnostics.js +15 -0
  31. package/src/catalog/entries.js +42 -0
  32. package/src/catalog/files.js +67 -0
  33. package/src/catalog/provenance.js +123 -0
  34. package/src/catalog/source.js +150 -0
  35. package/src/catalog/validation.js +252 -0
  36. package/src/catalog.d.ts +2 -0
  37. package/src/catalog.js +18 -746
  38. package/src/cli/command-parsers/project.js +3 -0
  39. package/src/cli/command-parsers/shared.js +1 -1
  40. package/src/cli/commands/agent.js +2 -2
  41. package/src/cli/commands/catalog/check.js +31 -0
  42. package/src/cli/commands/catalog/copy.js +59 -0
  43. package/src/cli/commands/catalog/doctor.js +248 -0
  44. package/src/cli/commands/catalog/help.js +21 -0
  45. package/src/cli/commands/catalog/list.js +52 -0
  46. package/src/cli/commands/catalog/runner.js +92 -0
  47. package/src/cli/commands/catalog/shared.js +17 -0
  48. package/src/cli/commands/catalog/show.js +134 -0
  49. package/src/cli/commands/catalog.js +30 -615
  50. package/src/cli/commands/check.js +3 -3
  51. package/src/cli/commands/doctor.js +2 -9
  52. package/src/cli/commands/generator-policy/package-info.js +162 -0
  53. package/src/cli/commands/generator-policy/payloads.js +372 -0
  54. package/src/cli/commands/generator-policy/printers.js +159 -0
  55. package/src/cli/commands/generator-policy/runner.js +81 -0
  56. package/src/cli/commands/generator-policy/shared.js +39 -0
  57. package/src/cli/commands/generator-policy.js +15 -783
  58. package/src/cli/commands/import/adopt.js +170 -0
  59. package/src/cli/commands/import/check.js +91 -0
  60. package/src/cli/commands/import/diff.js +84 -0
  61. package/src/cli/commands/import/help.js +47 -0
  62. package/src/cli/commands/import/paths.js +269 -0
  63. package/src/cli/commands/import/plan.js +292 -0
  64. package/src/cli/commands/import/refresh.js +471 -0
  65. package/src/cli/commands/import/status-history.js +196 -0
  66. package/src/cli/commands/import/workspace.js +233 -0
  67. package/src/cli/commands/import.js +33 -1732
  68. package/src/cli/commands/migrate.js +153 -0
  69. package/src/cli/commands/package/constants.js +17 -0
  70. package/src/cli/commands/package/doctor.js +240 -0
  71. package/src/cli/commands/package/help.js +27 -0
  72. package/src/cli/commands/package/lockfile.js +135 -0
  73. package/src/cli/commands/package/npm.js +97 -0
  74. package/src/cli/commands/package/reporting.js +35 -0
  75. package/src/cli/commands/package/runner.js +33 -0
  76. package/src/cli/commands/package/shared.js +9 -0
  77. package/src/cli/commands/package/update-cli.js +252 -0
  78. package/src/cli/commands/package/versions.js +35 -0
  79. package/src/cli/commands/package.js +29 -813
  80. package/src/cli/commands/query/change-plan.js +68 -0
  81. package/src/cli/commands/query/definitions.js +202 -0
  82. package/src/cli/commands/query/import-adopt.js +121 -0
  83. package/src/cli/commands/query/runner/artifacts.js +102 -0
  84. package/src/cli/commands/query/runner/boundaries.js +211 -0
  85. package/src/cli/commands/query/runner/change.js +182 -0
  86. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  87. package/src/cli/commands/query/runner/index.js +31 -0
  88. package/src/cli/commands/query/runner/output.js +12 -0
  89. package/src/cli/commands/query/runner/workflow.js +241 -0
  90. package/src/cli/commands/query/runner.js +3 -0
  91. package/src/cli/commands/query/workflow-context.js +5 -0
  92. package/src/cli/commands/query/workspace.js +270 -0
  93. package/src/cli/commands/query.js +9 -1300
  94. package/src/cli/commands/source.js +3 -12
  95. package/src/cli/commands/template/baseline.js +100 -0
  96. package/src/cli/commands/template/check.js +467 -0
  97. package/src/cli/commands/template/constants.js +8 -0
  98. package/src/cli/commands/template/diagnostics.js +26 -0
  99. package/src/cli/commands/template/help.js +28 -0
  100. package/src/cli/commands/template/lifecycle.js +404 -0
  101. package/src/cli/commands/template/list-show.js +287 -0
  102. package/src/cli/commands/template/policy.js +422 -0
  103. package/src/cli/commands/template/shared.js +127 -0
  104. package/src/cli/commands/template/updates.js +352 -0
  105. package/src/cli/commands/template-runner.js +6 -6
  106. package/src/cli/commands/template.js +41 -2143
  107. package/src/cli/commands/trust.js +1 -1
  108. package/src/cli/commands/workflow.js +6 -1
  109. package/src/cli/dispatcher.js +6 -1
  110. package/src/cli/help.js +15 -14
  111. package/src/cli/migration-guidance.js +1 -1
  112. package/src/cli/output-safety.js +2 -1
  113. package/src/cli/path-normalization.js +3 -13
  114. package/src/generator/api/contracts.js +497 -0
  115. package/src/generator/api/metadata.js +221 -0
  116. package/src/generator/api/openapi.js +559 -0
  117. package/src/generator/api/schema.js +124 -0
  118. package/src/generator/api/types.d.ts +98 -0
  119. package/src/generator/api.js +3 -1195
  120. package/src/generator/context/domain-page.js +1 -1
  121. package/src/generator/context/shared/domain-sdlc.js +282 -0
  122. package/src/generator/context/shared/maintained-boundary.js +665 -0
  123. package/src/generator/context/shared/metrics.js +85 -0
  124. package/src/generator/context/shared/primitives.js +64 -0
  125. package/src/generator/context/shared/relationships.js +453 -0
  126. package/src/generator/context/shared/summaries.js +263 -0
  127. package/src/generator/context/shared/types.d.ts +207 -0
  128. package/src/generator/context/shared.d.ts +42 -0
  129. package/src/generator/context/shared.js +80 -1390
  130. package/src/generator/context/slice/core.js +397 -0
  131. package/src/generator/context/slice/sdlc.js +417 -0
  132. package/src/generator/context/slice/ui-packets.js +183 -0
  133. package/src/generator/context/slice.js +2 -859
  134. package/src/generator/context/task-mode.js +2 -2
  135. package/src/generator/registry/index.js +507 -0
  136. package/src/generator/registry.js +18 -504
  137. package/src/generator/runtime/environment/index.js +666 -0
  138. package/src/generator/runtime/environment.js +4 -666
  139. package/src/generator/runtime/runtime-check/index.js +554 -0
  140. package/src/generator/runtime/runtime-check.js +4 -554
  141. package/src/generator/runtime/shared/index.js +572 -0
  142. package/src/generator/runtime/shared.js +19 -570
  143. package/src/generator/sdlc/doc-page.js +1 -1
  144. package/src/generator/shared.d.ts +2 -0
  145. package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
  146. package/src/generator/surfaces/native/swiftui-templates/README.generated.md +1 -1
  147. package/src/generator/surfaces/shared.d.ts +3 -0
  148. package/src/generator/widget-conformance/behavior-report.js +258 -0
  149. package/src/generator/widget-conformance/checks.js +371 -0
  150. package/src/generator/widget-conformance/projection-context.js +200 -0
  151. package/src/generator/widget-conformance/report.js +166 -0
  152. package/src/generator/widget-conformance/types.d.ts +121 -0
  153. package/src/generator/widget-conformance.js +3 -824
  154. package/src/import/core/context.d.ts +3 -0
  155. package/src/import/core/context.js +5 -7
  156. package/src/import/core/contracts.d.ts +1 -0
  157. package/src/import/core/registry.d.ts +4 -0
  158. package/src/import/core/runner/candidates.js +337 -0
  159. package/src/import/core/runner/options.js +22 -0
  160. package/src/import/core/runner/reports.js +51 -0
  161. package/src/import/core/runner/run.js +79 -0
  162. package/src/import/core/runner/tracks.js +150 -0
  163. package/src/import/core/runner/ui-drafts.js +393 -0
  164. package/src/import/core/runner.js +3 -698
  165. package/src/import/core/shared/api-routes.js +221 -0
  166. package/src/import/core/shared/candidates.js +97 -0
  167. package/src/import/core/shared/files.js +177 -0
  168. package/src/import/core/shared/next-app.js +389 -0
  169. package/src/import/core/shared/types.d.ts +51 -0
  170. package/src/import/core/shared/ui-routes.js +230 -0
  171. package/src/import/core/shared.js +60 -861
  172. package/src/new-project/constants.js +128 -0
  173. package/src/new-project/create.js +90 -0
  174. package/src/new-project/json.js +28 -0
  175. package/src/new-project/metadata.js +96 -0
  176. package/src/new-project/package-spec.js +161 -0
  177. package/src/new-project/project-files.js +351 -0
  178. package/src/new-project/template-policy.js +269 -0
  179. package/src/new-project/template-resolution.js +370 -0
  180. package/src/new-project/template-snapshots.js +442 -0
  181. package/src/new-project/template-updates.js +512 -0
  182. package/src/new-project/types.d.ts +83 -0
  183. package/src/new-project.js +6 -2277
  184. package/src/parser.d.ts +87 -1
  185. package/src/parser.js +118 -0
  186. package/src/policy/review-boundaries.d.ts +15 -0
  187. package/src/project-config/index.js +591 -0
  188. package/src/project-config.js +19 -561
  189. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  190. package/src/resolver/enrich/bug.js +2 -0
  191. package/src/resolver/enrich/pitch.js +2 -0
  192. package/src/resolver/enrich/requirement.js +2 -0
  193. package/src/resolver/enrich/task.js +2 -0
  194. package/src/resolver/index.js +19 -2089
  195. package/src/resolver/normalize.js +384 -1
  196. package/src/resolver/plans.js +168 -0
  197. package/src/resolver/projections-api.js +494 -0
  198. package/src/resolver/projections-db.js +133 -0
  199. package/src/resolver/projections-ui.js +317 -0
  200. package/src/resolver/shapes.js +251 -0
  201. package/src/resolver/shared.js +278 -0
  202. package/src/resolver/widgets.js +132 -0
  203. package/src/sdlc/adopt.js +6 -5
  204. package/src/sdlc/paths.js +3 -5
  205. package/src/sdlc/scaffold.js +2 -1
  206. package/src/template-trust/constants.js +62 -0
  207. package/src/template-trust/content.js +258 -0
  208. package/src/template-trust/diff.js +92 -0
  209. package/src/template-trust/policy.js +61 -0
  210. package/src/template-trust/record.js +90 -0
  211. package/src/template-trust/status.js +182 -0
  212. package/src/template-trust.js +24 -687
  213. package/src/text-helpers.d.ts +1 -0
  214. package/src/topogram-types.d.ts +69 -0
  215. package/src/validator/common.js +488 -0
  216. package/src/validator/data-model.js +237 -0
  217. package/src/validator/docs.js +167 -0
  218. package/src/validator/expressions.js +146 -1
  219. package/src/validator/index.d.ts +23 -0
  220. package/src/validator/index.js +32 -3585
  221. package/src/validator/kinds.d.ts +41 -0
  222. package/src/validator/kinds.js +2 -0
  223. package/src/validator/model-helpers.js +46 -0
  224. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  225. package/src/validator/per-kind/bug.js +6 -0
  226. package/src/validator/per-kind/domain.js +15 -2
  227. package/src/validator/per-kind/pitch.js +7 -0
  228. package/src/validator/per-kind/requirement.js +5 -0
  229. package/src/validator/per-kind/task.js +7 -0
  230. package/src/validator/per-kind/widget.js +14 -0
  231. package/src/validator/projections/api-http-async.js +410 -0
  232. package/src/validator/projections/api-http-authz.js +88 -0
  233. package/src/validator/projections/api-http-core.js +205 -0
  234. package/src/validator/projections/api-http-policies.js +339 -0
  235. package/src/validator/projections/api-http-responses.js +233 -0
  236. package/src/validator/projections/api-http.js +44 -0
  237. package/src/validator/projections/db.js +353 -0
  238. package/src/validator/projections/generator-defaults.js +45 -0
  239. package/src/validator/projections/helpers.js +87 -0
  240. package/src/validator/projections/ui-helpers.js +214 -0
  241. package/src/validator/projections/ui-navigation.js +344 -0
  242. package/src/validator/projections/ui-structure.js +364 -0
  243. package/src/validator/projections/ui-widgets.js +493 -0
  244. package/src/validator/projections/ui.js +46 -0
  245. package/src/validator/registry.js +48 -1
  246. package/src/validator/utils.d.ts +20 -0
  247. package/src/validator/utils.js +115 -12
  248. package/src/widget-behavior.d.ts +1 -0
  249. package/src/workflows/import-app/api/collect.js +221 -0
  250. package/src/workflows/import-app/api/openapi.js +257 -0
  251. package/src/workflows/import-app/api/routes.js +327 -0
  252. package/src/workflows/import-app/api/sources.js +22 -0
  253. package/src/workflows/import-app/api.js +2 -797
  254. package/src/workflows/reconcile/adoption-plan/build.js +212 -0
  255. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  256. package/src/workflows/reconcile/adoption-plan/outputs.js +153 -0
  257. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  258. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  259. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  260. package/src/workflows/reconcile/adoption-plan.js +30 -740
  261. package/src/workflows/reconcile/auth/closures.js +115 -0
  262. package/src/workflows/reconcile/auth/formatters.js +142 -0
  263. package/src/workflows/reconcile/auth/inference.js +330 -0
  264. package/src/workflows/reconcile/auth/roles.js +122 -0
  265. package/src/workflows/reconcile/auth.js +35 -690
  266. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  267. package/src/workflows/reconcile/bundle-core.js +12 -598
  268. package/src/workflows/reconcile/candidate-model.js +18 -2
  269. package/src/workflows/reconcile/canonical-surface.js +1 -1
  270. package/src/workflows/reconcile/impacts/adoption-plan.js +196 -0
  271. package/src/workflows/reconcile/impacts/indexes.js +105 -0
  272. package/src/workflows/reconcile/impacts/patches.js +252 -0
  273. package/src/workflows/reconcile/impacts/reports.js +80 -0
  274. package/src/workflows/reconcile/impacts.js +14 -623
  275. package/src/workflows/reconcile/renderers.js +41 -6
  276. package/src/workflows/shared.js +5 -11
  277. package/src/workspace-docs.d.ts +29 -0
  278. package/src/workspace-paths.js +328 -0
@@ -2,5 +2,6 @@ export const canonicalCandidateTerm: (value: unknown, options?: { technicalStopw
2
2
  export const ensureTrailingNewline: (value: string) => string;
3
3
  export const extractRankedTerms: (markdown: string, options?: { technicalStopwords?: boolean }) => string[];
4
4
  export const idHintify: (value: unknown) => string;
5
+ export const pluralizeCandidateTerm: (value: unknown) => string;
5
6
  export const slugify: (value: unknown) => string;
6
7
  export const titleCase: (value: unknown) => string;
@@ -0,0 +1,69 @@
1
+ export interface TopogramLocation {
2
+ file?: string;
3
+ start?: {
4
+ line?: number;
5
+ column?: number;
6
+ offset?: number;
7
+ };
8
+ end?: {
9
+ line?: number;
10
+ column?: number;
11
+ offset?: number;
12
+ };
13
+ }
14
+
15
+ export interface TopogramToken {
16
+ type: string;
17
+ value: any;
18
+ items: TopogramToken[];
19
+ entries: TopogramBlockEntry[];
20
+ loc: TopogramLocation;
21
+ [key: string]: any;
22
+ }
23
+
24
+ export interface TopogramBlockEntry {
25
+ items: TopogramToken[];
26
+ loc: TopogramLocation;
27
+ [key: string]: any;
28
+ }
29
+
30
+ export interface TopogramField {
31
+ key: string;
32
+ value: TopogramToken;
33
+ loc: TopogramLocation;
34
+ [key: string]: any;
35
+ }
36
+
37
+ export interface TopogramStatement {
38
+ id: string;
39
+ kind: string;
40
+ fields: TopogramField[];
41
+ loc: TopogramLocation;
42
+ [key: string]: any;
43
+ }
44
+
45
+ export interface ValidationError {
46
+ message: string;
47
+ loc?: TopogramLocation | null;
48
+ }
49
+
50
+ export type ValidationErrors = ValidationError[];
51
+
52
+ export type TopogramFieldMap = Map<string, TopogramField[]>;
53
+
54
+ export type TopogramRegistry = Map<string, TopogramStatement>;
55
+
56
+ export type ResolverBacklinkIndex = Record<string, Map<string, string[]>>;
57
+
58
+ declare global {
59
+ type TopogramLocation = import("./topogram-types.js").TopogramLocation;
60
+ type TopogramToken = import("./topogram-types.js").TopogramToken;
61
+ type TopogramBlockEntry = import("./topogram-types.js").TopogramBlockEntry;
62
+ type TopogramField = import("./topogram-types.js").TopogramField;
63
+ type TopogramStatement = import("./topogram-types.js").TopogramStatement;
64
+ type ValidationError = import("./topogram-types.js").ValidationError;
65
+ type ValidationErrors = import("./topogram-types.js").ValidationErrors;
66
+ type TopogramFieldMap = import("./topogram-types.js").TopogramFieldMap;
67
+ type TopogramRegistry = import("./topogram-types.js").TopogramRegistry;
68
+ type ResolverBacklinkIndex = import("./topogram-types.js").ResolverBacklinkIndex;
69
+ }
@@ -0,0 +1,488 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ FIELD_SPECS,
5
+ GLOBAL_STATUSES,
6
+ RULE_SEVERITIES,
7
+ STATEMENT_KINDS,
8
+ STATUS_SETS_BY_KIND,
9
+ VERIFICATION_METHODS
10
+ } from "./kinds.js";
11
+ import {
12
+ pushError,
13
+ symbolValue,
14
+ valueAsArray
15
+ } from "./utils.js";
16
+
17
+ /**
18
+ * @param {string} oldName
19
+ * @param {string} newName
20
+ * @param {string} example
21
+ * @returns {string}
22
+ */
23
+ function renameDiagnostic(oldName, newName, example) {
24
+ return `${oldName} was renamed to ${newName}. Example fix: ${example}`;
25
+ }
26
+
27
+ /**
28
+ * @param {ValidationErrors} errors
29
+ * @param {TopogramStatement} statement
30
+ * @param {TopogramFieldMap} fieldMap
31
+ * @param {string} key
32
+ * @param {string[]} expectedTypes
33
+ * @returns {void}
34
+ */
35
+ function ensureSingleValueField(errors, statement, fieldMap, key, expectedTypes) {
36
+ const fields = fieldMap.get(key) || [];
37
+ if (fields.length > 1) {
38
+ for (const field of fields.slice(1)) {
39
+ pushError(errors, `Duplicate field '${key}' on ${statement.kind} ${statement.id}`, field.loc);
40
+ }
41
+ }
42
+
43
+ const field = fields[0];
44
+ if (!field) {
45
+ return;
46
+ }
47
+
48
+ if (!expectedTypes.includes(field.value.type)) {
49
+ pushError(
50
+ errors,
51
+ `Field '${key}' on ${statement.kind} ${statement.id} must be ${expectedTypes.join(" or ")}, found ${field.value.type}`,
52
+ field.loc
53
+ );
54
+ }
55
+ }
56
+
57
+ /**
58
+ * @param {ValidationErrors} errors
59
+ * @param {TopogramStatement} statement
60
+ * @param {TopogramFieldMap} fieldMap
61
+ * @returns {void}
62
+ */
63
+ function validateFieldPresence(errors, statement, fieldMap) {
64
+ const spec = FIELD_SPECS[statement.kind];
65
+ if (!spec) {
66
+ return;
67
+ }
68
+
69
+ /** @type {Map<string, [string, string]>} */
70
+ const renamedFields = new Map([
71
+ ["platform", ["type", "type web_surface"]],
72
+ ["ui_components", ["widget_bindings", "widget_bindings { screen item_list region results widget widget_data_grid }"]],
73
+ ["ui_design", ["design_tokens", "design_tokens { density comfortable tone operational }"]],
74
+ ["ui_routes", ["screen_routes", "screen_routes { screen item_list path /items }"]],
75
+ ["ui_screens", ["screens", "screens { item_list list title \"Items\" }"]],
76
+ ["ui_screen_regions", ["screen_regions", "screen_regions { screen item_list region results pattern resource_table }"]],
77
+ ["ui_navigation", ["navigation", "navigation { primary item_list label \"Items\" }"]],
78
+ ["ui_app_shell", ["app_shell", "app_shell { shell sidebar }"]],
79
+ ["ui_collections", ["collection_views", "collection_views { item_list presentation table }"]],
80
+ ["ui_actions", ["screen_actions", "screen_actions { screen item_list action create target cap_create_item }"]],
81
+ ["ui_visibility", ["visibility_rules", "visibility_rules { screen item_list when cap_list_items }"]],
82
+ ["ui_lookups", ["field_lookups", "field_lookups { field owner_id source cap_list_users }"]],
83
+ ["web_surface", ["web_hints", "web_hints { router file_based }"]],
84
+ ["ios_surface", ["ios_hints", "ios_hints { navigation stack }"]],
85
+ ["http", ["endpoints", "endpoints { cap_list_items method GET path /items success 200 }"]],
86
+ ["http_errors", ["error_responses", "error_responses { cap_list_items 404 shape_error }"]],
87
+ ["http_fields", ["wire_fields", "wire_fields { shape_item title title }"]],
88
+ ["http_responses", ["responses", "responses { cap_list_items 200 shape_item_list }"]],
89
+ ["http_preconditions", ["preconditions", "preconditions { cap_update_item rule_item_exists }"]],
90
+ ["http_idempotency", ["idempotency", "idempotency { cap_create_item key request_id }"]],
91
+ ["http_cache", ["cache", "cache { cap_list_items max_age 60 }"]],
92
+ ["http_delete", ["delete_semantics", "delete_semantics { cap_delete_item mode soft_delete }"]],
93
+ ["http_async", ["async_jobs", "async_jobs { cap_export_items job task_export }"]],
94
+ ["http_status", ["async_status", "async_status { cap_export_items path /exports/{job_id} }"]],
95
+ ["http_download", ["downloads", "downloads { cap_download_export content_type text/csv }"]],
96
+ ["http_authz", ["authorization", "authorization { cap_update_item role editor }"]],
97
+ ["http_callbacks", ["callbacks", "callbacks { cap_export_items event completed }"]],
98
+ ["db_tables", ["tables", "tables { entity_item table items }"]],
99
+ ["db_columns", ["columns", "columns { entity_item field title column title }"]],
100
+ ["db_keys", ["keys", "keys { entity_item primary [id] }"]],
101
+ ["db_indexes", ["indexes", "indexes { entity_item index [title] }"]],
102
+ ["db_relations", ["relations", "relations { entity_item foreign_key owner_id references entity_user.id }"]],
103
+ ["db_lifecycle", ["lifecycle", "lifecycle { entity_item timestamps created_at created_at updated_at updated_at }"]]
104
+ ]);
105
+
106
+ for (const key of fieldMap.keys()) {
107
+ const field = fieldMap.get(key)?.[0];
108
+ const renamedField = renamedFields.get(key);
109
+ if (renamedField) {
110
+ const [newName, example] = renamedField;
111
+ pushError(errors, `Field '${key}' on ${statement.kind} ${statement.id} ${renameDiagnostic(`'${key}'`, `'${newName}'`, example)}`, field?.loc);
112
+ continue;
113
+ }
114
+ if (!spec.allowed.includes(key)) {
115
+ pushError(errors, `Field '${key}' is not allowed on ${statement.kind} ${statement.id}`, field?.loc);
116
+ }
117
+ }
118
+
119
+ for (const key of spec.required) {
120
+ if (!fieldMap.has(key)) {
121
+ pushError(errors, `Missing required field '${key}' on ${statement.kind} ${statement.id}`, statement.loc);
122
+ }
123
+ }
124
+ }
125
+
126
+ /**
127
+ * @param {ValidationErrors} errors
128
+ * @param {TopogramStatement} statement
129
+ * @param {TopogramFieldMap} fieldMap
130
+ * @returns {void}
131
+ */
132
+ function validateProjectionTypeRenames(errors, statement, fieldMap) {
133
+ if (statement.kind !== "projection") {
134
+ return;
135
+ }
136
+
137
+ const typeField = fieldMap.get("type")?.[0];
138
+ const typeValue = symbolValue(typeField?.value);
139
+ const renamedTypes = new Map([
140
+ ["ui_shared", "ui_contract"],
141
+ ["ui_web", "web_surface"],
142
+ ["ui_ios", "ios_surface"],
143
+ ["ui_android", "android_surface"],
144
+ ["dotnet", "api_contract"],
145
+ ["api", "api_contract"],
146
+ ["backend", "api_contract"],
147
+ ["db_postgres", "db_contract"],
148
+ ["db_sqlite", "db_contract"]
149
+ ]);
150
+ if (!typeField || !typeValue || !renamedTypes.has(typeValue)) {
151
+ return;
152
+ }
153
+
154
+ const nextType = renamedTypes.get(typeValue);
155
+ if (!nextType) {
156
+ return;
157
+ }
158
+ pushError(
159
+ errors,
160
+ `Projection ${statement.id} ${renameDiagnostic(`type value '${typeValue}'`, `'${nextType}'`, `type ${nextType}`)}`,
161
+ typeField.value.loc
162
+ );
163
+ }
164
+
165
+ /**
166
+ * @param {ValidationErrors} errors
167
+ * @param {TopogramStatement} statement
168
+ * @param {TopogramFieldMap} fieldMap
169
+ * @param {string} key
170
+ * @param {number} minimumWidth
171
+ * @returns {void}
172
+ */
173
+ function validateBlockEntryLengths(errors, statement, fieldMap, key, minimumWidth) {
174
+ const field = fieldMap.get(key)?.[0];
175
+ if (!field || field.value.type !== "block") {
176
+ return;
177
+ }
178
+
179
+ for (const entry of field.value.entries) {
180
+ if (entry.items.length < minimumWidth) {
181
+ pushError(errors, `Each '${key}' entry on ${statement.kind} ${statement.id} must have at least ${minimumWidth} token(s)`, entry.loc);
182
+ }
183
+ }
184
+ }
185
+
186
+ /**
187
+ * @param {ValidationErrors} errors
188
+ * @param {TopogramStatement} statement
189
+ * @param {TopogramFieldMap} fieldMap
190
+ * @returns {void}
191
+ */
192
+ function validateFieldShapes(errors, statement, fieldMap) {
193
+ ensureSingleValueField(errors, statement, fieldMap, "name", ["string"]);
194
+ ensureSingleValueField(errors, statement, fieldMap, "description", ["string"]);
195
+ ensureSingleValueField(errors, statement, fieldMap, "status", ["symbol"]);
196
+ ensureSingleValueField(errors, statement, fieldMap, "type", ["symbol"]);
197
+ ensureSingleValueField(errors, statement, fieldMap, "method", ["symbol"]);
198
+ ensureSingleValueField(errors, statement, fieldMap, "severity", ["symbol"]);
199
+ ensureSingleValueField(errors, statement, fieldMap, "category", ["symbol"]);
200
+ ensureSingleValueField(errors, statement, fieldMap, "version", ["string"]);
201
+
202
+ for (const key of [
203
+ "aliases",
204
+ "excludes",
205
+ "uses_terms",
206
+ "include",
207
+ "exclude",
208
+ "derived_from",
209
+ "applies_to",
210
+ "actors",
211
+ "roles",
212
+ "reads",
213
+ "creates",
214
+ "updates",
215
+ "deletes",
216
+ "input",
217
+ "output",
218
+ "context",
219
+ "consequences",
220
+ "realizes",
221
+ "outputs",
222
+ "inputs",
223
+ "steps",
224
+ "validates",
225
+ "scenarios",
226
+ "observes",
227
+ "metrics",
228
+ "alerts",
229
+ "source_of_truth",
230
+ "behavior",
231
+ "patterns",
232
+ "regions",
233
+ "lookups",
234
+ "dependencies",
235
+ "approvals"
236
+ ]) {
237
+ ensureSingleValueField(errors, statement, fieldMap, key, ["list"]);
238
+ }
239
+
240
+ for (const key of ["fields", "props", "events", "slots", "behaviors", "keys", "relations", "invariants", "rename", "overrides", "endpoints", "error_responses", "wire_fields", "responses", "preconditions", "idempotency", "cache", "delete_semantics", "async_jobs", "async_status", "downloads", "authorization", "callbacks", "screens", "collection_views", "screen_actions", "visibility_rules", "field_lookups", "screen_routes", "web_hints", "ios_hints", "app_shell", "navigation", "screen_regions", "widget_bindings", "design_tokens", "tables", "columns", "keys", "indexes", "relations", "lifecycle", "generator_defaults"]) {
241
+ ensureSingleValueField(errors, statement, fieldMap, key, ["block"]);
242
+ }
243
+
244
+ validateBlockEntryLengths(errors, statement, fieldMap, "fields", 2);
245
+ validateBlockEntryLengths(errors, statement, fieldMap, "props", 3);
246
+ validateBlockEntryLengths(errors, statement, fieldMap, "events", 2);
247
+ validateBlockEntryLengths(errors, statement, fieldMap, "slots", 2);
248
+ validateBlockEntryLengths(errors, statement, fieldMap, "invariants", 2);
249
+ validateBlockEntryLengths(errors, statement, fieldMap, "generator_defaults", 2);
250
+
251
+ if (statement.kind === "entity") {
252
+ validateBlockEntryLengths(errors, statement, fieldMap, "keys", 2);
253
+ validateBlockEntryLengths(errors, statement, fieldMap, "relations", 3);
254
+ }
255
+
256
+ if (statement.kind === "projection") {
257
+ validateBlockEntryLengths(errors, statement, fieldMap, "endpoints", 7);
258
+ validateBlockEntryLengths(errors, statement, fieldMap, "error_responses", 3);
259
+ validateBlockEntryLengths(errors, statement, fieldMap, "wire_fields", 5);
260
+ validateBlockEntryLengths(errors, statement, fieldMap, "responses", 3);
261
+ validateBlockEntryLengths(errors, statement, fieldMap, "preconditions", 9);
262
+ validateBlockEntryLengths(errors, statement, fieldMap, "idempotency", 7);
263
+ validateBlockEntryLengths(errors, statement, fieldMap, "cache", 11);
264
+ validateBlockEntryLengths(errors, statement, fieldMap, "delete_semantics", 7);
265
+ validateBlockEntryLengths(errors, statement, fieldMap, "async_jobs", 11);
266
+ validateBlockEntryLengths(errors, statement, fieldMap, "async_status", 11);
267
+ validateBlockEntryLengths(errors, statement, fieldMap, "downloads", 7);
268
+ validateBlockEntryLengths(errors, statement, fieldMap, "authorization", 3);
269
+ validateBlockEntryLengths(errors, statement, fieldMap, "callbacks", 11);
270
+ validateBlockEntryLengths(errors, statement, fieldMap, "screens", 4);
271
+ validateBlockEntryLengths(errors, statement, fieldMap, "collection_views", 4);
272
+ validateBlockEntryLengths(errors, statement, fieldMap, "screen_actions", 6);
273
+ validateBlockEntryLengths(errors, statement, fieldMap, "visibility_rules", 5);
274
+ validateBlockEntryLengths(errors, statement, fieldMap, "field_lookups", 8);
275
+ validateBlockEntryLengths(errors, statement, fieldMap, "screen_routes", 4);
276
+ validateBlockEntryLengths(errors, statement, fieldMap, "web_hints", 4);
277
+ validateBlockEntryLengths(errors, statement, fieldMap, "ios_hints", 4);
278
+ validateBlockEntryLengths(errors, statement, fieldMap, "app_shell", 2);
279
+ validateBlockEntryLengths(errors, statement, fieldMap, "navigation", 2);
280
+ validateBlockEntryLengths(errors, statement, fieldMap, "screen_regions", 4);
281
+ validateBlockEntryLengths(errors, statement, fieldMap, "tables", 3);
282
+ validateBlockEntryLengths(errors, statement, fieldMap, "columns", 5);
283
+ validateBlockEntryLengths(errors, statement, fieldMap, "keys", 3);
284
+ validateBlockEntryLengths(errors, statement, fieldMap, "indexes", 3);
285
+ validateBlockEntryLengths(errors, statement, fieldMap, "relations", 6);
286
+ validateBlockEntryLengths(errors, statement, fieldMap, "lifecycle", 3);
287
+ }
288
+ }
289
+
290
+ /**
291
+ * @param {ValidationErrors} errors
292
+ * @param {TopogramStatement} statement
293
+ * @param {TopogramFieldMap} fieldMap
294
+ * @returns {void}
295
+ */
296
+ function validateStatus(errors, statement, fieldMap) {
297
+ const field = fieldMap.get("status")?.[0];
298
+ if (!field || field.value.type !== "symbol") {
299
+ return;
300
+ }
301
+
302
+ // Per-kind status table takes precedence (decision and SDLC kinds), with
303
+ // GLOBAL_STATUSES as the default.
304
+ const allowed = STATUS_SETS_BY_KIND[statement.kind] || GLOBAL_STATUSES;
305
+ if (!allowed.has(field.value.value)) {
306
+ pushError(errors, `Invalid status '${field.value.value}' on ${statement.kind} ${statement.id}`, field.loc);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * @param {ValidationErrors} errors
312
+ * @param {TopogramStatement} statement
313
+ * @param {TopogramFieldMap} fieldMap
314
+ * @returns {void}
315
+ */
316
+ function validateRuleSeverity(errors, statement, fieldMap) {
317
+ if (statement.kind !== "rule") {
318
+ return;
319
+ }
320
+
321
+ const field = fieldMap.get("severity")?.[0];
322
+ if (!field) {
323
+ return;
324
+ }
325
+
326
+ if (field.value.type === "symbol" && !RULE_SEVERITIES.has(field.value.value)) {
327
+ pushError(errors, `Invalid severity '${field.value.value}' on rule ${statement.id}`, field.loc);
328
+ }
329
+ }
330
+
331
+ /**
332
+ * @param {ValidationErrors} errors
333
+ * @param {TopogramStatement} statement
334
+ * @param {TopogramFieldMap} fieldMap
335
+ * @returns {void}
336
+ */
337
+ function validateVerification(errors, statement, fieldMap) {
338
+ if (statement.kind !== "verification") {
339
+ return;
340
+ }
341
+
342
+ const methodField = fieldMap.get("method")?.[0];
343
+ if (methodField?.value.type === "symbol" && !VERIFICATION_METHODS.has(methodField.value.value)) {
344
+ pushError(
345
+ errors,
346
+ `Invalid verification method '${methodField.value.value}' on verification ${statement.id}`,
347
+ methodField.loc
348
+ );
349
+ }
350
+
351
+ const scenariosField = fieldMap.get("scenarios")?.[0];
352
+ if (!scenariosField || scenariosField.value.type !== "list") {
353
+ return;
354
+ }
355
+
356
+ if (scenariosField.value.items.length === 0) {
357
+ pushError(errors, `Verification ${statement.id} must include at least one scenario`, scenariosField.loc);
358
+ return;
359
+ }
360
+
361
+ for (const item of scenariosField.value.items) {
362
+ if (item.type !== "symbol" && item.type !== "string") {
363
+ pushError(errors, `Verification ${statement.id} scenarios must use symbols or strings`, item.loc);
364
+ }
365
+ }
366
+ }
367
+
368
+ /**
369
+ * @param {ValidationErrors} errors
370
+ * @param {TopogramStatement} statement
371
+ * @param {TopogramFieldMap} fieldMap
372
+ * @param {TopogramRegistry} registry
373
+ * @returns {void}
374
+ */
375
+ function validateReferenceKinds(errors, statement, fieldMap, registry) {
376
+ // Phase 2: SDLC kinds add several reference fields. The `affects` field is
377
+ // polymorphic — pitches/requirements/tasks/bugs all use it, so we keep the
378
+ // target set wide. `pitch` is single-id but lives in the same map for
379
+ // uniform validation.
380
+ const expectedByField = {
381
+ uses_terms: ["term"],
382
+ derived_from: ["entity"],
383
+ applies_to: ["capability"],
384
+ source_of_truth: ["decision"],
385
+ actors: ["actor"],
386
+ roles: ["role"],
387
+ reads: ["entity"],
388
+ creates: ["entity"],
389
+ updates: ["entity"],
390
+ deletes: ["entity"],
391
+ input: ["shape"],
392
+ output: ["shape"],
393
+ dependencies: [...STATEMENT_KINDS],
394
+ realizes: ["capability", "projection", "entity"],
395
+ validates: [...STATEMENT_KINDS],
396
+ observes: [...STATEMENT_KINDS],
397
+ inputs: [...STATEMENT_KINDS],
398
+ outputs: null,
399
+ steps: null,
400
+ scenarios: null,
401
+ metrics: null,
402
+ alerts: null,
403
+ aliases: null,
404
+ excludes: null,
405
+ include: null,
406
+ exclude: null,
407
+ context: null,
408
+ consequences: null,
409
+ pitch: ["pitch"],
410
+ requirement: null,
411
+ from_requirement: ["requirement"],
412
+ affects: ["capability", "entity", "rule", "projection", "widget", "orchestration", "operation"],
413
+ introduces_rules: ["rule"],
414
+ respects_rules: ["rule"],
415
+ decisions: ["decision"],
416
+ introduces_decisions: ["decision"],
417
+ satisfies: ["requirement", "acceptance_criterion"],
418
+ acceptance_refs: ["acceptance_criterion"],
419
+ requirement_refs: ["requirement"],
420
+ fixes_bugs: ["bug"],
421
+ blocks: ["task"],
422
+ blocked_by: ["task"],
423
+ claimed_by: ["actor", "role"],
424
+ violates: ["rule"],
425
+ surfaces_rule: ["rule"],
426
+ introduced_in: ["task", "bug"],
427
+ fixed_in: ["task"],
428
+ fixed_in_verification: ["verification"],
429
+ supersedes: null,
430
+ modifies: [...STATEMENT_KINDS],
431
+ introduces: [...STATEMENT_KINDS],
432
+ removes: [...STATEMENT_KINDS]
433
+ };
434
+
435
+ for (const [key, allowedKinds] of Object.entries(expectedByField)) {
436
+ const field = fieldMap.get(key)?.[0];
437
+ if (!field || !allowedKinds) {
438
+ continue;
439
+ }
440
+
441
+ for (const item of valueAsArray(field.value)) {
442
+ if (item.type !== "symbol") {
443
+ pushError(errors, `Field '${key}' on ${statement.kind} ${statement.id} must only contain symbols`, item.loc);
444
+ continue;
445
+ }
446
+
447
+ const target = registry.get(item.value);
448
+ if (!target) {
449
+ pushError(errors, `Missing reference '${item.value}' in field '${key}' on ${statement.kind} ${statement.id}`, item.loc);
450
+ continue;
451
+ }
452
+
453
+ if (!allowedKinds.includes(target.kind)) {
454
+ pushError(
455
+ errors,
456
+ `Field '${key}' on ${statement.kind} ${statement.id} must reference ${allowedKinds.join(" or ")}, found ${target.kind} '${target.id}'`,
457
+ item.loc
458
+ );
459
+ }
460
+ }
461
+ }
462
+ }
463
+
464
+ /**
465
+ * @param {ValidationErrors} errors
466
+ * @param {TopogramStatement} statement
467
+ * @param {TopogramFieldMap} fieldMap
468
+ * @returns {void}
469
+ */
470
+ export function validateCoreStatement(errors, statement, fieldMap) {
471
+ validateFieldPresence(errors, statement, fieldMap);
472
+ validateProjectionTypeRenames(errors, statement, fieldMap);
473
+ validateFieldShapes(errors, statement, fieldMap);
474
+ validateStatus(errors, statement, fieldMap);
475
+ validateRuleSeverity(errors, statement, fieldMap);
476
+ validateVerification(errors, statement, fieldMap);
477
+ }
478
+
479
+ /**
480
+ * @param {ValidationErrors} errors
481
+ * @param {TopogramStatement} statement
482
+ * @param {TopogramFieldMap} fieldMap
483
+ * @param {TopogramRegistry} registry
484
+ * @returns {void}
485
+ */
486
+ export function validateReferenceRules(errors, statement, fieldMap, registry) {
487
+ validateReferenceKinds(errors, statement, fieldMap, registry);
488
+ }