@monoes/monomindcli 2.15.5 → 2.15.7

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 (226) hide show
  1. package/dist/src/commands/cleanup-plan.d.ts +57 -0
  2. package/dist/src/commands/cleanup-plan.d.ts.map +1 -0
  3. package/dist/src/commands/cleanup-plan.js +379 -0
  4. package/dist/src/commands/cleanup-plan.js.map +1 -0
  5. package/dist/src/commands/cleanup.d.ts.map +1 -1
  6. package/dist/src/commands/cleanup.js +87 -160
  7. package/dist/src/commands/cleanup.js.map +1 -1
  8. package/dist/src/commands/search-universal.d.ts +8 -0
  9. package/dist/src/commands/search-universal.d.ts.map +1 -1
  10. package/dist/src/commands/search-universal.js +14 -0
  11. package/dist/src/commands/search-universal.js.map +1 -1
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/index.js +11 -2
  14. package/dist/src/index.js.map +1 -1
  15. package/dist/src/orgrt/completion-gate.d.ts +40 -0
  16. package/dist/src/orgrt/completion-gate.d.ts.map +1 -1
  17. package/dist/src/orgrt/completion-gate.js +90 -9
  18. package/dist/src/orgrt/completion-gate.js.map +1 -1
  19. package/dist/src/orgrt/daemon.d.ts +4 -0
  20. package/dist/src/orgrt/daemon.d.ts.map +1 -1
  21. package/dist/src/orgrt/daemon.js +15 -2
  22. package/dist/src/orgrt/daemon.js.map +1 -1
  23. package/dist/src/orgrt/decisions.d.ts +24 -1
  24. package/dist/src/orgrt/decisions.d.ts.map +1 -1
  25. package/dist/src/orgrt/decisions.js +171 -11
  26. package/dist/src/orgrt/decisions.js.map +1 -1
  27. package/dist/src/orgrt/idle-deadline.d.ts +10 -0
  28. package/dist/src/orgrt/idle-deadline.d.ts.map +1 -1
  29. package/dist/src/orgrt/idle-deadline.js +8 -0
  30. package/dist/src/orgrt/idle-deadline.js.map +1 -1
  31. package/dist/src/orgrt/mailbox.d.ts +7 -0
  32. package/dist/src/orgrt/mailbox.d.ts.map +1 -1
  33. package/dist/src/orgrt/mailbox.js +14 -0
  34. package/dist/src/orgrt/mailbox.js.map +1 -1
  35. package/dist/src/orgrt/review-packet.d.ts +1 -1
  36. package/dist/src/orgrt/review-packet.d.ts.map +1 -1
  37. package/dist/src/orgrt/review-packet.js +2 -1
  38. package/dist/src/orgrt/review-packet.js.map +1 -1
  39. package/dist/src/orgrt/role-sandbox.d.ts +1 -0
  40. package/dist/src/orgrt/role-sandbox.d.ts.map +1 -1
  41. package/dist/src/orgrt/role-sandbox.js +6 -1
  42. package/dist/src/orgrt/role-sandbox.js.map +1 -1
  43. package/dist/src/orgrt/session.d.ts +3 -0
  44. package/dist/src/orgrt/session.d.ts.map +1 -1
  45. package/dist/src/orgrt/session.js +8 -1
  46. package/dist/src/orgrt/session.js.map +1 -1
  47. package/dist/src/orgrt/task-dag.d.ts +3 -0
  48. package/dist/src/orgrt/task-dag.d.ts.map +1 -1
  49. package/dist/src/orgrt/task-dag.js +7 -0
  50. package/dist/src/orgrt/task-dag.js.map +1 -1
  51. package/dist/src/orgrt/types.d.ts +3 -0
  52. package/dist/src/orgrt/types.d.ts.map +1 -1
  53. package/dist/src/orgrt/types.js +6 -2
  54. package/dist/src/orgrt/types.js.map +1 -1
  55. package/dist/src/update/checker.d.ts +3 -1
  56. package/dist/src/update/checker.d.ts.map +1 -1
  57. package/dist/src/update/checker.js +7 -3
  58. package/dist/src/update/checker.js.map +1 -1
  59. package/dist/src/update/index.d.ts +16 -0
  60. package/dist/src/update/index.d.ts.map +1 -1
  61. package/dist/src/update/index.js +37 -1
  62. package/dist/src/update/index.js.map +1 -1
  63. package/dist/src/update/refresh-worker.d.ts +9 -0
  64. package/dist/src/update/refresh-worker.d.ts.map +1 -0
  65. package/dist/src/update/refresh-worker.js +17 -0
  66. package/dist/src/update/refresh-worker.js.map +1 -0
  67. package/dist/src/utils/resource-governor.d.ts +44 -2
  68. package/dist/src/utils/resource-governor.d.ts.map +1 -1
  69. package/dist/src/utils/resource-governor.js +157 -40
  70. package/dist/src/utils/resource-governor.js.map +1 -1
  71. package/dist/tsconfig.tsbuildinfo +1 -1
  72. package/package.json +4 -4
  73. package/.claude/skills/monodesign/SKILL.md +0 -257
  74. package/.claude/skills/monodesign/adapt.md +0 -53
  75. package/.claude/skills/monodesign/agents/monodesign-asset-producer.md +0 -100
  76. package/.claude/skills/monodesign/agents/monodesign-manual-edit-applier.md +0 -103
  77. package/.claude/skills/monodesign/animate.md +0 -65
  78. package/.claude/skills/monodesign/audit.md +0 -89
  79. package/.claude/skills/monodesign/bolder.md +0 -50
  80. package/.claude/skills/monodesign/clarify.md +0 -64
  81. package/.claude/skills/monodesign/colorize.md +0 -68
  82. package/.claude/skills/monodesign/craft.md +0 -51
  83. package/.claude/skills/monodesign/critique.md +0 -66
  84. package/.claude/skills/monodesign/delight.md +0 -47
  85. package/.claude/skills/monodesign/distill.md +0 -56
  86. package/.claude/skills/monodesign/document.md +0 -83
  87. package/.claude/skills/monodesign/extract.md +0 -74
  88. package/.claude/skills/monodesign/harden.md +0 -65
  89. package/.claude/skills/monodesign/init.md +0 -30
  90. package/.claude/skills/monodesign/layout.md +0 -36
  91. package/.claude/skills/monodesign/live.md +0 -34
  92. package/.claude/skills/monodesign/onboard.md +0 -50
  93. package/.claude/skills/monodesign/optimize.md +0 -64
  94. package/.claude/skills/monodesign/overdrive.md +0 -56
  95. package/.claude/skills/monodesign/polish.md +0 -68
  96. package/.claude/skills/monodesign/quieter.md +0 -57
  97. package/.claude/skills/monodesign/reference/adapt.md +0 -312
  98. package/.claude/skills/monodesign/reference/adapt.native.md +0 -58
  99. package/.claude/skills/monodesign/reference/android.md +0 -40
  100. package/.claude/skills/monodesign/reference/animate.md +0 -203
  101. package/.claude/skills/monodesign/reference/antipatterns-catalog.md +0 -410
  102. package/.claude/skills/monodesign/reference/audit.md +0 -135
  103. package/.claude/skills/monodesign/reference/audit.native.md +0 -139
  104. package/.claude/skills/monodesign/reference/bolder.md +0 -120
  105. package/.claude/skills/monodesign/reference/brand-workflow.md +0 -180
  106. package/.claude/skills/monodesign/reference/brand.md +0 -108
  107. package/.claude/skills/monodesign/reference/clarify.md +0 -288
  108. package/.claude/skills/monodesign/reference/codex.md +0 -105
  109. package/.claude/skills/monodesign/reference/cognitive-load.md +0 -106
  110. package/.claude/skills/monodesign/reference/color-and-contrast.md +0 -105
  111. package/.claude/skills/monodesign/reference/colorize.md +0 -257
  112. package/.claude/skills/monodesign/reference/component-specs.md +0 -260
  113. package/.claude/skills/monodesign/reference/component-states.md +0 -274
  114. package/.claude/skills/monodesign/reference/component-system.md +0 -358
  115. package/.claude/skills/monodesign/reference/copy-formulas.md +0 -160
  116. package/.claude/skills/monodesign/reference/craft.md +0 -123
  117. package/.claude/skills/monodesign/reference/critique.md +0 -780
  118. package/.claude/skills/monodesign/reference/delight.md +0 -302
  119. package/.claude/skills/monodesign/reference/design-principles.md +0 -246
  120. package/.claude/skills/monodesign/reference/distill.md +0 -111
  121. package/.claude/skills/monodesign/reference/document.md +0 -429
  122. package/.claude/skills/monodesign/reference/extract.md +0 -69
  123. package/.claude/skills/monodesign/reference/harden.md +0 -347
  124. package/.claude/skills/monodesign/reference/heuristics-scoring.md +0 -234
  125. package/.claude/skills/monodesign/reference/hooks.md +0 -92
  126. package/.claude/skills/monodesign/reference/image-prompts.md +0 -130
  127. package/.claude/skills/monodesign/reference/init.md +0 -221
  128. package/.claude/skills/monodesign/reference/interaction-design.md +0 -189
  129. package/.claude/skills/monodesign/reference/ios.md +0 -45
  130. package/.claude/skills/monodesign/reference/layout.md +0 -185
  131. package/.claude/skills/monodesign/reference/live.md +0 -710
  132. package/.claude/skills/monodesign/reference/motion-design.md +0 -109
  133. package/.claude/skills/monodesign/reference/onboard.md +0 -234
  134. package/.claude/skills/monodesign/reference/optimize.md +0 -258
  135. package/.claude/skills/monodesign/reference/overdrive.md +0 -130
  136. package/.claude/skills/monodesign/reference/personas.md +0 -179
  137. package/.claude/skills/monodesign/reference/polish.md +0 -241
  138. package/.claude/skills/monodesign/reference/pre-delivery-checklist.md +0 -108
  139. package/.claude/skills/monodesign/reference/product.md +0 -60
  140. package/.claude/skills/monodesign/reference/quieter.md +0 -99
  141. package/.claude/skills/monodesign/reference/responsive-design.md +0 -114
  142. package/.claude/skills/monodesign/reference/shape.md +0 -165
  143. package/.claude/skills/monodesign/reference/spatial-design.md +0 -100
  144. package/.claude/skills/monodesign/reference/teach.md +0 -10
  145. package/.claude/skills/monodesign/reference/token-architecture.md +0 -222
  146. package/.claude/skills/monodesign/reference/typeset.md +0 -301
  147. package/.claude/skills/monodesign/reference/typography.md +0 -159
  148. package/.claude/skills/monodesign/reference/ux-research.md +0 -143
  149. package/.claude/skills/monodesign/reference/ux-rules.md +0 -211
  150. package/.claude/skills/monodesign/reference/ux-writing.md +0 -107
  151. package/.claude/skills/monodesign/scripts/command-metadata.json +0 -94
  152. package/.claude/skills/monodesign/scripts/context-signals.mjs +0 -260
  153. package/.claude/skills/monodesign/scripts/context.mjs +0 -1065
  154. package/.claude/skills/monodesign/scripts/critique-storage.mjs +0 -416
  155. package/.claude/skills/monodesign/scripts/detect-csp.mjs +0 -193
  156. package/.claude/skills/monodesign/scripts/detect.mjs +0 -33
  157. package/.claude/skills/monodesign/scripts/detector/browser/injected/index.mjs +0 -1960
  158. package/.claude/skills/monodesign/scripts/detector/cli/main.mjs +0 -321
  159. package/.claude/skills/monodesign/scripts/detector/design-system.mjs +0 -814
  160. package/.claude/skills/monodesign/scripts/detector/detect-antipatterns-browser.js +0 -5628
  161. package/.claude/skills/monodesign/scripts/detector/detect-antipatterns.mjs +0 -50
  162. package/.claude/skills/monodesign/scripts/detector/engines/browser/detect-url.mjs +0 -259
  163. package/.claude/skills/monodesign/scripts/detector/engines/browser/drivers.mjs +0 -349
  164. package/.claude/skills/monodesign/scripts/detector/engines/regex/detect-text.mjs +0 -636
  165. package/.claude/skills/monodesign/scripts/detector/engines/static-html/css-cascade.mjs +0 -1016
  166. package/.claude/skills/monodesign/scripts/detector/engines/static-html/detect-html.mjs +0 -253
  167. package/.claude/skills/monodesign/scripts/detector/engines/visual/screenshot-contrast.mjs +0 -198
  168. package/.claude/skills/monodesign/scripts/detector/findings.mjs +0 -12
  169. package/.claude/skills/monodesign/scripts/detector/fix/css-regions.mjs +0 -181
  170. package/.claude/skills/monodesign/scripts/detector/fix/fixers.mjs +0 -373
  171. package/.claude/skills/monodesign/scripts/detector/fix/index.mjs +0 -426
  172. package/.claude/skills/monodesign/scripts/detector/node/file-system.mjs +0 -198
  173. package/.claude/skills/monodesign/scripts/detector/profile/profiler.mjs +0 -166
  174. package/.claude/skills/monodesign/scripts/detector/registry/antipatterns.mjs +0 -563
  175. package/.claude/skills/monodesign/scripts/detector/rules/checks.mjs +0 -3024
  176. package/.claude/skills/monodesign/scripts/detector/shared/color.mjs +0 -124
  177. package/.claude/skills/monodesign/scripts/detector/shared/constants.mjs +0 -101
  178. package/.claude/skills/monodesign/scripts/detector/shared/fonts.mjs +0 -30
  179. package/.claude/skills/monodesign/scripts/detector/shared/inline-ignores.mjs +0 -148
  180. package/.claude/skills/monodesign/scripts/detector/shared/page.mjs +0 -7
  181. package/.claude/skills/monodesign/scripts/hook-admin.mjs +0 -694
  182. package/.claude/skills/monodesign/scripts/hook-before-edit.mjs +0 -516
  183. package/.claude/skills/monodesign/scripts/hook-lib.mjs +0 -1785
  184. package/.claude/skills/monodesign/scripts/hook.mjs +0 -61
  185. package/.claude/skills/monodesign/scripts/lib/design-parser.mjs +0 -809
  186. package/.claude/skills/monodesign/scripts/lib/is-generated.mjs +0 -69
  187. package/.claude/skills/monodesign/scripts/lib/monodesign-config.mjs +0 -640
  188. package/.claude/skills/monodesign/scripts/lib/monodesign-paths.mjs +0 -129
  189. package/.claude/skills/monodesign/scripts/lib/provider.mjs +0 -4
  190. package/.claude/skills/monodesign/scripts/lib/target-args.mjs +0 -42
  191. package/.claude/skills/monodesign/scripts/live/browser-script-parts.mjs +0 -50
  192. package/.claude/skills/monodesign/scripts/live/completion.mjs +0 -19
  193. package/.claude/skills/monodesign/scripts/live/event-validation.mjs +0 -137
  194. package/.claude/skills/monodesign/scripts/live/insert-ui.mjs +0 -458
  195. package/.claude/skills/monodesign/scripts/live/manual-apply.mjs +0 -939
  196. package/.claude/skills/monodesign/scripts/live/manual-edit-routes.mjs +0 -357
  197. package/.claude/skills/monodesign/scripts/live/manual-edits-buffer.mjs +0 -152
  198. package/.claude/skills/monodesign/scripts/live/session-store.mjs +0 -293
  199. package/.claude/skills/monodesign/scripts/live/svelte-component.mjs +0 -826
  200. package/.claude/skills/monodesign/scripts/live/sveltekit-adapter.mjs +0 -279
  201. package/.claude/skills/monodesign/scripts/live/ui-core.mjs +0 -180
  202. package/.claude/skills/monodesign/scripts/live/vocabulary.mjs +0 -36
  203. package/.claude/skills/monodesign/scripts/live-accept.mjs +0 -812
  204. package/.claude/skills/monodesign/scripts/live-browser-dom.js +0 -147
  205. package/.claude/skills/monodesign/scripts/live-browser-session.js +0 -124
  206. package/.claude/skills/monodesign/scripts/live-browser.js +0 -11213
  207. package/.claude/skills/monodesign/scripts/live-commit-manual-edits.mjs +0 -1241
  208. package/.claude/skills/monodesign/scripts/live-complete.mjs +0 -75
  209. package/.claude/skills/monodesign/scripts/live-copy-edit-agent.mjs +0 -683
  210. package/.claude/skills/monodesign/scripts/live-discard-manual-edits.mjs +0 -51
  211. package/.claude/skills/monodesign/scripts/live-inject.mjs +0 -596
  212. package/.claude/skills/monodesign/scripts/live-insert.mjs +0 -272
  213. package/.claude/skills/monodesign/scripts/live-manual-edit-evidence.mjs +0 -363
  214. package/.claude/skills/monodesign/scripts/live-poll.mjs +0 -384
  215. package/.claude/skills/monodesign/scripts/live-resume.mjs +0 -94
  216. package/.claude/skills/monodesign/scripts/live-server.mjs +0 -1210
  217. package/.claude/skills/monodesign/scripts/live-status.mjs +0 -61
  218. package/.claude/skills/monodesign/scripts/live-target.mjs +0 -30
  219. package/.claude/skills/monodesign/scripts/live-wrap.mjs +0 -884
  220. package/.claude/skills/monodesign/scripts/live.mjs +0 -297
  221. package/.claude/skills/monodesign/scripts/modern-screenshot.umd.js +0 -14
  222. package/.claude/skills/monodesign/scripts/palette.mjs +0 -635
  223. package/.claude/skills/monodesign/scripts/pin.mjs +0 -221
  224. package/.claude/skills/monodesign/shape.md +0 -71
  225. package/.claude/skills/monodesign/teach.md +0 -18
  226. package/.claude/skills/monodesign/typeset.md +0 -59
@@ -1,809 +0,0 @@
1
- // Parse a DESIGN.md (Stitch-spec format) into a structured JSON model that
2
- // the live-mode design-system panel can render. Deterministic, dependency-free.
3
- //
4
- // Two-layer: YAML frontmatter (machine-readable tokens) + markdown body
5
- // (prose with six canonical H2 sections). When frontmatter is present, it's
6
- // exposed on `model.frontmatter` alongside the prose-scraped sections;
7
- // consumers can prefer frontmatter values and fall back to prose.
8
-
9
- const CANONICAL_SECTIONS = [
10
- 'Overview',
11
- 'Colors',
12
- 'Typography',
13
- 'Elevation',
14
- 'Components',
15
- "Do's and Don'ts",
16
- ];
17
-
18
- // ---------- Frontmatter (Stitch YAML subset) ----------
19
-
20
- function parseFrontmatter(md) {
21
- const lines = md.split(/\r?\n/);
22
- if (lines[0]?.trim() !== '---') return { frontmatter: null, body: md };
23
-
24
- let end = -1;
25
- for (let i = 1; i < lines.length; i++) {
26
- if (lines[i].trim() === '---') { end = i; break; }
27
- }
28
- if (end === -1) return { frontmatter: null, body: md };
29
-
30
- const yaml = lines.slice(1, end).join('\n');
31
- const body = lines.slice(end + 1).join('\n');
32
- try {
33
- return { frontmatter: parseYamlSubset(yaml), body };
34
- } catch {
35
- return { frontmatter: null, body: md };
36
- }
37
- }
38
-
39
- // Minimal YAML reader for the Stitch frontmatter subset: scalar maps with
40
- // one level of nested objects (typography roles, components). Indent-based,
41
- // 2-space convention. No arrays, no anchors, no multi-line scalars — Stitch's
42
- // schema doesn't need them and accepting them would require a real YAML
43
- // dependency we don't want to vendor.
44
- function parseYamlSubset(yaml) {
45
- const lines = yaml.split(/\r?\n/);
46
- const root = {};
47
- const stack = [{ indent: -1, obj: root }];
48
-
49
- for (const raw of lines) {
50
- // Skip blanks and line-only comments. Don't strip inline comments:
51
- // unquoted hex values start with `#` and can't be safely distinguished
52
- // from a comment after whitespace.
53
- if (!raw.trim() || /^\s*#/.test(raw)) continue;
54
-
55
- const indent = raw.match(/^\s*/)[0].length;
56
- const content = raw.slice(indent);
57
-
58
- const colonIdx = findTopLevelColon(content);
59
- if (colonIdx === -1) continue;
60
-
61
- while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
62
- stack.pop();
63
- }
64
-
65
- const key = unquoteYamlKey(content.slice(0, colonIdx).trim());
66
- const rest = stripInlineYamlComment(content.slice(colonIdx + 1).trim());
67
- const parent = stack[stack.length - 1].obj;
68
-
69
- if (rest === '') {
70
- const obj = {};
71
- parent[key] = obj;
72
- stack.push({ indent, obj });
73
- } else {
74
- parent[key] = parseScalar(rest);
75
- }
76
- }
77
-
78
- return root;
79
- }
80
-
81
- function findTopLevelColon(s) {
82
- let inQuote = null;
83
- for (let i = 0; i < s.length; i++) {
84
- const ch = s[i];
85
- if (inQuote) {
86
- if (ch === inQuote && s[i - 1] !== '\\') inQuote = null;
87
- } else if (ch === '"' || ch === "'") {
88
- inQuote = ch;
89
- } else if (ch === ':') {
90
- return i;
91
- }
92
- }
93
- return -1;
94
- }
95
-
96
- function unquoteYamlKey(key) {
97
- if ((key.startsWith('"') && key.endsWith('"')) || (key.startsWith("'") && key.endsWith("'"))) {
98
- return key.slice(1, -1);
99
- }
100
- return key;
101
- }
102
-
103
- function stripInlineYamlComment(s) {
104
- let inQuote = null;
105
- for (let i = 0; i < s.length; i++) {
106
- const ch = s[i];
107
- if (inQuote) {
108
- if (ch === inQuote && s[i - 1] !== '\\') inQuote = null;
109
- } else if (ch === '"' || ch === "'") {
110
- inQuote = ch;
111
- } else if (ch === '#' && i > 0 && /\s/.test(s[i - 1])) {
112
- return s.slice(0, i).trimEnd();
113
- }
114
- }
115
- return s;
116
- }
117
-
118
- function parseScalar(raw) {
119
- const s = raw.trim();
120
- if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
121
- return s.slice(1, -1);
122
- }
123
- if (s === 'true') return true;
124
- if (s === 'false') return false;
125
- if (s === 'null' || s === '~') return null;
126
- if (/^-?\d+$/.test(s)) return Number(s);
127
- if (/^-?\d*\.\d+$/.test(s)) return Number(s);
128
- return s;
129
- }
130
-
131
- const HEX_RE = /#[0-9a-fA-F]{3,8}\b/g;
132
- const OKLCH_RE = /oklch\([^)]+\)/gi;
133
-
134
- // ---------- Section splitting ----------
135
-
136
- function splitSections(md) {
137
- const lines = md.split(/\r?\n/);
138
- let title = null;
139
- const sections = {};
140
- let current = null;
141
-
142
- for (const raw of lines) {
143
- const line = raw.trimEnd();
144
-
145
- if (!title && line.startsWith('# ') && !line.startsWith('## ')) {
146
- title = line.replace(/^#\s+/, '').trim();
147
- continue;
148
- }
149
-
150
- const h2 = line.match(/^##\s+(?:\d+\.\s*)?([^:\n]+?)(?::\s*(.+))?$/);
151
- if (h2) {
152
- const rawName = normalizeApostrophes(h2[1].trim());
153
- const subtitle = h2[2] ? h2[2].trim() : null;
154
- const canonical = matchCanonicalSection(rawName);
155
- if (canonical) {
156
- current = { name: canonical, subtitle, lines: [] };
157
- sections[canonical] = current;
158
- continue;
159
- }
160
- // non-canonical H2 — ignore but stop feeding into current
161
- current = null;
162
- continue;
163
- }
164
-
165
- if (current) current.lines.push(raw);
166
- }
167
-
168
- return { title, sections };
169
- }
170
-
171
- function normalizeApostrophes(s) {
172
- return s.replace(/[\u2018\u2019]/g, "'");
173
- }
174
-
175
- function matchCanonicalSection(name) {
176
- const normalized = normalizeApostrophes(name).toLowerCase();
177
- // Exact match first
178
- for (const c of CANONICAL_SECTIONS) {
179
- if (normalizeApostrophes(c).toLowerCase() === normalized) return c;
180
- }
181
- // Keyword-contained match: "Overview & Creative North Star" -> "Overview",
182
- // "Elevation & Depth" -> "Elevation", etc.
183
- for (const c of CANONICAL_SECTIONS) {
184
- const key = normalizeApostrophes(c).toLowerCase();
185
- const pattern = new RegExp(`\\b${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`);
186
- if (pattern.test(normalized)) return c;
187
- }
188
- return null;
189
- }
190
-
191
- // ---------- Subsection splitting (inside a canonical section) ----------
192
-
193
- function splitSubsections(lines) {
194
- const subs = [];
195
- let current = { name: null, lines: [] };
196
- subs.push(current);
197
-
198
- for (const raw of lines) {
199
- const h3 = raw.match(/^###\s+(.+?)\s*$/);
200
- if (h3) {
201
- current = { name: h3[1].trim(), lines: [] };
202
- subs.push(current);
203
- continue;
204
- }
205
- current.lines.push(raw);
206
- }
207
-
208
- return subs;
209
- }
210
-
211
- // ---------- Generic helpers ----------
212
-
213
- function collectParagraphs(lines) {
214
- const paragraphs = [];
215
- let buf = [];
216
- const flush = () => {
217
- if (buf.length) {
218
- paragraphs.push(buf.join(' ').trim());
219
- buf = [];
220
- }
221
- };
222
- for (const raw of lines) {
223
- const trimmed = raw.trim();
224
- if (trimmed === '') { flush(); continue; }
225
- // Horizontal rules (---, ***) and headings/bullets end a paragraph.
226
- if (/^(?:-{3,}|\*{3,}|_{3,})$/.test(trimmed)) { flush(); continue; }
227
- if (raw.startsWith('#') || raw.match(/^[-*]\s/)) { flush(); continue; }
228
- buf.push(trimmed);
229
- }
230
- flush();
231
- return paragraphs.filter(Boolean);
232
- }
233
-
234
- function collectBullets(lines) {
235
- const bullets = [];
236
- let current = null;
237
- for (const raw of lines) {
238
- const m = raw.match(/^\s*[-*]\s+(.+)$/);
239
- if (m) {
240
- if (current) bullets.push(current);
241
- current = m[1];
242
- continue;
243
- }
244
- // continuation of a bullet (indented line)
245
- if (current && raw.match(/^\s{2,}\S/)) {
246
- current += ` ${raw.trim()}`;
247
- continue;
248
- }
249
- // blank line ends a bullet
250
- if (raw.trim() === '' && current) {
251
- bullets.push(current);
252
- current = null;
253
- }
254
- }
255
- if (current) bullets.push(current);
256
- return bullets;
257
- }
258
-
259
- function stripBold(s) {
260
- return s.replace(/\*\*(.+?)\*\*/g, '$1');
261
- }
262
-
263
- function extractNamedRules(lines) {
264
- const rules = [];
265
- const seen = new Set();
266
-
267
- // Style A (Monodesign): "**The X Rule.** body body body" — can span lines.
268
- const joined = lines.join('\n');
269
- const inlineStart = /\*\*(The [^*]+?Rule)\.\*\*/g;
270
- const inlineMatches = [];
271
- let m;
272
- while ((m = inlineStart.exec(joined)) !== null) {
273
- inlineMatches.push({ name: m[1], start: m.index, end: inlineStart.lastIndex });
274
- }
275
- for (let i = 0; i < inlineMatches.length; i++) {
276
- const mm = inlineMatches[i];
277
- const bodyEnd = i + 1 < inlineMatches.length ? inlineMatches[i + 1].start : joined.length;
278
- const body = joined
279
- .slice(mm.end, bodyEnd)
280
- .replace(/\n##[^\n]*$/s, '')
281
- .replace(/\n###[^\n]*$/s, '')
282
- .trim();
283
- const name = stripBold(mm.name).trim();
284
- seen.add(name.toLowerCase());
285
- rules.push({ name, body: stripBold(body) });
286
- }
287
-
288
- // Style B (Stitch): `### The "X" Rule` or `### The X Fallback`, body is the
289
- // bullets/paragraphs until the next heading. Accept Rule / Fallback / Principle.
290
- for (let i = 0; i < lines.length; i++) {
291
- const h3 = lines[i].match(/^###\s+(.+?)\s*$/);
292
- if (!h3) continue;
293
- const headerName = stripBold(h3[1]).replace(/["“”]/g, '').trim();
294
- if (!/^The\b.*\b(Rule|Fallback|Principle)\b/i.test(headerName)) continue;
295
- if (seen.has(headerName.toLowerCase())) continue;
296
-
297
- const bodyLines = [];
298
- for (let j = i + 1; j < lines.length; j++) {
299
- if (/^##\s|^###\s/.test(lines[j])) break;
300
- bodyLines.push(lines[j]);
301
- }
302
- const body = stripBold(bodyLines.join('\n').replace(/\n+/g, ' ')).trim();
303
- if (body) {
304
- seen.add(headerName.toLowerCase());
305
- rules.push({ name: headerName, body });
306
- }
307
- }
308
-
309
- // Style C (Stitch bullet form): "* **The Layering Principle:** body"
310
- // Colon/period lives inside the bold, so match "**...**" then inspect.
311
- for (const b of collectBullets(lines)) {
312
- const mm = b.match(/^\*\*([^*]+?)\*\*\s*(.+)$/);
313
- if (!mm) continue;
314
- const nameRaw = mm[1].replace(/[.:]\s*$/, '').replace(/["“”]/g, '').trim();
315
- if (!/^The\b.+\b(Rule|Fallback|Principle)$/i.test(nameRaw)) continue;
316
- if (seen.has(nameRaw.toLowerCase())) continue;
317
- seen.add(nameRaw.toLowerCase());
318
- rules.push({ name: nameRaw, body: stripBold(mm[2]).trim() });
319
- }
320
-
321
- return rules;
322
- }
323
-
324
- // ---------- Per-section extractors ----------
325
-
326
- function extractOverview(section) {
327
- if (!section) return null;
328
- const text = section.lines.join('\n');
329
- const northStar = text.match(/\*\*Creative North Star:\s*"([^"]+)"\*\*/);
330
- const keyChars = [];
331
- const keyCharMatch = text.match(/\*\*Key Characteristics:\*\*\s*\n([\s\S]+?)(?:\n##|\n###|$)/);
332
- if (keyCharMatch) {
333
- for (const line of keyCharMatch[1].split('\n')) {
334
- const m = line.match(/^\s*[-*]\s+(.+)$/);
335
- if (m) keyChars.push(stripBold(m[1].trim()));
336
- }
337
- }
338
-
339
- // Philosophy paragraphs: everything that isn't a rule header or key-char block
340
- const paragraphs = collectParagraphs(section.lines).filter(
341
- (p) =>
342
- !p.startsWith('**Creative North Star') &&
343
- !p.startsWith('**Key Characteristics')
344
- );
345
-
346
- return {
347
- subtitle: section.subtitle,
348
- creativeNorthStar: northStar ? northStar[1] : null,
349
- philosophy: paragraphs,
350
- keyCharacteristics: keyChars,
351
- };
352
- }
353
-
354
- function extractColors(section) {
355
- if (!section) return null;
356
- const subs = splitSubsections(section.lines);
357
-
358
- const description = collectParagraphs(subs[0].lines).join(' ');
359
- const groups = [];
360
- const ROLE_KEYWORDS = /^(primary|secondary|tertiary|neutral|accent)\b/i;
361
-
362
- for (const sub of subs.slice(1)) {
363
- if (!sub.name || /Named Rules?/i.test(sub.name) || /^The\s/i.test(sub.name)) continue;
364
-
365
- const bullets = collectBullets(sub.lines);
366
- const parsed = bullets.map((b) => parseColorBullet(b)).filter(Boolean);
367
- if (parsed.length === 0) continue;
368
-
369
- // If every bullet starts with a role keyword (Primary/Secondary/...), promote
370
- // each bullet to its own group. Otherwise keep the subsection as the group.
371
- const allRoleBullets =
372
- parsed.length > 0 && parsed.every((p) => p.name && ROLE_KEYWORDS.test(p.name));
373
-
374
- if (allRoleBullets) {
375
- for (const p of parsed) {
376
- groups.push({ role: p.name, colors: [p] });
377
- }
378
- } else {
379
- groups.push({ role: sub.name, colors: parsed });
380
- }
381
- }
382
-
383
- // If the Colors section has no subsections at all (unlikely), fall back to
384
- // scanning the whole section as a flat bullet list.
385
- if (groups.length === 0) {
386
- const flat = collectBullets(section.lines)
387
- .map((b) => parseColorBullet(b))
388
- .filter(Boolean);
389
- if (flat.length) {
390
- for (const p of flat) {
391
- if (p.name && ROLE_KEYWORDS.test(p.name)) {
392
- groups.push({ role: p.name, colors: [p] });
393
- } else {
394
- const fallback = groups.find((g) => g.role === 'Palette');
395
- if (fallback) fallback.colors.push(p);
396
- else groups.push({ role: 'Palette', colors: [p] });
397
- }
398
- }
399
- }
400
- }
401
-
402
- return {
403
- subtitle: section.subtitle,
404
- description: description || null,
405
- groups,
406
- rules: extractNamedRules(section.lines),
407
- };
408
- }
409
-
410
- function parseColorBullet(bullet) {
411
- const text = bullet.trim();
412
-
413
- // Case 1 (Monodesign): **Name** (value-with-maybe-nested-parens): description
414
- const bold = text.match(/^\*\*(.+?)\*\*\s*(.*)$/);
415
- if (bold?.[2].startsWith('(')) {
416
- const value = extractParenGroup(bold[2]);
417
- if (value !== null) {
418
- const after = bold[2].slice(value.length + 2).trimStart();
419
- if (after.startsWith(':')) {
420
- return buildColor(bold[1], value, after.slice(1).trim());
421
- }
422
- }
423
- }
424
-
425
- // Case 2 (Stitch): **Name (values):** description — value embedded in bold.
426
- const stitch = text.match(/^\*\*([^*]+?)\s*\(([^)]+)\):\*\*\s*(.*)$/);
427
- if (stitch) {
428
- return buildColor(stitch[1].trim(), stitch[2], stitch[3]);
429
- }
430
-
431
- // Case 3: bullet without bold, just hex/oklch inside.
432
- const values = collectColorValues(text);
433
- if (values.length) {
434
- return buildColor(null, values.join(' to '), text);
435
- }
436
- return null;
437
- }
438
-
439
- function extractParenGroup(s) {
440
- if (s[0] !== '(') return null;
441
- let depth = 0;
442
- for (let i = 0; i < s.length; i++) {
443
- if (s[i] === '(') depth++;
444
- else if (s[i] === ')') {
445
- depth--;
446
- if (depth === 0) return s.slice(1, i);
447
- }
448
- }
449
- return null;
450
- }
451
-
452
- function buildColor(name, rawValue, description) {
453
- const values = collectColorValues(rawValue);
454
- const primary = values[0] ?? rawValue.trim();
455
- return {
456
- name: name ? stripBold(name).trim() : null,
457
- value: primary,
458
- valueRange: values.length > 1 ? values : null,
459
- format: detectFormat(primary),
460
- description: stripBold(description || '').trim() || null,
461
- };
462
- }
463
-
464
- function collectColorValues(s) {
465
- const out = [];
466
- s.replace(HEX_RE, (v) => {
467
- out.push(v);
468
- return v;
469
- });
470
- s.replace(OKLCH_RE, (v) => {
471
- out.push(v);
472
- return v;
473
- });
474
- return out;
475
- }
476
-
477
- function detectFormat(v) {
478
- if (!v) return 'unknown';
479
- if (v.startsWith('#')) return 'hex';
480
- if (/^oklch/i.test(v)) return 'oklch';
481
- if (/^rgb/i.test(v)) return 'rgb';
482
- return 'unknown';
483
- }
484
-
485
- function extractTypography(section) {
486
- if (!section) return null;
487
- const text = section.lines.join('\n');
488
-
489
- const fonts = {};
490
- // Pattern A: **Display Font:** Family (with fallback)
491
- const fontLineRe = /\*\*([\w\s/]+?)Font:\*\*\s*([^\n(]+?)(?:\s*\(with\s+([^)]+)\))?\s*$/gm;
492
- let fm;
493
- while ((fm = fontLineRe.exec(text)) !== null) {
494
- const rawRole = fm[1].trim().toLowerCase().replace(/\s+/g, '-');
495
- const role = normalizeFontRole(rawRole) || 'display';
496
- fonts[role] = {
497
- family: fm[2].trim(),
498
- fallback: fm[3] ? fm[3].trim() : null,
499
- };
500
- }
501
-
502
- // Pattern B (Stitch): * **Display & Headlines (Noto Serif):** description
503
- if (Object.keys(fonts).length === 0) {
504
- const stitchRe = /\*\*([\w\s&/]+?)\s*\(([^)]+)\):\*\*\s*(.+)/g;
505
- let sm;
506
- while ((sm = stitchRe.exec(text)) !== null) {
507
- const rawRole = sm[1]
508
- .trim()
509
- .toLowerCase()
510
- .replace(/\s*&\s*/g, '-')
511
- .replace(/\s+/g, '-');
512
- const role = normalizeFontRole(rawRole) || rawRole;
513
- fonts[role] = { family: sm[2].trim(), fallback: null, purpose: sm[3].trim() };
514
- }
515
- }
516
-
517
- // Character paragraph — either a **Character:** label, or fall back to the
518
- // first free paragraph under the section header (Stitch style).
519
- const characterMatch = text.match(/\*\*Character:\*\*\s*([^\n]+(?:\n[^\n]+)*?)(?=\n\n|\n###|\n##|$)/);
520
- let character = characterMatch ? characterMatch[1].replace(/\n/g, ' ').trim() : null;
521
- if (!character) {
522
- const paragraphs = collectParagraphs(section.lines).filter(
523
- (p) => !/^\*\*[\w\s/&]+Font/i.test(p) && !/^\*\*[\w\s/&]+\([^)]+\)/.test(p)
524
- );
525
- if (paragraphs.length) character = paragraphs[0];
526
- }
527
-
528
- // Hierarchy bullets under ### Hierarchy
529
- const subs = splitSubsections(section.lines);
530
- let hierarchy = [];
531
- const hierSub = subs.find((s) => s.name && /hierarch/i.test(s.name));
532
- if (hierSub) {
533
- const bullets = collectBullets(hierSub.lines);
534
- hierarchy = bullets.map(parseTypeBullet).filter(Boolean);
535
- }
536
-
537
- return {
538
- subtitle: section.subtitle,
539
- fonts,
540
- character,
541
- hierarchy,
542
- rules: extractNamedRules(section.lines),
543
- };
544
- }
545
-
546
- function normalizeFontRole(raw) {
547
- // Canonical roles the panel cares about: display, body, label, mono.
548
- // Stitch often writes compound roles like "display-&-headlines" or "ui-&-body"
549
- // — collapse them to the first canonical role present.
550
- const tokens = raw.split(/[-/&\s]+/).filter(Boolean);
551
- const priority = ['display', 'headline', 'body', 'ui', 'label', 'mono'];
552
- const canonical = { headline: 'display', ui: 'body' };
553
- for (const p of priority) {
554
- if (tokens.includes(p)) return canonical[p] || p;
555
- }
556
- return null;
557
- }
558
-
559
- function parseTypeBullet(bullet) {
560
- // - **Display** (family, weight 300, italic, clamp(...), line-height 1): purpose
561
- const m = bullet.match(/^\*\*(.+?)\*\*\s*\(([^)]+)\):\s*(.*)$/);
562
- if (!m) return null;
563
- const name = m[1].trim();
564
- const specs = m[2].split(',').map((s) => s.trim());
565
- return {
566
- name,
567
- specs,
568
- purpose: stripBold(m[3] || '').trim() || null,
569
- };
570
- }
571
-
572
- function extractElevation(section) {
573
- if (!section) return null;
574
- const subs = splitSubsections(section.lines);
575
-
576
- const description = collectParagraphs(subs[0].lines).join(' ') || null;
577
-
578
- const shadows = [];
579
- const seen = new Set();
580
- const dedupe = (entry) => {
581
- const key = `${entry.name || ''}::${entry.value}`;
582
- if (seen.has(key)) return;
583
- seen.add(key);
584
- shadows.push(entry);
585
- };
586
-
587
- for (const b of collectBullets(section.lines)) {
588
- const parsed = parseShadowBullet(b);
589
- if (parsed) dedupe(parsed);
590
- }
591
-
592
- // Fallback: extract shadows written inline in prose. Stitch style is
593
- // "...use an extra-diffused shadow: `box-shadow: 0 12px 40px rgba(...)`."
594
- for (const p of collectParagraphs(section.lines)) {
595
- for (const inline of extractInlineShadows(p)) dedupe(inline);
596
- }
597
- for (const b of collectBullets(section.lines)) {
598
- for (const inline of extractInlineShadows(b)) dedupe(inline);
599
- }
600
-
601
- return {
602
- subtitle: section.subtitle,
603
- description,
604
- shadows,
605
- rules: extractNamedRules(section.lines),
606
- };
607
- }
608
-
609
- function extractInlineShadows(text) {
610
- // Find `box-shadow: ...` anywhere in prose and capture the value. Work on the
611
- // raw string so it handles both backtick-fenced and unfenced variants.
612
- const out = [];
613
- const re = /box-shadow\s*:\s*([^`;\n]+)/gi;
614
- let m;
615
- while ((m = re.exec(text)) !== null) {
616
- const value = m[1].replace(/[`.)]+$/, '').trim();
617
- if (!value) continue;
618
- // Name heuristic: the noun immediately before the shadow phrase.
619
- // e.g. "an extra-diffused shadow: ..." -> "extra-diffused shadow"
620
- const before = text.slice(0, m.index);
621
- const nameMatch = before.match(/\b([A-Za-z][A-Za-z\- ]{2,40})\s+shadow\b[^A-Za-z0-9]*$/i);
622
- let name = null;
623
- if (nameMatch) {
624
- const stripped = nameMatch[1]
625
- .replace(/^(?:use|using|apply|applying|is|are|looks? like)\s+/i, '')
626
- .replace(/^(?:a|an|the)\s+/i, '')
627
- .trim();
628
- if (stripped) {
629
- name =
630
- `${stripped.charAt(0).toUpperCase() + stripped.slice(1)} shadow`;
631
- }
632
- }
633
- out.push({
634
- name,
635
- value,
636
- purpose: null,
637
- });
638
- }
639
- return out;
640
- }
641
-
642
- function parseShadowBullet(bullet) {
643
- // - **Name** (`box-shadow: value`): purpose
644
- // - **Name** (`value`): purpose
645
- // Only accept if the paren content looks like a shadow value (contains px,
646
- // rem, rgba, or box-shadow). This filters out `**Rule Name:**` bullets.
647
- const m = bullet.match(/^\*\*(.+?)\*\*\s*\(`?([^`]+?)`?\):\s*(.*)$/);
648
- if (!m) return null;
649
- const rawValue = m[2].replace(/^box-shadow:\s*/i, '').trim();
650
- const looksLikeShadow =
651
- /box-shadow|rgba?\(|\bpx\b|\brem\b|^-?\d+\s/i.test(rawValue) &&
652
- /\d/.test(rawValue);
653
- if (!looksLikeShadow) return null;
654
- const name = stripBold(m[1]).trim();
655
- return {
656
- name,
657
- value: rawValue,
658
- purpose: stripBold(m[3] || '').trim() || null,
659
- };
660
- }
661
-
662
- function extractComponents(section) {
663
- if (!section) return null;
664
- const subs = splitSubsections(section.lines);
665
- const components = [];
666
-
667
- for (const sub of subs.slice(1)) {
668
- if (!sub.name) continue;
669
-
670
- const bullets = collectBullets(sub.lines);
671
- const paragraphs = collectParagraphs(sub.lines);
672
-
673
- const variants = [];
674
- const properties = {};
675
-
676
- for (const b of bullets) {
677
- // - **Key:** value
678
- const m = b.match(/^\*\*(.+?):?\*\*:?\s*(.+)$/);
679
- if (m) {
680
- const key = stripBold(m[1]).trim();
681
- const value = stripBold(m[2]).trim();
682
- // Heuristic: "Primary", "Secondary", "Hover", "Focus" etc are variants;
683
- // "Shape", "Background", "Padding" are properties.
684
- if (/^(primary|secondary|tertiary|ghost|hover|focus|active|disabled|default|error|selected|unselected|state)$/i.test(key.split(/[\s/]/)[0])) {
685
- variants.push({ name: key, description: value });
686
- } else {
687
- properties[key.toLowerCase()] = value;
688
- }
689
- }
690
- }
691
-
692
- components.push({
693
- name: sub.name,
694
- description: paragraphs.join(' ') || null,
695
- properties,
696
- variants,
697
- });
698
- }
699
-
700
- return {
701
- subtitle: section.subtitle,
702
- components,
703
- };
704
- }
705
-
706
- function extractDosDonts(section) {
707
- if (!section) return null;
708
- const subs = splitSubsections(section.lines);
709
- const dos = [];
710
- const donts = [];
711
-
712
- for (const sub of subs.slice(1)) {
713
- if (!sub.name) continue;
714
- const subName = normalizeApostrophes(sub.name);
715
- const bullets = collectBullets(sub.lines).map((b) => stripBold(b).trim());
716
- if (/^do'?t?:?$/i.test(subName) || /^do:?$/i.test(subName)) {
717
- dos.push(...bullets);
718
- } else if (/^don'?t:?$/i.test(subName)) {
719
- donts.push(...bullets);
720
- }
721
- }
722
-
723
- // Classify by bullet prefix as a backup (catches loose bullets outside H3 wrappers)
724
- for (const b of collectBullets(section.lines)) {
725
- const stripped = normalizeApostrophes(stripBold(b).trim());
726
- if (/^don'?t\b/i.test(stripped)) {
727
- if (!donts.some((d) => normalizeApostrophes(d) === stripped)) donts.push(stripped);
728
- } else if (/^do\b/i.test(stripped)) {
729
- if (!dos.some((d) => normalizeApostrophes(d) === stripped)) dos.push(stripped);
730
- }
731
- }
732
-
733
- return { dos, donts };
734
- }
735
-
736
- // ---------- Coverage assessment ----------
737
-
738
- function assessCoverage(model) {
739
- const report = {};
740
-
741
- report.overview = model.overview
742
- ? {
743
- northStar: Boolean(model.overview.creativeNorthStar),
744
- philosophy: model.overview.philosophy.length > 0,
745
- keyCharacteristics: model.overview.keyCharacteristics.length,
746
- }
747
- : 'missing';
748
-
749
- report.colors = model.colors
750
- ? {
751
- groups: model.colors.groups.length,
752
- totalColors: model.colors.groups.reduce((n, g) => n + g.colors.length, 0),
753
- rules: model.colors.rules.length,
754
- }
755
- : 'missing';
756
-
757
- report.typography = model.typography
758
- ? {
759
- fonts: Object.keys(model.typography.fonts).length,
760
- hierarchyEntries: model.typography.hierarchy.length,
761
- character: Boolean(model.typography.character),
762
- rules: model.typography.rules.length,
763
- }
764
- : 'missing';
765
-
766
- report.elevation = model.elevation
767
- ? {
768
- shadows: model.elevation.shadows.length,
769
- rules: model.elevation.rules.length,
770
- description: Boolean(model.elevation.description),
771
- }
772
- : 'missing';
773
-
774
- report.components = model.components
775
- ? {
776
- count: model.components.components.length,
777
- variantTotal: model.components.components.reduce((n, c) => n + c.variants.length, 0),
778
- }
779
- : 'missing';
780
-
781
- report.dosDonts = model.dosDonts
782
- ? {
783
- dos: model.dosDonts.dos.length,
784
- donts: model.dosDonts.donts.length,
785
- }
786
- : 'missing';
787
-
788
- return report;
789
- }
790
-
791
- // ---------- Main ----------
792
-
793
- export function parseDesignMd(md) {
794
- const { frontmatter, body } = parseFrontmatter(md);
795
- const { title, sections } = splitSections(body);
796
- return {
797
- schemaVersion: 2,
798
- title,
799
- frontmatter,
800
- overview: extractOverview(sections.Overview),
801
- colors: extractColors(sections.Colors),
802
- typography: extractTypography(sections.Typography),
803
- elevation: extractElevation(sections.Elevation),
804
- components: extractComponents(sections.Components),
805
- dosDonts: extractDosDonts(sections["Do's and Don'ts"]),
806
- };
807
- }
808
-
809
- export { assessCoverage };