@hegemonart/get-design-done 1.52.0 → 1.54.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 (60) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +90 -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/design-context-reviewer-gate.md +102 -0
  8. package/agents/design-context-reviewer.md +186 -0
  9. package/agents/motion-mapper.md +1 -0
  10. package/agents/token-mapper.md +3 -0
  11. package/dist/claude-code/.claude/skills/discover/SKILL.md +7 -1
  12. package/dist/claude-code/.claude/skills/explore/SKILL.md +3 -1
  13. package/dist/claude-code/.claude/skills/new-addendum/SKILL.md +81 -0
  14. package/package.json +1 -1
  15. package/reference/frameworks/astro.md +43 -0
  16. package/reference/frameworks/nextjs.md +44 -0
  17. package/reference/frameworks/remix.md +44 -0
  18. package/reference/frameworks/storybook.md +44 -0
  19. package/reference/frameworks/sveltekit.md +43 -0
  20. package/reference/frameworks/vite-react.md +43 -0
  21. package/reference/interaction.md +1 -0
  22. package/reference/motion/framer-motion.md +45 -0
  23. package/reference/motion/gsap.md +45 -0
  24. package/reference/motion/motion-one.md +44 -0
  25. package/reference/motion/react-spring.md +44 -0
  26. package/reference/motion.md +1 -0
  27. package/reference/registry.json +163 -1
  28. package/reference/registry.schema.json +18 -1
  29. package/reference/skill-graph.md +2 -1
  30. package/reference/systems/chakra.md +44 -0
  31. package/reference/systems/css-modules.md +44 -0
  32. package/reference/systems/mui.md +44 -0
  33. package/reference/systems/radix-themes.md +43 -0
  34. package/reference/systems/shadcn.md +45 -0
  35. package/reference/systems/styled-components.md +44 -0
  36. package/reference/systems/tailwind.md +44 -0
  37. package/reference/systems/vanilla-extract.md +44 -0
  38. package/scripts/lib/detect/stack.cjs +455 -0
  39. package/scripts/lib/detect/stack.d.cts +44 -0
  40. package/scripts/lib/explore-parallel-runner/index.ts +196 -1
  41. package/scripts/lib/explore-parallel-runner/types.ts +85 -0
  42. package/scripts/lib/health-mirror/index.cjs +73 -1
  43. package/scripts/lib/manifest/skills.json +10 -2
  44. package/scripts/lib/mapper-spawn.cjs +257 -0
  45. package/scripts/lib/mapper-spawn.d.cts +60 -0
  46. package/scripts/lib/mappers/compute-batches.mjs +625 -0
  47. package/scripts/lib/mappers/graph-adjacency.mjs +129 -0
  48. package/scripts/lib/mappers/incremental-discover.cjs +617 -0
  49. package/scripts/lib/mappers/incremental-discover.d.cts +133 -0
  50. package/scripts/lib/mappers/neighbor-map.mjs +0 -0
  51. package/scripts/lib/new-addendum.cjs +204 -0
  52. package/sdk/cli/index.js +1504 -3
  53. package/sdk/fingerprint/classify.cjs +406 -0
  54. package/sdk/fingerprint/index.ts +405 -0
  55. package/sdk/fingerprint/store.cjs +523 -0
  56. package/sdk/index.ts +1 -0
  57. package/sdk/mcp/gdd-mcp/server.js +1047 -0
  58. package/skills/discover/SKILL.md +7 -1
  59. package/skills/explore/SKILL.md +3 -1
  60. package/skills/new-addendum/SKILL.md +81 -0
@@ -0,0 +1,257 @@
1
+ 'use strict';
2
+ // scripts/lib/mapper-spawn.cjs - Phase 54 (Composable Reference Addendums), executor B (COMP-01).
3
+ //
4
+ // Pre-spawn composition step for the explore mappers. Given a mapper name + a
5
+ // detected stack fingerprint, select the matching stack-addendum registry
6
+ // entries, read their bodies from the reference dir, and concat them into a
7
+ // single "## Stack-specific guidance" block that the runner appends to
8
+ // spec.prompt BEFORE spawnMapper (the agent bodies are NOT edited; addendums
9
+ // ride in spec.prompt).
10
+ //
11
+ // Design notes:
12
+ // * PURE w.r.t. its inputs: takes a stack OBJECT (does NOT import
13
+ // detect/stack.cjs - independent of executor A) and an explicit registry
14
+ // object + refDir. Reads addendum file bodies via node:fs only.
15
+ // * DEP-FREE (node:fs + node:path only) and NEVER throws. An absent
16
+ // registry, an entry with no readable file, or a malformed stack all
17
+ // degrade to an empty block; detected-but-unmatched stacks land in
18
+ // `missing` so the runner can raise the fallback flag (R6).
19
+ // * CAP 3 per spawn: at most one DS + one framework + one motion addendum.
20
+ // A 4th category (or a second entry in the same category) is ignored.
21
+ //
22
+ // Matching rule (so executor F wires it + executors C/D/E name addendums
23
+ // consistently): each stack-addendum entry resolves to a {category, key}:
24
+ // - category: explicit entry.kind / entry.category if present
25
+ // ('system'|'framework'|'motion'), else inferred from the path dir
26
+ // (reference/systems/* -> 'system', reference/frameworks/* ->
27
+ // 'framework', reference/motion/* -> 'motion').
28
+ // - key: explicit entry.stack if present, else the path basename without
29
+ // '.md', else the trailing '-'-segment of entry.name. Compared
30
+ // case-insensitively.
31
+ // The detected stack supplies the values to match:
32
+ // stack.ds matches a 'system' addendum whose key === ds
33
+ // stack.framework matches a 'framework' addendum whose key === framework
34
+ // stack.motion_libs matches a 'motion' addendum whose key is in the list
35
+ // The first matching entry per (category, value) wins; later duplicates are
36
+ // skipped. This keeps the cap at 1+1+1 = 3 without any category priority math.
37
+
38
+ const fs = require('node:fs');
39
+ const path = require('node:path');
40
+
41
+ const BLOCK_HEADER = '## Stack-specific guidance';
42
+ const ADDENDUM_SEPARATOR = '\n\n---\n\n';
43
+
44
+ // Map a detected-stack field onto the addendum category it selects from.
45
+ // Order is the canonical 1 DS + 1 framework + 1 motion fill order.
46
+ const CATEGORY_ORDER = ['system', 'framework', 'motion'];
47
+
48
+ /** Lowercase + trim a value to a comparable key, or '' for non-strings. */
49
+ function normKey(value) {
50
+ return typeof value === 'string' ? value.trim().toLowerCase() : '';
51
+ }
52
+
53
+ /** Path basename without a trailing `.md` (forward-slash + back-slash safe). */
54
+ function baseNameNoExt(p) {
55
+ if (typeof p !== 'string' || p.length === 0) return '';
56
+ const tail = p.replace(/\\/g, '/').split('/').pop() || '';
57
+ return tail.replace(/\.md$/i, '');
58
+ }
59
+
60
+ /**
61
+ * Classify a stack-addendum registry entry into { category, key }.
62
+ * `category` is one of CATEGORY_ORDER or null when it cannot be determined;
63
+ * `key` is the normalized stack identifier ('' when absent).
64
+ */
65
+ function classifyEntry(entry) {
66
+ // Category: explicit kind/category wins, else infer from the path directory.
67
+ let category = null;
68
+ const explicitKind = normKey(entry.kind || entry.category);
69
+ if (explicitKind === 'system' || explicitKind === 'ds' || explicitKind === 'design-system') {
70
+ category = 'system';
71
+ } else if (explicitKind === 'framework') {
72
+ category = 'framework';
73
+ } else if (explicitKind === 'motion') {
74
+ category = 'motion';
75
+ } else if (typeof entry.path === 'string') {
76
+ const p = entry.path.replace(/\\/g, '/');
77
+ if (/(^|\/)reference\/systems\//i.test(p) || /(^|\/)systems\//i.test(p)) category = 'system';
78
+ else if (/(^|\/)reference\/frameworks\//i.test(p) || /(^|\/)frameworks\//i.test(p)) category = 'framework';
79
+ else if (/(^|\/)reference\/motion\//i.test(p) || /(^|\/)motion\//i.test(p)) category = 'motion';
80
+ }
81
+
82
+ // Key: explicit `stack` field wins, else path basename, else name tail.
83
+ let key = normKey(entry.stack);
84
+ if (key === '') key = normKey(baseNameNoExt(entry.path));
85
+ if (key === '') {
86
+ // Fall back to the trailing '-'-segment of the entry name
87
+ // (e.g. "addendum-system-tailwind" -> "tailwind").
88
+ const nameParts = normKey(entry.name).split('-').filter(Boolean);
89
+ key = nameParts.length > 0 ? nameParts[nameParts.length - 1] : '';
90
+ }
91
+
92
+ return { category, key };
93
+ }
94
+
95
+ /** True when `entry` is a stack-addendum that composes into `mapperName`. */
96
+ function composesInto(entry, mapperName) {
97
+ if (!entry || entry.type !== 'stack-addendum') return false;
98
+ const list = entry.composes_into;
99
+ if (!Array.isArray(list)) return false;
100
+ return list.some((m) => normKey(m) === normKey(mapperName));
101
+ }
102
+
103
+ /**
104
+ * Read an addendum body from disk. Returns the trimmed file contents, or null
105
+ * when the file is missing / unreadable / empty. `entry.path` is resolved
106
+ * relative to `refDir` when it is not already absolute; a leading
107
+ * `reference/` segment is tolerated so registry paths (which are repo-root
108
+ * relative, e.g. "reference/systems/tailwind.md") resolve against a refDir
109
+ * that already points at the reference dir.
110
+ */
111
+ function readAddendumBody(entry, refDir) {
112
+ if (typeof entry.path !== 'string' || entry.path.length === 0) return null;
113
+ const rel = entry.path.replace(/\\/g, '/');
114
+ const candidates = [];
115
+ if (path.isAbsolute(rel)) {
116
+ candidates.push(rel);
117
+ } else {
118
+ candidates.push(path.resolve(refDir, rel));
119
+ // refDir may itself be the `reference/` dir; strip a redundant leading
120
+ // `reference/` so "reference/systems/x.md" still resolves.
121
+ const stripped = rel.replace(/^reference\//i, '');
122
+ if (stripped !== rel) candidates.push(path.resolve(refDir, stripped));
123
+ }
124
+ for (const abs of candidates) {
125
+ let body;
126
+ try {
127
+ body = fs.readFileSync(abs, 'utf8');
128
+ } catch {
129
+ continue;
130
+ }
131
+ const trimmed = body.replace(/\s+$/, '').replace(/^/, '');
132
+ if (trimmed.trim().length > 0) return trimmed;
133
+ }
134
+ return null;
135
+ }
136
+
137
+ /**
138
+ * Compose the stack-specific guidance block for one mapper.
139
+ *
140
+ * @param {string} mapperName the mapper the addendums must compose into
141
+ * (e.g. "token-mapper").
142
+ * @param {{ds?: string|null, framework?: string|null, motion_libs?: string[]}} stack
143
+ * the detected stack fingerprint (executor A shape).
144
+ * Null / undefined / {} -> empty block.
145
+ * @param {{registry?: object, refDir?: string, cap?: number}} [opts]
146
+ * - registry: the parsed reference/registry.json object ({ entries: [] }).
147
+ * - refDir: directory addendum `path`s resolve against (repo root or
148
+ * the reference/ dir).
149
+ * - cap: max addendums in the block (default 3).
150
+ * @returns {{block: string, used: string[], missing: string[]}}
151
+ * - block: "## Stack-specific guidance" text (incl. trailing bodies),
152
+ * or '' when nothing matched.
153
+ * - used: names (or path basenames) of the addendums included, in
154
+ * system -> framework -> motion order.
155
+ * - missing: detected stack values that had NO matching addendum, in the
156
+ * same order (drives the fallback flag).
157
+ */
158
+ function composeAddendums(mapperName, stack, opts) {
159
+ const used = [];
160
+ const missing = [];
161
+ const empty = () => ({ block: '', used, missing });
162
+
163
+ const o = opts || {};
164
+ const cap = Number.isInteger(o.cap) && o.cap >= 0 ? o.cap : 3;
165
+ const refDir = typeof o.refDir === 'string' && o.refDir.length > 0 ? o.refDir : process.cwd();
166
+
167
+ if (!stack || typeof stack !== 'object' || cap === 0) return empty();
168
+
169
+ const registry = o.registry;
170
+ const entries = registry && Array.isArray(registry.entries) ? registry.entries : [];
171
+
172
+ // The detected value we want to match, per category.
173
+ const detected = {
174
+ system: normKey(stack.ds),
175
+ framework: normKey(stack.framework),
176
+ // motion is a list; take the first non-empty entry (cap allows only one
177
+ // motion addendum, so the leading detected lib wins).
178
+ motion: Array.isArray(stack.motion_libs)
179
+ ? normKey(stack.motion_libs.find((m) => normKey(m) !== ''))
180
+ : '',
181
+ };
182
+
183
+ // Pre-classify candidate entries (only those composing into this mapper).
184
+ const candidates = [];
185
+ for (const entry of entries) {
186
+ if (!composesInto(entry, mapperName)) continue;
187
+ const { category, key } = classifyEntry(entry);
188
+ if (category === null || key === '') continue;
189
+ candidates.push({ entry, category, key });
190
+ }
191
+
192
+ const bodies = [];
193
+ for (const category of CATEGORY_ORDER) {
194
+ if (used.length >= cap) break;
195
+ const want = detected[category];
196
+ if (want === '') continue; // nothing detected in this category
197
+
198
+ const hit = candidates.find((c) => c.category === category && c.key === want);
199
+ if (!hit) {
200
+ // Detected this stack but no addendum registered for it -> fallback flag.
201
+ missing.push(want);
202
+ continue;
203
+ }
204
+ const body = readAddendumBody(hit.entry, refDir);
205
+ if (body === null) {
206
+ // Entry exists but the file is missing/empty: treat as no coverage.
207
+ missing.push(want);
208
+ continue;
209
+ }
210
+ bodies.push(body);
211
+ used.push(typeof hit.entry.name === 'string' && hit.entry.name.length > 0
212
+ ? hit.entry.name
213
+ : hit.key);
214
+ }
215
+
216
+ if (bodies.length === 0) return empty();
217
+
218
+ const block = `${BLOCK_HEADER}\n\n${bodies.join(ADDENDUM_SEPARATOR)}`;
219
+ return { block, used, missing };
220
+ }
221
+
222
+ /**
223
+ * Pre-spawn mutation helper for the explore runner. Composes the addendum
224
+ * block for `spec.name` and, when non-empty, APPENDS it to `spec.prompt`
225
+ * (separated by a blank line). Returns the same `spec` object (mutated in
226
+ * place) plus the compose metadata so the caller can surface `missing`.
227
+ *
228
+ * Backward-compatible + additive: an empty block leaves `spec.prompt`
229
+ * byte-for-byte unchanged. Never throws: a malformed spec returns unchanged
230
+ * with empty metadata.
231
+ *
232
+ * @param {{name?: string, prompt?: string}} spec a MapperSpec-shaped object.
233
+ * @param {object} stack detected stack (see composeAddendums).
234
+ * @param {object} [opts] registry/refDir/cap (see composeAddendums).
235
+ * @returns {{spec: object, block: string, used: string[], missing: string[]}}
236
+ */
237
+ function applyAddendums(spec, stack, opts) {
238
+ if (!spec || typeof spec !== 'object') {
239
+ return { spec, block: '', used: [], missing: [] };
240
+ }
241
+ const mapperName = typeof spec.name === 'string' ? spec.name : '';
242
+ const { block, used, missing } = composeAddendums(mapperName, stack, opts);
243
+ if (block !== '') {
244
+ const base = typeof spec.prompt === 'string' ? spec.prompt : '';
245
+ spec.prompt = base === '' ? block : `${base}\n\n${block}`;
246
+ }
247
+ return { spec, block, used, missing };
248
+ }
249
+
250
+ module.exports = {
251
+ composeAddendums,
252
+ applyAddendums,
253
+ // Exported for unit-level coverage + reuse by the runner wiring (executor F).
254
+ classifyEntry,
255
+ composesInto,
256
+ BLOCK_HEADER,
257
+ };
@@ -0,0 +1,60 @@
1
+ // scripts/lib/mapper-spawn.d.cts — types for mapper-spawn.cjs (Phase 54 COMP-01).
2
+
3
+ export interface DetectedStackInput {
4
+ ds?: string | null;
5
+ framework?: string | null;
6
+ motion_libs?: string[];
7
+ }
8
+
9
+ export interface ComposeAddendumsOptions {
10
+ /** Parsed reference/registry.json object ({ entries: [...] }). */
11
+ registry?: unknown;
12
+ /** Directory addendum `path`s resolve against (repo root or reference/). */
13
+ refDir?: string;
14
+ /** Max addendums in the block (default 3 = 1 system + 1 framework + 1 motion). */
15
+ cap?: number;
16
+ }
17
+
18
+ export interface ComposeAddendumsResult {
19
+ /** "## Stack-specific guidance" block text, or '' when nothing matched. */
20
+ block: string;
21
+ /** Names (or path basenames) of the addendums included. */
22
+ used: string[];
23
+ /** Detected stack values that had NO matching addendum (fallback flag). */
24
+ missing: string[];
25
+ }
26
+
27
+ export interface ApplyAddendumsResult extends ComposeAddendumsResult {
28
+ /** The (possibly mutated) spec object passed in. */
29
+ spec: unknown;
30
+ }
31
+
32
+ export interface ClassifiedEntry {
33
+ category: 'system' | 'framework' | 'motion' | null;
34
+ key: string;
35
+ }
36
+
37
+ /**
38
+ * Compose the stack-specific guidance block for one mapper. Pure (reads files);
39
+ * NEVER throws. Detected-but-unmatched stack values land in `missing`.
40
+ */
41
+ export function composeAddendums(
42
+ mapperName: string,
43
+ stack: DetectedStackInput | null | undefined,
44
+ opts?: ComposeAddendumsOptions,
45
+ ): ComposeAddendumsResult;
46
+
47
+ /**
48
+ * Pre-spawn helper: composes the block for `spec.name` and APPENDS it to
49
+ * `spec.prompt` (mutated in place) when non-empty. Backward-compatible: an
50
+ * empty block leaves `spec.prompt` byte-for-byte unchanged. NEVER throws.
51
+ */
52
+ export function applyAddendums(
53
+ spec: { name?: string; prompt?: string } | null | undefined,
54
+ stack: DetectedStackInput | null | undefined,
55
+ opts?: ComposeAddendumsOptions,
56
+ ): ApplyAddendumsResult;
57
+
58
+ export function classifyEntry(entry: Record<string, unknown>): ClassifiedEntry;
59
+ export function composesInto(entry: Record<string, unknown>, mapperName: string): boolean;
60
+ export const BLOCK_HEADER: string;