@sdd-method/maintain-cli 0.0.0-stage → 0.1.0

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 (206) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +17 -0
  3. package/README.md +6 -2
  4. package/dist/lib/adr-origin.js +59 -0
  5. package/dist/lib/agent-harness.js +28 -0
  6. package/dist/lib/briefing/index.js +13 -0
  7. package/dist/lib/briefing/render.js +123 -0
  8. package/dist/lib/briefing/variables.js +92 -0
  9. package/dist/lib/bundle.js +123 -0
  10. package/dist/lib/catalogue/aggregate.js +2404 -0
  11. package/dist/lib/catalogue/aggregation-manifest-loader.js +173 -0
  12. package/dist/lib/catalogue/application-archetype/expectations.js +119 -0
  13. package/dist/lib/catalogue/application-archetype/vocabulary.js +177 -0
  14. package/dist/lib/catalogue/archetype/maps/android.yaml +66 -0
  15. package/dist/lib/catalogue/archetype/maps/swiftui.yaml +58 -0
  16. package/dist/lib/catalogue/archetype/maps/uikit.yaml +48 -0
  17. package/dist/lib/catalogue/archetype/maps/web-generic.yaml +86 -0
  18. package/dist/lib/catalogue/archetype/openui-archetype-vocabulary.yaml +46 -0
  19. package/dist/lib/catalogue/archetype/resolver.js +103 -0
  20. package/dist/lib/catalogue/archetype/wireframe.js +418 -0
  21. package/dist/lib/catalogue/baseline-check.js +151 -0
  22. package/dist/lib/catalogue/build.js +1502 -0
  23. package/dist/lib/catalogue/builders/adr-supersedes.js +103 -0
  24. package/dist/lib/catalogue/builders/adrs.js +222 -0
  25. package/dist/lib/catalogue/builders/application-conformance-coverage.js +47 -0
  26. package/dist/lib/catalogue/builders/application-data-plane-bindings.js +115 -0
  27. package/dist/lib/catalogue/builders/c4-components.js +229 -0
  28. package/dist/lib/catalogue/builders/c4-containers.js +240 -0
  29. package/dist/lib/catalogue/builders/c4-entity-rollups.js +87 -0
  30. package/dist/lib/catalogue/builders/c4-external-systems.js +194 -0
  31. package/dist/lib/catalogue/builders/c4-l1-candidates.js +107 -0
  32. package/dist/lib/catalogue/builders/c4-systems.js +98 -0
  33. package/dist/lib/catalogue/builders/capabilities.js +488 -0
  34. package/dist/lib/catalogue/builders/capability-domain-model.js +74 -0
  35. package/dist/lib/catalogue/builders/capability-edges.js +256 -0
  36. package/dist/lib/catalogue/builders/capability-spec-walker.js +99 -0
  37. package/dist/lib/catalogue/builders/capability-spec.js +348 -0
  38. package/dist/lib/catalogue/builders/container-dependency.js +183 -0
  39. package/dist/lib/catalogue/builders/contracts.js +402 -0
  40. package/dist/lib/catalogue/builders/data-items.js +344 -0
  41. package/dist/lib/catalogue/builders/data-store-mapping.js +113 -0
  42. package/dist/lib/catalogue/builders/domain-models.js +175 -0
  43. package/dist/lib/catalogue/builders/domains.js +119 -0
  44. package/dist/lib/catalogue/builders/edge-label-merge.js +19 -0
  45. package/dist/lib/catalogue/builders/entities-manifest.js +160 -0
  46. package/dist/lib/catalogue/builders/entities-mermaid.js +189 -0
  47. package/dist/lib/catalogue/builders/entity-persistence.js +102 -0
  48. package/dist/lib/catalogue/builders/entity-slug.js +47 -0
  49. package/dist/lib/catalogue/builders/event-catalog-walker.js +186 -0
  50. package/dist/lib/catalogue/builders/event-edges.js +327 -0
  51. package/dist/lib/catalogue/builders/events.js +168 -0
  52. package/dist/lib/catalogue/builders/evidence-class.js +43 -0
  53. package/dist/lib/catalogue/builders/evidence-target-repo.js +67 -0
  54. package/dist/lib/catalogue/builders/external-system-exemption.js +120 -0
  55. package/dist/lib/catalogue/builders/feature-contract.js +77 -0
  56. package/dist/lib/catalogue/builders/feature-domain-model.js +50 -0
  57. package/dist/lib/catalogue/builders/feature-grounding-walker.js +254 -0
  58. package/dist/lib/catalogue/builders/feature-grounding.js +391 -0
  59. package/dist/lib/catalogue/builders/feature-groups.js +63 -0
  60. package/dist/lib/catalogue/builders/feature-subscription-channels.js +228 -0
  61. package/dist/lib/catalogue/builders/feature-tree-walker.js +295 -0
  62. package/dist/lib/catalogue/builders/features.js +300 -0
  63. package/dist/lib/catalogue/builders/glossary-xref.js +378 -0
  64. package/dist/lib/catalogue/builders/glossary.js +204 -0
  65. package/dist/lib/catalogue/builders/implementations.js +214 -0
  66. package/dist/lib/catalogue/builders/index.js +64 -0
  67. package/dist/lib/catalogue/builders/integration-components.js +344 -0
  68. package/dist/lib/catalogue/builders/integrations.js +181 -0
  69. package/dist/lib/catalogue/builders/persona-c4system.js +149 -0
  70. package/dist/lib/catalogue/builders/persona-container.js +133 -0
  71. package/dist/lib/catalogue/builders/persona-journey-edges.js +133 -0
  72. package/dist/lib/catalogue/builders/personas.js +152 -0
  73. package/dist/lib/catalogue/builders/product-edges.js +106 -0
  74. package/dist/lib/catalogue/builders/product-platform-dependencies.js +405 -0
  75. package/dist/lib/catalogue/builders/products.js +162 -0
  76. package/dist/lib/catalogue/builders/quality-scores.js +635 -0
  77. package/dist/lib/catalogue/builders/repo-capability-exemption.js +61 -0
  78. package/dist/lib/catalogue/builders/repo-dependency.js +127 -0
  79. package/dist/lib/catalogue/builders/repo-edges.js +340 -0
  80. package/dist/lib/catalogue/builders/repositories.js +458 -0
  81. package/dist/lib/catalogue/builders/scaffolds.js +197 -0
  82. package/dist/lib/catalogue/builders/screen-tree.js +222 -0
  83. package/dist/lib/catalogue/builders/screens.js +396 -0
  84. package/dist/lib/catalogue/builders/service-capability-exemption.js +63 -0
  85. package/dist/lib/catalogue/builders/service-contract-consumes.js +186 -0
  86. package/dist/lib/catalogue/builders/service-contract.js +74 -0
  87. package/dist/lib/catalogue/builders/service-entity-tables.js +187 -0
  88. package/dist/lib/catalogue/builders/services.js +232 -0
  89. package/dist/lib/catalogue/builders/system-external-dependency.js +115 -0
  90. package/dist/lib/catalogue/builders/user-journeys.js +253 -0
  91. package/dist/lib/catalogue/canonical-schema.yaml +4117 -0
  92. package/dist/lib/catalogue/cli-version.js +10 -0
  93. package/dist/lib/catalogue/contract-id.js +49 -0
  94. package/dist/lib/catalogue/contract-path-resolution.js +149 -0
  95. package/dist/lib/catalogue/coverage-predicates.js +603 -0
  96. package/dist/lib/catalogue/coverage-render.js +552 -0
  97. package/dist/lib/catalogue/coverage-run.js +115 -0
  98. package/dist/lib/catalogue/csv-reader.js +97 -0
  99. package/dist/lib/catalogue/csv-writer.js +96 -0
  100. package/dist/lib/catalogue/data-item-coverage.js +142 -0
  101. package/dist/lib/catalogue/data-plane-coherence.js +77 -0
  102. package/dist/lib/catalogue/ddl-generator.js +233 -0
  103. package/dist/lib/catalogue/dedupe-keyed-tables.js +0 -0
  104. package/dist/lib/catalogue/engine-validator.js +239 -0
  105. package/dist/lib/catalogue/ext-id.js +76 -0
  106. package/dist/lib/catalogue/feature-grounding-aliases-loader.js +65 -0
  107. package/dist/lib/catalogue/feature-repo-zero-resolution-summary.js +31 -0
  108. package/dist/lib/catalogue/finding-identity.js +183 -0
  109. package/dist/lib/catalogue/findings.js +46 -0
  110. package/dist/lib/catalogue/fk-validator.js +136 -0
  111. package/dist/lib/catalogue/freshness.js +69 -0
  112. package/dist/lib/catalogue/gate-checks.js +338 -0
  113. package/dist/lib/catalogue/graph-projection.js +346 -0
  114. package/dist/lib/catalogue/graph-run.js +64 -0
  115. package/dist/lib/catalogue/impact-run.js +228 -0
  116. package/dist/lib/catalogue/manifest-loader.js +435 -0
  117. package/dist/lib/catalogue/platform-scope.js +7 -0
  118. package/dist/lib/catalogue/population-checks.js +87 -0
  119. package/dist/lib/catalogue/relationship-semantics.js +66 -0
  120. package/dist/lib/catalogue/reverse-product-quality.js +99 -0
  121. package/dist/lib/catalogue/rubric-coverage-map.yaml +871 -0
  122. package/dist/lib/catalogue/runway-vocabulary-loader.js +56 -0
  123. package/dist/lib/catalogue/scaffold-delta.js +261 -0
  124. package/dist/lib/catalogue/schema-compat.js +52 -0
  125. package/dist/lib/catalogue/schema-loader.js +87 -0
  126. package/dist/lib/catalogue/scores-coverage.js +573 -0
  127. package/dist/lib/catalogue/screen-validation-loader.js +89 -0
  128. package/dist/lib/catalogue/service-architecture-parser.js +193 -0
  129. package/dist/lib/catalogue/service-contract-aliases-loader.js +109 -0
  130. package/dist/lib/catalogue/staleness-verdicts.js +345 -0
  131. package/dist/lib/catalogue/traceability-loader.js +350 -0
  132. package/dist/lib/catalogue/validate.js +200 -0
  133. package/dist/lib/catalogue/version.js +18 -0
  134. package/dist/lib/ci-binding/index.js +220 -0
  135. package/dist/lib/ci-binding/module.js +204 -0
  136. package/dist/lib/conformance/binding.js +70 -0
  137. package/dist/lib/context-pull/index.js +151 -0
  138. package/dist/lib/context-pull/resolve.js +70 -0
  139. package/dist/lib/generate-briefing/index.js +77 -0
  140. package/dist/lib/hooks-seed/copilot.js +167 -0
  141. package/dist/lib/hooks-seed/entries.js +47 -0
  142. package/dist/lib/hooks-seed/index.js +17 -0
  143. package/dist/lib/hooks-seed/lifecycle-mapping.js +28 -0
  144. package/dist/lib/hooks-seed/merge.js +128 -0
  145. package/dist/lib/hooks-seed/types.js +20 -0
  146. package/dist/lib/init/ci-workflow.js +98 -0
  147. package/dist/lib/list/discover.js +100 -0
  148. package/dist/lib/list/format.js +84 -0
  149. package/dist/lib/list/index.js +16 -0
  150. package/dist/lib/lock-file.js +437 -0
  151. package/dist/lib/mcp-seed/entries.js +14 -0
  152. package/dist/lib/mcp-seed/index.js +28 -0
  153. package/dist/lib/mcp-seed/merge.js +99 -0
  154. package/dist/lib/mcp-seed/types.js +13 -0
  155. package/dist/lib/pack/builder.js +303 -0
  156. package/dist/lib/pack/index.js +87 -0
  157. package/dist/lib/pack/leakage.js +144 -0
  158. package/dist/lib/pack/log.js +18 -0
  159. package/dist/lib/pack/publish-transform.js +233 -0
  160. package/dist/lib/pack/reference-closure.js +125 -0
  161. package/dist/lib/pack/schema.js +121 -0
  162. package/dist/lib/pack/seed.js +182 -0
  163. package/dist/lib/pack/validate.js +49 -0
  164. package/dist/lib/package-version.js +92 -0
  165. package/dist/lib/repo-kind.js +129 -0
  166. package/dist/lib/scripts.js +7 -0
  167. package/dist/lib/snapshot/catalogue-diff.js +219 -0
  168. package/dist/lib/snapshot/manifest.js +206 -0
  169. package/dist/lib/sync/adr-transitions.js +250 -0
  170. package/dist/lib/sync/apply.js +91 -0
  171. package/dist/lib/sync/briefing-merge.js +90 -0
  172. package/dist/lib/sync/bundle-cache.js +109 -0
  173. package/dist/lib/sync/bundle-source.js +61 -0
  174. package/dist/lib/sync/config.js +72 -0
  175. package/dist/lib/sync/copilot-hooks-merge.js +108 -0
  176. package/dist/lib/sync/diff.js +251 -0
  177. package/dist/lib/sync/index.js +818 -0
  178. package/dist/lib/sync/settings-merge.js +216 -0
  179. package/dist/lib/sync/yaml-block-merge.js +120 -0
  180. package/dist/lib/validate/aggregate.js +51 -0
  181. package/dist/lib/validate/bundle-coherence.js +74 -0
  182. package/dist/lib/validate/dispatch.js +243 -0
  183. package/dist/lib/validate/index.js +110 -0
  184. package/dist/lib/validate/plan.js +54 -0
  185. package/dist/lib/validate/runner.js +85 -0
  186. package/dist/lib/validate/yaml-parse.js +112 -0
  187. package/dist/maintain-bin.js +8 -0
  188. package/dist/maintain-program.js +91 -0
  189. package/dist/maintain-version.json +4 -0
  190. package/dist/verbs/catalogue-maintain.js +110 -0
  191. package/dist/verbs/catalogue-verify.js +114 -0
  192. package/dist/verbs/context.js +39 -0
  193. package/dist/verbs/generate-briefing.js +29 -0
  194. package/dist/verbs/list.js +45 -0
  195. package/dist/verbs/pack-build.js +31 -0
  196. package/dist/verbs/pack-validate.js +17 -0
  197. package/dist/verbs/path-option.js +25 -0
  198. package/dist/verbs/render.js +87 -0
  199. package/dist/verbs/sync.js +110 -0
  200. package/dist/verbs/validate.js +48 -0
  201. package/dist/verify-program.js +122 -0
  202. package/dist/verify-version.json +4 -0
  203. package/package.json +32 -4
  204. package/scripts/validate-app-dependencies.sh +664 -0
  205. package/scripts/validate-app-docs.sh +199 -0
  206. package/scripts/validate-integration-docs.sh +505 -0
@@ -0,0 +1,86 @@
1
+ # Default archetype map — web-generic (ADR 0169 §4.2). Method-owned.
2
+ #
3
+ # Keys off component NAMES (which the builder already parses), not design-system source.
4
+ # Vendor-standard / common-DS tokens ONLY — never an adopter's bespoke vocabulary.
5
+ # Resolution order (ADR 0169 §4.3): adopter overlay override -> by_token -> by_suffix_heuristic -> default.
6
+ # Selected per screen by `platform: web-generic` (or web) frontmatter.
7
+
8
+ map_version: "1.0.0"
9
+ platform: web-generic
10
+
11
+ # Stripped (case-insensitive) before token lookup, so `MuiButton`/`ChButton` -> `Button`.
12
+ strip_prefixes: [Mui, Ch, Ds, Ui, App, Base, Core, V]
13
+
14
+ by_token:
15
+ Container: grid
16
+ Grid: grid
17
+ Row: row
18
+ Col: col
19
+ Column: col
20
+ Stack: stack
21
+ Flex: row
22
+ Scroll: scroll
23
+ ScrollArea: scroll
24
+ Tabs: tab-strip
25
+ Tab: tab
26
+ TabPanel: pane
27
+ Table: table
28
+ DataGrid: table
29
+ List: list
30
+ ListItem: list-item
31
+ Menu: list
32
+ Card: card
33
+ Panel: panel
34
+ Dialog: modal
35
+ Modal: modal
36
+ Popup: modal
37
+ Popover: modal
38
+ Drawer: sidebar
39
+ SidePanel: sidebar
40
+ Sidebar: sidebar
41
+ Nav: nav-bar
42
+ Navbar: nav-bar
43
+ AppBar: app-bar
44
+ Header: app-bar
45
+ Toolbar: app-bar
46
+ Alert: banner
47
+ Banner: banner
48
+ Toast: banner
49
+ Snackbar: banner
50
+ Sheet: sheet
51
+ BottomSheet: sheet
52
+ Btn: button
53
+ Button: button
54
+ IconButton: button
55
+ Fab: fab
56
+ ButtonGroup: button-group
57
+ SegmentedControl: segmented-control
58
+ Stat: text
59
+ StatCard: card
60
+ KpiBar: kpi-bar
61
+ Text: text
62
+ Typography: text
63
+ Label: text
64
+ Heading: text
65
+ Image: image
66
+ Img: image
67
+ Avatar: image
68
+ Icon: image
69
+
70
+ # Applied when no token matches. First matching suffix wins.
71
+ by_suffix_heuristic:
72
+ Button: button
73
+ Bar: app-bar
74
+ List: list
75
+ Grid: grid
76
+ Card: card
77
+ Panel: panel
78
+ Dialog: modal
79
+ Modal: modal
80
+ Layout: stack
81
+ Container: grid
82
+ Text: text
83
+ Image: image
84
+ Icon: image
85
+
86
+ default: panel
@@ -0,0 +1,46 @@
1
+ # OpenUI Archetype Vocabulary — method-owned (ADR 0169 §4.1)
2
+ #
3
+ # A closed, platform-tagged superset of visual archetypes. An archetype assigns a
4
+ # component a VISUAL SHAPE + slots/orientation — never geometry or styling (stays inside
5
+ # the compose-only boundary, ADR 0166 §3). Default archetype maps
6
+ # (archetype-maps/*.yaml) resolve adopter component NAMES to entries here; the
7
+ # render-screen-wireframe skill lowers a resolved tree to SVG using `render`.
8
+ #
9
+ # `is_container` feeds ADR 0168 §4.1 isContainer when a resolved map is present.
10
+
11
+ vocabulary_version: "1.0.0"
12
+
13
+ archetypes:
14
+
15
+ # --- structural containers -------------------------------------------------
16
+ scaffold: { is_container: true, render: { slots: [header, main, footer], orientation: vertical } }
17
+ app-bar: { is_container: true, render: { shape: bar, position: top } }
18
+ nav-bar: { is_container: true, render: { shape: bar, position: top } }
19
+ tab-bar: { is_container: true, render: { shape: bar, position: bottom } }
20
+ sidebar: { is_container: true, render: { shape: rail, position: left } }
21
+ stack: { is_container: true, render: { orientation: vertical } }
22
+ row: { is_container: true, render: { orientation: horizontal } }
23
+ col: { is_container: true, render: { orientation: vertical } }
24
+ grid: { is_container: true, render: { shape: grid } }
25
+ scroll: { is_container: true, render: { orientation: vertical, scroll: true } }
26
+ list: { is_container: true, render: { orientation: vertical, repeat: true } }
27
+ table: { is_container: true, render: { shape: grid, header_row: true } }
28
+ card: { is_container: true, render: { shape: card } }
29
+ panel: { is_container: true, render: { shape: panel } } # opaque fallback
30
+ paged-panes: { is_container: true, render: { shape: paged, active_first: true } }
31
+ pane: { is_container: true, render: { shape: panel } }
32
+ tab-strip: { is_container: true, render: { orientation: horizontal, shape: chips } }
33
+ button-group: { is_container: true, render: { orientation: horizontal } }
34
+ segmented-control: { is_container: true, render: { orientation: horizontal, shape: chips } }
35
+ kpi-bar: { is_container: true, render: { orientation: horizontal, shape: chips } }
36
+ modal: { is_container: true, render: { shape: floating, overlay: true } }
37
+ sheet: { is_container: true, render: { shape: floating, position: bottom } }
38
+ banner: { is_container: true, render: { shape: bar } }
39
+
40
+ # --- leaves ----------------------------------------------------------------
41
+ tab: { is_container: false, render: { shape: chip } }
42
+ list-item: { is_container: false, render: { shape: row } }
43
+ button: { is_container: false, render: { shape: button } }
44
+ fab: { is_container: false, render: { shape: circle, position: bottom-right } }
45
+ text: { is_container: false, render: { shape: text } }
46
+ image: { is_container: false, render: { shape: image } }
@@ -0,0 +1,103 @@
1
+ // OpenUI archetype resolver (per ADR 0169).
2
+ //
3
+ // Binds an adopter component NAME to a visual ARCHETYPE via a method-shipped
4
+ // platform default map plus an optional adopter overlay. The binding is what
5
+ // makes a shape-blind openui-lang block renderable as a wireframe, and it
6
+ // supplies the container/leaf classification the ADR 0168 `screen-root-is-leaf`
7
+ // check consumes (isContainerArchetype).
8
+ //
9
+ // Resolution order (ADR 0169 §4.3): adopter override -> default by_token (after
10
+ // stripping a vendor prefix) -> by_suffix_heuristic -> default (panel).
11
+ //
12
+ // The vocabulary + the four default maps are method-owned reference data,
13
+ // VENDORED from sdd-method (docs/method/methods/guides/openui-archetype-*.yaml /
14
+ // archetype-maps/) — the same vendoring relationship as canonical-schema.yaml.
15
+ // They use vendor-standard names only; never an adopter's bespoke vocabulary.
16
+ // copy-yaml-assets.mjs copies them next to the compiled JS at build time.
17
+ import { readFileSync } from "node:fs";
18
+ import { fileURLToPath } from "node:url";
19
+ import { dirname, join } from "node:path";
20
+ import { parse as parseYaml } from "yaml";
21
+ const __dirname = dirname(fileURLToPath(import.meta.url));
22
+ const PLATFORMS = new Set(["web-generic", "android", "swiftui", "uikit"]);
23
+ const FALLBACK_ARCHETYPE = "panel";
24
+ // Loaded once, cached. Reference data is immutable at runtime.
25
+ let vocabularyCache;
26
+ const mapCache = new Map();
27
+ function loadVocabulary() {
28
+ if (vocabularyCache === undefined) {
29
+ const raw = readFileSync(join(__dirname, "openui-archetype-vocabulary.yaml"), "utf-8");
30
+ const parsed = parseYaml(raw);
31
+ vocabularyCache = { archetypes: parsed?.archetypes ?? {} };
32
+ }
33
+ return vocabularyCache;
34
+ }
35
+ function loadMap(platform) {
36
+ let m = mapCache.get(platform);
37
+ if (m === undefined) {
38
+ const raw = readFileSync(join(__dirname, "maps", `${platform}.yaml`), "utf-8");
39
+ m = parseYaml(raw) ?? {};
40
+ mapCache.set(platform, m);
41
+ }
42
+ return m;
43
+ }
44
+ /** True when `platform` has a method-shipped default map. */
45
+ export function isKnownPlatform(platform) {
46
+ return platform !== undefined && PLATFORMS.has(platform);
47
+ }
48
+ function stripPrefix(name, prefixes) {
49
+ for (const p of prefixes ?? []) {
50
+ if (name.length > p.length && name.startsWith(p))
51
+ return name.slice(p.length);
52
+ }
53
+ return name;
54
+ }
55
+ /**
56
+ * Resolve a component name to a visual archetype for a platform, honouring an
57
+ * optional adopter overlay. Always resolves (panel fallback) — never throws.
58
+ */
59
+ export function resolveArchetype(name, platform, overlay) {
60
+ const override = overlay?.overrides?.[name];
61
+ if (override !== undefined)
62
+ return { archetype: override, provenance: "override" };
63
+ const map = loadMap(platform);
64
+ const token = stripPrefix(name, map.strip_prefixes);
65
+ const byToken = map.by_token?.[token] ?? map.by_token?.[name];
66
+ if (byToken !== undefined)
67
+ return { archetype: byToken, provenance: "default" };
68
+ // First suffix the name ends with wins.
69
+ for (const [suffix, archetype] of Object.entries(map.by_suffix_heuristic ?? {})) {
70
+ if (name.endsWith(suffix))
71
+ return { archetype, provenance: "heuristic" };
72
+ }
73
+ return { archetype: map.default ?? FALLBACK_ARCHETYPE, provenance: "fallback" };
74
+ }
75
+ /**
76
+ * Whether a component classifies as a container archetype for a platform.
77
+ * Used by ADR 0168 §4.1 isContainer to refine the appears-with-children
78
+ * heuristic. Returns undefined when the platform has no map (caller falls back
79
+ * to the heuristic) so an absent map never mis-classifies a known container.
80
+ */
81
+ export function isContainerArchetype(name, platform, overlay) {
82
+ if (!isKnownPlatform(platform))
83
+ return undefined;
84
+ const { archetype } = resolveArchetype(name, platform, overlay);
85
+ return loadVocabulary().archetypes[archetype]?.is_container === true;
86
+ }
87
+ /**
88
+ * The vendor name prefixes the platform's default map strips (e.g. `UI`/`NS` for
89
+ * uikit, `Mui`/`Ch`/… for web-generic). The renderer combines these with the
90
+ * adopter overlay's `strip_prefixes` to humanise component names into labels.
91
+ * Returns `[]` for an unknown platform.
92
+ */
93
+ export function mapStripPrefixes(platform) {
94
+ if (!isKnownPlatform(platform))
95
+ return [];
96
+ return loadMap(platform).strip_prefixes ?? [];
97
+ }
98
+ /** Vocabulary metadata for an archetype: container flag + render hints. */
99
+ export function archetypeInfo(archetype) {
100
+ const entry = loadVocabulary().archetypes[archetype];
101
+ return { isContainer: entry?.is_container === true, render: entry?.render ?? {} };
102
+ }
103
+ //# sourceMappingURL=resolver.js.map
@@ -0,0 +1,418 @@
1
+ // Deterministic structural-wireframe renderer (ADR 0169 §4.4).
2
+ //
3
+ // Lowers a screen's openui-lang block to an SVG wireframe: parse the block into
4
+ // a composition tree, resolve each node to a visual archetype, and lay it out
5
+ // driven by the archetype's render hints — orientation, shape (bar / chips /
6
+ // grid / card / floating / paged / leaf shapes), slots, overlay, active_first,
7
+ // header_row. Each node is drawn as its archetype's visual SHAPE (an app-bar is
8
+ // a band, a tab-strip is a row of chips, a paged-panes shows the active pane and
9
+ // collapses the rest, a table has a header row, a modal floats), labelled with
10
+ // the component name + resolved archetype and shaded by resolution provenance so
11
+ // low-confidence inferences stay visible. A structural wireframe, not a pixel
12
+ // design; parameterised entirely by the archetype map (adopter-agnostic — no
13
+ // component vocabulary, no adopter knowledge).
14
+ //
15
+ // Deterministic companion to the LLM-driven render-screen-wireframe skill.
16
+ import { buildCompositionTree, stripBlockComments } from "../builders/screen-tree.js";
17
+ import { archetypeInfo, isKnownPlatform, mapStripPrefixes, resolveArchetype, } from "./resolver.js";
18
+ // --- geometry constants ----------------------------------------------------
19
+ const PAD = 8;
20
+ const GAP = 6;
21
+ const LABEL_H = 16; // container label strip
22
+ const TITLE_H = 26;
23
+ const TABLE_HDR_H = 20;
24
+ const COLLAPSE_H = 22; // collapsed (inactive) paged pane row
25
+ const SHADOW = 4; // floating/card drop-shadow offset
26
+ const GRID_COLS = 2;
27
+ /** Render an openui-lang block to a structural wireframe SVG string. */
28
+ export function renderWireframe(input) {
29
+ const tree = buildCompositionTree(stripBlockComments(input.block));
30
+ if (tree === null) {
31
+ return emptySvg(input.title, "empty composition — no statements");
32
+ }
33
+ const platform = isKnownPlatform(input.platform) ? input.platform : undefined;
34
+ const prefixes = [...(input.overlay?.strip_prefixes ?? []), ...mapStripPrefixes(platform)];
35
+ const root = measure(shape(tree, platform, input.overlay, prefixes));
36
+ const titleOffset = input.title ? TITLE_H : 0;
37
+ const width = root.w + PAD * 2;
38
+ const height = root.h + PAD * 2 + titleOffset;
39
+ const body = [];
40
+ if (input.title)
41
+ body.push(text(PAD, 17, escapeXml(input.title), "title"));
42
+ emit(root, PAD, PAD + titleOffset, body);
43
+ return svg(width, height, body.join("\n"));
44
+ }
45
+ // --- shape: resolve archetype + classify a render mode per node ------------
46
+ const LEAF_SHAPES = new Set(["button", "chip", "text", "image", "circle", "row"]);
47
+ function shape(node, platform, overlay, prefixes) {
48
+ const children = node.children.map((c) => shape(c, platform, overlay, prefixes));
49
+ const isContainer = children.length > 0;
50
+ linkPagedTabs(children, prefixes);
51
+ if (node.component === "" || platform === undefined) {
52
+ return base("", "none", isContainer ? "stack" : "leaf", "box");
53
+ }
54
+ const { archetype, provenance } = resolveArchetype(node.component, platform, overlay);
55
+ const r = archetypeInfo(archetype).render;
56
+ const sh = r.shape ?? "";
57
+ if (!isContainer) {
58
+ const leafShape = LEAF_SHAPES.has(sh) ? sh : "box";
59
+ return base(archetype, provenance, "leaf", leafShape);
60
+ }
61
+ let mode;
62
+ if (sh === "bar")
63
+ mode = "bar";
64
+ else if (sh === "chips")
65
+ mode = "chips";
66
+ else if (sh === "grid")
67
+ mode = r.header_row ? "table" : "grid";
68
+ else if (sh === "paged")
69
+ mode = "paged";
70
+ else if (sh === "card")
71
+ mode = "card";
72
+ else if (sh === "floating")
73
+ mode = "floating";
74
+ else
75
+ mode = r.orientation === "horizontal" ? "row" : "stack";
76
+ return base(archetype, provenance, mode, "box");
77
+ function base(archetype, provenance, mode, leafShape) {
78
+ return { node, archetype, provenance, mode, leafShape, children, stripPrefixes: prefixes, derivedLabel: "", w: 0, h: 0 };
79
+ }
80
+ }
81
+ /**
82
+ * Derive tab labels from windowed content (deterministic, spec-faithful): when a
83
+ * sibling group holds a tab-strip and a paged-panes with the SAME pane count, and
84
+ * the tabs carry no authored label, label each tab from its corresponding pane's
85
+ * content component name. Reads only names already in the spec — no fabrication.
86
+ */
87
+ function linkPagedTabs(siblings, prefixes) {
88
+ const strip = siblings.find((s) => s.archetype === "tab-strip");
89
+ const paged = siblings.find((s) => s.archetype === "paged-panes");
90
+ if (!strip || !paged)
91
+ return;
92
+ if (strip.children.length === 0 || strip.children.length !== paged.children.length)
93
+ return;
94
+ strip.children.forEach((tab, i) => {
95
+ if (tab.node.args.length > 0 || tab.derivedLabel)
96
+ return; // authored label wins
97
+ const pane = paged.children[i];
98
+ const contentName = pane.children[0]?.node.component || pane.node.component;
99
+ if (contentName)
100
+ tab.derivedLabel = humaniseName(contentName, prefixes);
101
+ });
102
+ }
103
+ // --- measure: size every node bottom-up ------------------------------------
104
+ function leafSize(shapeName, lbl) {
105
+ switch (shapeName) {
106
+ case "button": return { w: Math.max(84, lbl.length * 6 + 16), h: 26 };
107
+ case "chip": return { w: Math.max(56, lbl.length * 6 + 14), h: 22 };
108
+ case "text": return { w: Math.max(120, lbl.length * 6 + 12), h: 28 };
109
+ case "image": return { w: 72, h: 54 };
110
+ case "circle": return { w: 44, h: 44 };
111
+ case "row": return { w: Math.max(200, lbl.length * 6 + 24), h: 26 };
112
+ default: return { w: Math.max(120, lbl.length * 6.2 + 12), h: 40 };
113
+ }
114
+ }
115
+ function measure(s) {
116
+ if (s.mode === "leaf") {
117
+ const { w, h } = leafSize(s.leafShape, labelPlain(s));
118
+ s.w = w;
119
+ s.h = h;
120
+ return s;
121
+ }
122
+ s.children.forEach(measure);
123
+ const cs = s.children;
124
+ let innerW;
125
+ let innerH;
126
+ switch (s.mode) {
127
+ case "row":
128
+ case "bar":
129
+ case "chips": {
130
+ innerW = cs.reduce((a, c) => a + c.w, 0) + GAP * (cs.length - 1);
131
+ innerH = Math.max(...cs.map((c) => c.h));
132
+ break;
133
+ }
134
+ case "grid": {
135
+ const colW = Math.max(...cs.map((c) => c.w));
136
+ const rowH = Math.max(...cs.map((c) => c.h));
137
+ const rows = Math.ceil(cs.length / GRID_COLS);
138
+ const cols = Math.min(GRID_COLS, cs.length);
139
+ innerW = cols * colW + GAP * (cols - 1);
140
+ innerH = rows * rowH + GAP * (rows - 1);
141
+ break;
142
+ }
143
+ case "table": {
144
+ innerW = Math.max(180, ...cs.map((c) => c.w));
145
+ innerH = TABLE_HDR_H + GAP + cs.reduce((a, c) => a + c.h, 0) + GAP * (cs.length - 1);
146
+ break;
147
+ }
148
+ case "paged": {
149
+ const active = cs[0];
150
+ const restN = cs.length - 1;
151
+ innerW = Math.max(160, active.w);
152
+ innerH = active.h + (restN > 0 ? GAP + restN * COLLAPSE_H + GAP * (restN - 1) : 0);
153
+ break;
154
+ }
155
+ default: {
156
+ // stack / card / floating
157
+ innerW = Math.max(...cs.map((c) => c.w));
158
+ innerH = cs.reduce((a, c) => a + c.h, 0) + GAP * (cs.length - 1);
159
+ }
160
+ }
161
+ s.w = innerW + PAD * 2;
162
+ s.h = LABEL_H + innerH + PAD * 2;
163
+ return s;
164
+ }
165
+ // --- emit: place + draw top-down -------------------------------------------
166
+ function emit(s, x, y, out) {
167
+ if (s.mode === "leaf") {
168
+ drawLeaf(s, x, y, out);
169
+ return;
170
+ }
171
+ drawContainer(s, x, y, out);
172
+ const cx0 = x + PAD;
173
+ const cy0 = y + LABEL_H + PAD;
174
+ const cs = s.children;
175
+ switch (s.mode) {
176
+ case "row":
177
+ case "bar":
178
+ case "chips": {
179
+ const rowH = Math.max(...cs.map((c) => c.h));
180
+ let cx = cx0;
181
+ for (const c of cs) {
182
+ emit(c, cx, cy0 + (rowH - c.h) / 2, out);
183
+ cx += c.w + GAP;
184
+ }
185
+ break;
186
+ }
187
+ case "grid": {
188
+ const colW = Math.max(...cs.map((c) => c.w));
189
+ const rowH = Math.max(...cs.map((c) => c.h));
190
+ cs.forEach((c, i) => {
191
+ const col = i % GRID_COLS;
192
+ const row = Math.floor(i / GRID_COLS);
193
+ emit(c, cx0 + col * (colW + GAP), cy0 + row * (rowH + GAP), out);
194
+ });
195
+ break;
196
+ }
197
+ case "table": {
198
+ const innerW = s.w - PAD * 2;
199
+ out.push(rect(cx0, cy0, innerW, TABLE_HDR_H, "none", true, "#e2e8f0"));
200
+ for (let i = 1; i < 4; i++) {
201
+ const lx = cx0 + (innerW * i) / 4;
202
+ out.push(line(lx, cy0, lx, cy0 + TABLE_HDR_H));
203
+ }
204
+ let ry = cy0 + TABLE_HDR_H + GAP;
205
+ for (const c of cs) {
206
+ emit(c, cx0, ry, out);
207
+ ry += c.h + GAP;
208
+ }
209
+ break;
210
+ }
211
+ case "paged": {
212
+ const active = cs[0];
213
+ emit(active, cx0, cy0, out);
214
+ let ry = cy0 + active.h + GAP;
215
+ for (const c of cs.slice(1)) {
216
+ out.push(rect(cx0, ry, s.w - PAD * 2, COLLAPSE_H, c.provenance, false, "#f1f5f9"));
217
+ out.push(text(cx0 + 6, ry + 15, "▸ " + collapsedPaneLabel(c), "dim"));
218
+ ry += COLLAPSE_H + GAP;
219
+ }
220
+ break;
221
+ }
222
+ default: {
223
+ let cy = cy0;
224
+ for (const c of cs) {
225
+ emit(c, cx0, cy, out);
226
+ cy += c.h + GAP;
227
+ }
228
+ }
229
+ }
230
+ }
231
+ // --- drawing primitives ----------------------------------------------------
232
+ const DARK_BARS = new Set(["app-bar", "nav-bar"]);
233
+ function drawContainer(s, x, y, out) {
234
+ const dark = DARK_BARS.has(s.archetype);
235
+ let fill = "#f8fafc";
236
+ if (s.archetype === "banner")
237
+ fill = "#fef3c7";
238
+ else if (s.archetype === "tab-bar")
239
+ fill = "#eef2ff";
240
+ else if (s.mode === "card" || s.mode === "floating")
241
+ fill = "#ffffff";
242
+ else if (dark)
243
+ fill = "#1f2937";
244
+ if (s.mode === "floating" || s.mode === "card") {
245
+ out.push(`<rect x="${x + SHADOW}" y="${y + SHADOW}" width="${s.w}" height="${s.h}" rx="4" fill="#0f172a" opacity="0.12"/>`);
246
+ }
247
+ out.push(rect(x, y, s.w, s.h, s.provenance, true, fill));
248
+ out.push(text(x + PAD, y + 12, labelMarkup(s), dark ? "lblw" : "lbl"));
249
+ }
250
+ function drawLeaf(s, x, y, out) {
251
+ const lbl = labelMarkup(s);
252
+ switch (s.leafShape) {
253
+ case "button":
254
+ out.push(`<rect x="${x}" y="${y}" width="${s.w}" height="${s.h}" rx="13" fill="#dbeafe" stroke="#3b82f6"${dash(s.provenance)}/>`);
255
+ out.push(text(x + 10, y + s.h / 2 + 4, lbl, "btn"));
256
+ break;
257
+ case "chip":
258
+ out.push(`<rect x="${x}" y="${y}" width="${s.w}" height="${s.h}" rx="11" fill="#e2e8f0" stroke="#94a3b8"${dash(s.provenance)}/>`);
259
+ out.push(text(x + 9, y + s.h / 2 + 4, lbl, "lbl"));
260
+ break;
261
+ case "circle": {
262
+ const r = s.w / 2;
263
+ out.push(`<circle cx="${x + r}" cy="${y + r}" r="${r}" fill="#dbeafe" stroke="#3b82f6"${dash(s.provenance)}/>`);
264
+ out.push(text(x + 6, y + r + 4, lbl, "dim"));
265
+ break;
266
+ }
267
+ case "image":
268
+ out.push(rect(x, y, s.w, s.h, s.provenance, false, "#f1f5f9"));
269
+ out.push(line(x, y, x + s.w, y + s.h));
270
+ out.push(line(x + s.w, y, x, y + s.h));
271
+ out.push(text(x + 4, y + s.h - 4, lbl, "dim"));
272
+ break;
273
+ case "text":
274
+ out.push(text(x, y + 12, lbl, "lbl"));
275
+ out.push(`<rect x="${x}" y="${y + 16}" width="${s.w}" height="5" rx="2" fill="#cbd5e1"/>`);
276
+ out.push(`<rect x="${x}" y="${y + 24}" width="${s.w * 0.66}" height="5" rx="2" fill="#e2e8f0"/>`);
277
+ break;
278
+ case "row":
279
+ out.push(rect(x, y, s.w, s.h, s.provenance, false, "#ffffff"));
280
+ out.push(`<circle cx="${x + 11}" cy="${y + s.h / 2}" r="5" fill="#cbd5e1"/>`);
281
+ out.push(text(x + 24, y + s.h / 2 + 4, lbl, "lbl"));
282
+ break;
283
+ default:
284
+ out.push(rect(x, y, s.w, s.h, s.provenance, false, "#fdfdfe"));
285
+ out.push(text(x + 6, y + s.h / 2 + 4, lbl, "lbl"));
286
+ }
287
+ }
288
+ // --- label derivation ------------------------------------------------------
289
+ /** The primary human label for a node, derived only from tokens in the spec. */
290
+ function primaryLabel(s) {
291
+ if (s.node.args.length > 0)
292
+ return s.node.args[0]; // authored positional label
293
+ if (s.mode === "leaf" && s.node.actions.length > 0 && (s.archetype === "button" || s.archetype === "fab"))
294
+ return humaniseAction(s.node.actions[0]); // CTA from action=
295
+ if (s.derivedLabel)
296
+ return s.derivedLabel; // windowed-pane tab label
297
+ const comp = s.node.component;
298
+ if (!comp)
299
+ return s.node.id;
300
+ // A `default` resolution is a recognised standard widget — its short vendor name
301
+ // reads fine as-is; bespoke names (override / heuristic / fallback) humanise better.
302
+ return s.provenance === "default" || s.provenance === "none" ? comp : humaniseName(comp, s.stripPrefixes);
303
+ }
304
+ function archMark(prov) {
305
+ return prov === "fallback" ? "?" : prov === "heuristic" ? "~" : "";
306
+ }
307
+ /** Plain text label (for width measurement): primary + archetype annotation. */
308
+ function labelPlain(s) {
309
+ const primary = primaryLabel(s);
310
+ if (s.archetype === "" || s.provenance === "none")
311
+ return primary;
312
+ return `${primary} :${s.archetype}${archMark(s.provenance)}`;
313
+ }
314
+ /**
315
+ * Collapsed paged-pane label: a wrapper pane often has a generic name (its
316
+ * content carries the identity), so prefer the content component's label when
317
+ * the pane itself has no authored label of its own. Spec-faithful — the content
318
+ * name is already in the block.
319
+ */
320
+ function collapsedPaneLabel(s) {
321
+ const content = s.children[0];
322
+ if (s.node.args.length === 0 && !s.derivedLabel && content && content.node.component) {
323
+ const lbl = escapeXml(primaryLabel(content));
324
+ return s.archetype === "" ? lbl : `${lbl} <tspan class="arch">:${escapeXml(s.archetype)}</tspan>`;
325
+ }
326
+ return labelMarkup(s);
327
+ }
328
+ /** SVG markup label (for emission): primary text + a small dim `:archetype` tspan. */
329
+ function labelMarkup(s) {
330
+ const primary = escapeXml(primaryLabel(s));
331
+ if (s.archetype === "" || s.provenance === "none")
332
+ return primary;
333
+ return `${primary} <tspan class="arch">:${escapeXml(s.archetype)}${archMark(s.provenance)}</tspan>`;
334
+ }
335
+ // --- humanisation (deterministic, adopter-agnostic) ------------------------
336
+ const HUMANISE_SUFFIXES = [
337
+ "Component", "Container", "Controller", "Activity", "Fragment", "Section",
338
+ "Layout", "Panel", "Popup", "Dialog", "Modal", "Widget", "View", "Screen",
339
+ "Page", "Table", "List", "Card", "Item",
340
+ ];
341
+ /** Strip a vendor prefix + a trailing archetype-ish suffix, split camelCase, Title Case. */
342
+ function humaniseName(name, prefixes) {
343
+ let s = name;
344
+ for (const p of prefixes) {
345
+ // Strip only at a PascalCase boundary (next char uppercase) so a short prefix
346
+ // like "St" peels "StLayout" → "Layout" but never "Status" → "atus".
347
+ if (s.length > p.length && s.startsWith(p) && /[A-Z]/.test(s.charAt(p.length))) {
348
+ s = s.slice(p.length);
349
+ break;
350
+ }
351
+ }
352
+ for (const suf of HUMANISE_SUFFIXES) {
353
+ if (s.length > suf.length && s.endsWith(suf)) {
354
+ s = s.slice(0, -suf.length);
355
+ break;
356
+ }
357
+ }
358
+ const words = splitWords(s);
359
+ if (words.length === 0)
360
+ return titleCaseWords(splitWords(name)); // prefix/suffix ate the whole name
361
+ return titleCaseWords(words);
362
+ }
363
+ /** Humanise an action token, prefixing create/add/new verbs with "+ ". */
364
+ function humaniseAction(action) {
365
+ const human = titleCaseWords(splitWords(action));
366
+ return /^(new|add|create)([^a-z]|$)/i.test(action) ? `+ ${human}` : human;
367
+ }
368
+ function splitWords(s) {
369
+ return s
370
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
371
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
372
+ .replace(/[_\-.]+/g, " ")
373
+ .trim()
374
+ .split(/\s+/)
375
+ .filter(Boolean);
376
+ }
377
+ function titleCaseWords(words) {
378
+ return words.map((w) => (w === w.toUpperCase() ? w : w.charAt(0).toUpperCase() + w.slice(1))).join(" ");
379
+ }
380
+ function dash(prov) {
381
+ return prov === "heuristic" ? ' stroke-dasharray="5 3"' : prov === "fallback" ? ' stroke-dasharray="1 4"' : "";
382
+ }
383
+ function rect(x, y, w, h, prov, container, fill) {
384
+ const f = fill ?? (container ? "#f4f6f8" : "#fdfdfe");
385
+ return `<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="3" fill="${f}" stroke="#64748b" stroke-width="1"${dash(prov)}/>`;
386
+ }
387
+ function line(x1, y1, x2, y2) {
388
+ return `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="#cbd5e1" stroke-width="1"/>`;
389
+ }
390
+ function text(x, y, content, cls) {
391
+ return `<text x="${x}" y="${y}" class="${cls}">${content}</text>`;
392
+ }
393
+ function svg(width, height, body) {
394
+ return [
395
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`,
396
+ `<style>text{font-family:ui-monospace,monospace;font-size:11px;fill:#334155}.title{font-size:13px;font-weight:600;fill:#0f172a}.lbl{fill:#334155}.lblw{fill:#f8fafc}.dim{fill:#94a3b8}.btn{fill:#1e40af;font-weight:600}.arch{font-size:9px;fill:#a8b3c2}</style>`,
397
+ `<rect width="${width}" height="${height}" fill="#fff"/>`,
398
+ body,
399
+ `</svg>`,
400
+ ].join("\n");
401
+ }
402
+ function emptySvg(title, message) {
403
+ const body = [
404
+ title ? text(PAD, 16, escapeXml(title), "title") : "",
405
+ text(PAD, title ? 36 : 16, escapeXml(message), "lbl"),
406
+ ]
407
+ .filter(Boolean)
408
+ .join("\n");
409
+ return svg(240, title ? 48 : 28, body);
410
+ }
411
+ function escapeXml(s) {
412
+ return s
413
+ .replace(/&/g, "&amp;")
414
+ .replace(/</g, "&lt;")
415
+ .replace(/>/g, "&gt;")
416
+ .replace(/"/g, "&quot;");
417
+ }
418
+ //# sourceMappingURL=wireframe.js.map