@hegemonart/get-design-done 1.53.0 → 1.55.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 (56) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +88 -0
  4. package/README.md +4 -0
  5. package/SKILL.md +2 -1
  6. package/agents/component-taxonomy-mapper.md +3 -0
  7. package/agents/motion-mapper.md +1 -0
  8. package/agents/token-mapper.md +3 -0
  9. package/bin/gdd-dashboard +91 -0
  10. package/dist/claude-code/.claude/skills/new-addendum/SKILL.md +81 -0
  11. package/package.json +2 -1
  12. package/reference/frameworks/astro.md +43 -0
  13. package/reference/frameworks/nextjs.md +44 -0
  14. package/reference/frameworks/remix.md +44 -0
  15. package/reference/frameworks/storybook.md +44 -0
  16. package/reference/frameworks/sveltekit.md +43 -0
  17. package/reference/frameworks/vite-react.md +43 -0
  18. package/reference/interaction.md +1 -0
  19. package/reference/motion/framer-motion.md +45 -0
  20. package/reference/motion/gsap.md +45 -0
  21. package/reference/motion/motion-one.md +44 -0
  22. package/reference/motion/react-spring.md +44 -0
  23. package/reference/motion.md +1 -0
  24. package/reference/registry.json +163 -1
  25. package/reference/registry.schema.json +18 -1
  26. package/reference/skill-graph.md +2 -1
  27. package/reference/systems/chakra.md +44 -0
  28. package/reference/systems/css-modules.md +44 -0
  29. package/reference/systems/mui.md +44 -0
  30. package/reference/systems/radix-themes.md +43 -0
  31. package/reference/systems/shadcn.md +45 -0
  32. package/reference/systems/styled-components.md +44 -0
  33. package/reference/systems/tailwind.md +44 -0
  34. package/reference/systems/vanilla-extract.md +44 -0
  35. package/scripts/lib/dashboard/graph-html.cjs +0 -0
  36. package/scripts/lib/detect/stack.cjs +455 -0
  37. package/scripts/lib/detect/stack.d.cts +44 -0
  38. package/scripts/lib/explore-parallel-runner/index.ts +138 -1
  39. package/scripts/lib/explore-parallel-runner/types.ts +27 -0
  40. package/scripts/lib/health-mirror/index.cjs +218 -1
  41. package/scripts/lib/manifest/skills.json +8 -0
  42. package/scripts/lib/mapper-spawn.cjs +257 -0
  43. package/scripts/lib/mapper-spawn.d.cts +60 -0
  44. package/scripts/lib/new-addendum.cjs +204 -0
  45. package/sdk/cli/commands/dashboard.ts +419 -0
  46. package/sdk/cli/index.js +1388 -3
  47. package/sdk/cli/index.ts +7 -0
  48. package/sdk/dashboard/data/_pkg-root.cjs +92 -0
  49. package/sdk/dashboard/data/cost-aggregator.cjs +187 -0
  50. package/sdk/dashboard/data/discovery.cjs +297 -0
  51. package/sdk/dashboard/data/risk-surface.cjs +136 -0
  52. package/sdk/dashboard/data/source.cjs +576 -0
  53. package/sdk/dashboard/tui/ansi.cjs +355 -0
  54. package/sdk/dashboard/tui/index.cjs +778 -0
  55. package/sdk/mcp/gdd-mcp/server.js +1117 -0
  56. package/skills/new-addendum/SKILL.md +81 -0
@@ -0,0 +1,355 @@
1
+ 'use strict';
2
+ /**
3
+ * sdk/dashboard/tui/ansi.cjs — Phase 55 (GDD Dashboard, DEP-FREE), TUI-01 substrate.
4
+ *
5
+ * The hand-rolled terminal-render toolkit that REPLACES Ink/Yoga with ZERO new dependency
6
+ * (Node builtins only — in fact this file requires nothing). It is the layout + paint core
7
+ * the TUI main loop (sdk/dashboard/tui/index.cjs, executor D) draws its 5 panes with.
8
+ *
9
+ * Design rule: every helper here is PURE — it RETURNS a string / string[] / op[] and performs
10
+ * NO I/O. The main loop owns the single `process.stdout.write(...)`. That separation is what
11
+ * makes the whole render layer deterministically unit-testable (test/suite/phase-55-ansi.test.cjs)
12
+ * and is the heart of the dep-free / no-Yoga decision (CONTEXT D1, the ANSI render core contract).
13
+ *
14
+ * WIDTH MODEL (the subtle part). Terminals lay text out in CELLS, not bytes or UTF-16 units:
15
+ * - SGR color escapes occupy zero cells -> we strip them before measuring (`visibleWidth`).
16
+ * - A code point can be 0, 1, or 2 cells wide. We iterate by CODE POINT (for..of / spread),
17
+ * never by `.length` (UTF-16 units), so an astral char (emoji, U+1F600) is one indivisible
18
+ * unit -> we can NEVER slice a surrogate pair in half.
19
+ * - `wcwidthLite(cp)` is a compact East-Asian-Width table: the major CJK / fullwidth / wide-
20
+ * emoji ranges count as 2 columns; combining marks count as 0; everything else as 1. This
21
+ * is the floor the spec asks for (code-point counting) plus a wide-char bonus for CJK.
22
+ */
23
+
24
+ // --- ANSI control constants ------------------------------------------------
25
+ const ESC = '\x1b';
26
+ const CSI = ESC + '[';
27
+ const RESET = CSI + '0m';
28
+
29
+ // SGR matcher used to strip color codes before measuring visible width. Matches a CSI
30
+ // sequence terminated by 'm' (the SGR final byte). Kept narrow (only 'm') on purpose — we
31
+ // never emit cursor-move sequences inside content lines.
32
+ // eslint-disable-next-line no-control-regex
33
+ const SGR_RE = /\x1b\[[0-9;]*m/g;
34
+
35
+ // --- cursor / screen control (return the escape strings; no writes) --------
36
+
37
+ /** CUP — move the cursor to (row, col), 1-based, as the terminal expects. */
38
+ function cursorTo(row, col) {
39
+ return `${CSI}${Math.max(1, row | 0)};${Math.max(1, col | 0)}H`;
40
+ }
41
+
42
+ /** Erase the whole screen and home the cursor. */
43
+ function clearScreen() {
44
+ return `${CSI}2J${CSI}H`;
45
+ }
46
+
47
+ /** Erase the entire current line (cursor row), leaving the cursor where it is. */
48
+ function clearLine() {
49
+ return `${CSI}2K`;
50
+ }
51
+
52
+ /** DECTCEM — hide the cursor. */
53
+ function hideCursor() {
54
+ return `${CSI}?25l`;
55
+ }
56
+
57
+ /** DECTCEM — show the cursor. */
58
+ function showCursor() {
59
+ return `${CSI}?25h`;
60
+ }
61
+
62
+ /** Enter the alternate screen buffer (so quitting restores the user's scrollback). */
63
+ function altScreenEnter() {
64
+ return `${CSI}?1049h`;
65
+ }
66
+
67
+ /** Leave the alternate screen buffer. */
68
+ function altScreenExit() {
69
+ return `${CSI}?1049l`;
70
+ }
71
+
72
+ // --- color (SGR) -----------------------------------------------------------
73
+
74
+ // Named foreground SGR codes (the 8 base colors + their bright variants + a gray alias).
75
+ const FG = {
76
+ black: 30, red: 31, green: 32, yellow: 33, blue: 34, magenta: 35, cyan: 36, white: 37,
77
+ gray: 90, grey: 90, brightBlack: 90,
78
+ brightRed: 91, brightGreen: 92, brightYellow: 93, brightBlue: 94,
79
+ brightMagenta: 95, brightCyan: 96, brightWhite: 97,
80
+ };
81
+ // Background codes are the foreground codes + 10.
82
+ const BG = Object.fromEntries(Object.entries(FG).map(([k, v]) => [k, v + 10]));
83
+
84
+ /**
85
+ * Resolve a color spec to its SGR opener string(s).
86
+ * - integer 0-255 -> a STANDALONE 256-color sequence `\x1b[38;5;<n>m` (fg) / `\x1b[48;5;<n>m`
87
+ * (bg). Emitted on its own (not folded into a longer `;`-list): the `38;5;n` extended-color
88
+ * selector is an atomic 3-param unit and some terminals mishandle it when mixed with other
89
+ * params — keeping it standalone is the defensively-portable choice (and what the pinned
90
+ * contract asserts).
91
+ * - named string -> the table code (a bare numeric param, foldable into the attr opener).
92
+ * Returns { attrs:number[] (foldable params), seqs:string[] (standalone sequences) }.
93
+ */
94
+ function colorParts(spec, isBg) {
95
+ if (spec == null) return { attrs: [], seqs: [] };
96
+ if (typeof spec === 'number' && Number.isInteger(spec) && spec >= 0 && spec <= 255) {
97
+ return { attrs: [], seqs: [`${CSI}${isBg ? 48 : 38};5;${spec}m`] };
98
+ }
99
+ const code = (isBg ? BG : FG)[spec];
100
+ return code == null ? { attrs: [], seqs: [] } : { attrs: [code], seqs: [] };
101
+ }
102
+
103
+ /**
104
+ * Wrap `text` in an SGR opener (+ any standalone 256-color sequences) and a SINGLE trailing
105
+ * reset.
106
+ * opts: { fg?, bg?, bold?, dim?, underline?, noColor? }
107
+ * No-op (returns `text` unchanged) when opts.noColor is truthy, when process.env.NO_COLOR is
108
+ * set (the NO_COLOR convention), or when no style was requested.
109
+ */
110
+ function color(text, opts = {}) {
111
+ const s = String(text);
112
+ if (opts.noColor || process.env.NO_COLOR) return s;
113
+
114
+ const attrs = []; // foldable into one opener: 1/2/4 + named color codes
115
+ if (opts.bold) attrs.push(1);
116
+ if (opts.dim) attrs.push(2);
117
+ if (opts.underline) attrs.push(4);
118
+
119
+ const fg = colorParts(opts.fg, false);
120
+ const bg = colorParts(opts.bg, true);
121
+ attrs.push(...fg.attrs, ...bg.attrs);
122
+
123
+ const opener = attrs.length ? `${CSI}${attrs.join(';')}m` : '';
124
+ const standalone = [...fg.seqs, ...bg.seqs].join('');
125
+
126
+ if (!opener && !standalone) return s; // nothing to wrap
127
+ return `${opener}${standalone}${s}${RESET}`;
128
+ }
129
+
130
+ // --- width model -----------------------------------------------------------
131
+
132
+ /**
133
+ * wcwidth-lite: cells occupied by a single code point.
134
+ * 0 -> zero-width combining marks (so they don't inflate the measured width)
135
+ * 2 -> wide (East-Asian Wide/Fullwidth) + the common wide-emoji planes
136
+ * 1 -> everything else (the floor)
137
+ */
138
+ function wcwidthLite(cp) {
139
+ // C0/C1 controls measured as 0 (they don't advance the cursor as a glyph would).
140
+ if (cp === 0) return 0;
141
+ if (cp < 32 || (cp >= 0x7f && cp < 0xa0)) return 0;
142
+
143
+ // Combining marks / zero-width: count as 0 columns.
144
+ if (
145
+ (cp >= 0x0300 && cp <= 0x036f) || // combining diacritical marks
146
+ (cp >= 0x200b && cp <= 0x200f) || // zero-width space / joiners / marks
147
+ cp === 0xfeff || // BOM / zero-width no-break space
148
+ (cp >= 0xfe00 && cp <= 0xfe0f) // variation selectors
149
+ ) return 0;
150
+
151
+ // Wide (count as 2 columns). A compact but representative East-Asian-Width table.
152
+ if (
153
+ (cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo
154
+ (cp >= 0x2e80 && cp <= 0x303e) || // CJK radicals / Kangxi
155
+ (cp >= 0x3041 && cp <= 0x33ff) || // Hiragana, Katakana, CJK symbols
156
+ (cp >= 0x3400 && cp <= 0x4dbf) || // CJK Ext A
157
+ (cp >= 0x4e00 && cp <= 0x9fff) || // CJK Unified Ideographs
158
+ (cp >= 0xa000 && cp <= 0xa4cf) || // Yi
159
+ (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables
160
+ (cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs
161
+ (cp >= 0xfe30 && cp <= 0xfe4f) || // CJK Compatibility Forms
162
+ (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms
163
+ (cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth signs
164
+ (cp >= 0x1f300 && cp <= 0x1faff) || // emoji & pictographs (most live here)
165
+ (cp >= 0x20000 && cp <= 0x3fffd) // CJK Ext B..G (supplementary ideographic planes)
166
+ ) return 2;
167
+
168
+ return 1;
169
+ }
170
+
171
+ /** Visible column width of `s`: SGR stripped, counted per code point with wcwidthLite. */
172
+ function visibleWidth(s) {
173
+ const plain = String(s).replace(SGR_RE, '');
174
+ let w = 0;
175
+ for (const ch of plain) w += wcwidthLite(ch.codePointAt(0));
176
+ return w;
177
+ }
178
+
179
+ // --- truncate / pad (width-aware, surrogate-safe) --------------------------
180
+
181
+ const ELLIPSIS = '…'; // a single 1-column glyph
182
+
183
+ /**
184
+ * Width-aware truncation. If `s` already fits in `width` visible columns it is returned as-is.
185
+ * Otherwise it is cut to `width` columns with a trailing 1-column ellipsis. Iterates by code
186
+ * point, so a surrogate pair (astral char / emoji) is an indivisible unit and is NEVER split:
187
+ * a wide char that cannot fit alongside the ellipsis is dropped whole.
188
+ *
189
+ * NOTE: operates on the plain text (no embedded SGR). Callers that need color should color
190
+ * the already-truncated result, or color whole cells (see columns/box, which truncate plain
191
+ * content). This keeps the width math exact.
192
+ */
193
+ function truncate(s, width) {
194
+ const w = Math.max(0, width | 0);
195
+ if (w === 0) return '';
196
+ if (visibleWidth(s) <= w) return String(s);
197
+
198
+ const budget = w - 1; // reserve one column for the ellipsis
199
+ let out = '';
200
+ let used = 0;
201
+ for (const ch of String(s)) {
202
+ const cw = wcwidthLite(ch.codePointAt(0));
203
+ if (used + cw > budget) break; // a wide char that won't fit is dropped whole (surrogate-safe)
204
+ out += ch;
205
+ used += cw;
206
+ }
207
+ return out + ELLIPSIS;
208
+ }
209
+
210
+ /** Pad (or truncate) `s` to exactly `width` visible columns, spaces on the RIGHT. */
211
+ function padRight(s, width) {
212
+ const w = Math.max(0, width | 0);
213
+ const str = String(s);
214
+ const vis = visibleWidth(str);
215
+ if (vis > w) return truncate(str, w);
216
+ return str + ' '.repeat(w - vis);
217
+ }
218
+
219
+ /** Pad (or truncate) `s` to exactly `width` visible columns, spaces on the LEFT. */
220
+ function padLeft(s, width) {
221
+ const w = Math.max(0, width | 0);
222
+ const str = String(s);
223
+ const vis = visibleWidth(str);
224
+ if (vis > w) return truncate(str, w);
225
+ return ' '.repeat(w - vis) + str;
226
+ }
227
+
228
+ // --- columns (the row layout helper) ---------------------------------------
229
+
230
+ /**
231
+ * Lay `cells` out in fixed-width columns. Each cell is padded/truncated to its width via
232
+ * padRight, then joined with `sep` (default a single space). The returned row therefore has a
233
+ * deterministic, exact visible width: sum(widths) + sep*(n-1).
234
+ */
235
+ function columns(cells, widths, sep = ' ') {
236
+ const list = Array.isArray(cells) ? cells : [];
237
+ const w = Array.isArray(widths) ? widths : [];
238
+ return list.map((c, i) => padRight(c == null ? '' : c, w[i] == null ? 0 : w[i])).join(sep);
239
+ }
240
+
241
+ // --- box (the bordered-panel helper — the Yoga replacement) ----------------
242
+
243
+ const BORDERS = {
244
+ round: { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│' },
245
+ space: { tl: ' ', tr: ' ', bl: ' ', br: ' ', h: ' ', v: ' ' },
246
+ };
247
+
248
+ /**
249
+ * Render a bordered box as string[] — EVERY returned line is exactly `width` visible columns.
250
+ *
251
+ * opts: { title?, lines: string[], width, height?, border? }
252
+ * title — embedded in the top edge (truncated to fit), optional.
253
+ * lines — content rows; each is padded/truncated to the inner width (width-2).
254
+ * width — total outer width (borders included). The hard invariant.
255
+ * height — optional total row count; content is padded with blank rows or truncated.
256
+ * border — 'round' (default, rounded line-drawing) | 'space' (borderless padding).
257
+ *
258
+ * Layout: [top edge] + [content rows] + [bottom edge]. Inner content width = width - 2 (the two
259
+ * vertical borders). Deterministic and pure.
260
+ */
261
+ function box(opts = {}) {
262
+ const width = Math.max(2, opts.width | 0);
263
+ const b = BORDERS[opts.border] || BORDERS.round;
264
+ const inner = width - 2;
265
+ const lines = Array.isArray(opts.lines) ? opts.lines : [];
266
+
267
+ // Top edge with an embedded, truncated title. The title sits one cell in from the left
268
+ // corner; the remaining edge is filled with the horizontal glyph.
269
+ let top;
270
+ const rawTitle = opts.title == null ? '' : String(opts.title);
271
+ if (rawTitle && inner > 0) {
272
+ // Reserve 1 leading + at least 1 trailing horizontal cell around the title.
273
+ const titleMax = Math.max(0, inner - 2);
274
+ const title = truncate(rawTitle, titleMax); // fit to the edge; no padding
275
+ const titleW = visibleWidth(title);
276
+ const fill = inner - 1 - titleW; // 1 leading h before the title
277
+ top = b.tl + b.h + title + b.h.repeat(Math.max(0, fill)) + b.tr;
278
+ } else {
279
+ top = b.tl + b.h.repeat(inner) + b.tr;
280
+ }
281
+
282
+ // Content rows: pad/truncate each to the inner width and wrap in vertical borders.
283
+ let content = lines.map((ln) => b.v + padRight(ln == null ? '' : ln, inner) + b.v);
284
+
285
+ // Honor an explicit height (top + N content rows + bottom). Pad with blank rows or truncate.
286
+ if (opts.height != null) {
287
+ const targetContent = Math.max(0, (opts.height | 0) - 2);
288
+ if (content.length < targetContent) {
289
+ const blank = b.v + ' '.repeat(inner) + b.v;
290
+ while (content.length < targetContent) content.push(blank);
291
+ } else if (content.length > targetContent) {
292
+ content = content.slice(0, targetContent);
293
+ }
294
+ }
295
+
296
+ const bottom = b.bl + b.h.repeat(inner) + b.br;
297
+ return [top, ...content, bottom];
298
+ }
299
+
300
+ // --- diff repaint ----------------------------------------------------------
301
+
302
+ /**
303
+ * Compute the minimal set of row repaints between two frames. Returns [{row, text}] for ONLY
304
+ * the rows whose text changed (row is 1-based, matching cursorTo). Rows present in `next` but
305
+ * not `prev` are emitted (appended); rows in `prev` but gone from `next` are emitted with an
306
+ * empty `text` so the main loop can clear them. Identical frames -> []. Pure.
307
+ *
308
+ * The TUI loop applies each op as `cursorTo(row,1) + clearLine() + text`, so a steady frame
309
+ * costs zero writes and a single-cell change repaints exactly one line (no flicker).
310
+ */
311
+ function diffRender(prevLines, nextLines) {
312
+ const prev = Array.isArray(prevLines) ? prevLines : [];
313
+ const next = Array.isArray(nextLines) ? nextLines : [];
314
+ const ops = [];
315
+ const n = Math.max(prev.length, next.length);
316
+ for (let i = 0; i < n; i++) {
317
+ const before = i < prev.length ? prev[i] : undefined;
318
+ const after = i < next.length ? next[i] : undefined;
319
+ if (after === undefined) {
320
+ // Row removed in the new frame -> clear it.
321
+ ops.push({ row: i + 1, text: '' });
322
+ } else if (before !== after) {
323
+ ops.push({ row: i + 1, text: after });
324
+ }
325
+ }
326
+ return ops;
327
+ }
328
+
329
+ module.exports = {
330
+ // cursor / screen
331
+ cursorTo,
332
+ clearScreen,
333
+ clearLine,
334
+ hideCursor,
335
+ showCursor,
336
+ altScreenEnter,
337
+ altScreenExit,
338
+ // color
339
+ color,
340
+ // width
341
+ visibleWidth,
342
+ wcwidthLite,
343
+ // layout
344
+ truncate,
345
+ padRight,
346
+ padLeft,
347
+ columns,
348
+ box,
349
+ // diff repaint
350
+ diffRender,
351
+ // constants exported for executor D (so panes can compose raw escapes if needed)
352
+ ESC,
353
+ CSI,
354
+ RESET,
355
+ };