@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +90 -0
- package/README.md +4 -0
- package/SKILL.md +2 -1
- package/agents/component-taxonomy-mapper.md +3 -0
- package/agents/design-context-reviewer-gate.md +102 -0
- package/agents/design-context-reviewer.md +186 -0
- package/agents/motion-mapper.md +1 -0
- package/agents/token-mapper.md +3 -0
- package/dist/claude-code/.claude/skills/discover/SKILL.md +7 -1
- package/dist/claude-code/.claude/skills/explore/SKILL.md +3 -1
- package/dist/claude-code/.claude/skills/new-addendum/SKILL.md +81 -0
- package/package.json +1 -1
- package/reference/frameworks/astro.md +43 -0
- package/reference/frameworks/nextjs.md +44 -0
- package/reference/frameworks/remix.md +44 -0
- package/reference/frameworks/storybook.md +44 -0
- package/reference/frameworks/sveltekit.md +43 -0
- package/reference/frameworks/vite-react.md +43 -0
- package/reference/interaction.md +1 -0
- package/reference/motion/framer-motion.md +45 -0
- package/reference/motion/gsap.md +45 -0
- package/reference/motion/motion-one.md +44 -0
- package/reference/motion/react-spring.md +44 -0
- package/reference/motion.md +1 -0
- package/reference/registry.json +163 -1
- package/reference/registry.schema.json +18 -1
- package/reference/skill-graph.md +2 -1
- package/reference/systems/chakra.md +44 -0
- package/reference/systems/css-modules.md +44 -0
- package/reference/systems/mui.md +44 -0
- package/reference/systems/radix-themes.md +43 -0
- package/reference/systems/shadcn.md +45 -0
- package/reference/systems/styled-components.md +44 -0
- package/reference/systems/tailwind.md +44 -0
- package/reference/systems/vanilla-extract.md +44 -0
- package/scripts/lib/detect/stack.cjs +455 -0
- package/scripts/lib/detect/stack.d.cts +44 -0
- package/scripts/lib/explore-parallel-runner/index.ts +196 -1
- package/scripts/lib/explore-parallel-runner/types.ts +85 -0
- package/scripts/lib/health-mirror/index.cjs +73 -1
- package/scripts/lib/manifest/skills.json +10 -2
- package/scripts/lib/mapper-spawn.cjs +257 -0
- package/scripts/lib/mapper-spawn.d.cts +60 -0
- package/scripts/lib/mappers/compute-batches.mjs +625 -0
- package/scripts/lib/mappers/graph-adjacency.mjs +129 -0
- package/scripts/lib/mappers/incremental-discover.cjs +617 -0
- package/scripts/lib/mappers/incremental-discover.d.cts +133 -0
- package/scripts/lib/mappers/neighbor-map.mjs +0 -0
- package/scripts/lib/new-addendum.cjs +204 -0
- package/sdk/cli/index.js +1504 -3
- package/sdk/fingerprint/classify.cjs +406 -0
- package/sdk/fingerprint/index.ts +405 -0
- package/sdk/fingerprint/store.cjs +523 -0
- package/sdk/index.ts +1 -0
- package/sdk/mcp/gdd-mcp/server.js +1047 -0
- package/skills/discover/SKILL.md +7 -1
- package/skills/explore/SKILL.md +3 -1
- 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;
|