@hegemonart/get-design-done 1.45.0 → 1.47.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 (35) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +97 -0
  4. package/README.md +4 -0
  5. package/SKILL.md +5 -1
  6. package/dist/claude-code/.claude/skills/figma-extract/SKILL.md +1 -1
  7. package/dist/claude-code/.claude/skills/graphify/SKILL.md +1 -1
  8. package/dist/claude-code/.claude/skills/list-pins/SKILL.md +27 -0
  9. package/dist/claude-code/.claude/skills/live/SKILL.md +98 -0
  10. package/dist/claude-code/.claude/skills/pin/SKILL.md +37 -0
  11. package/dist/claude-code/.claude/skills/unpin/SKILL.md +31 -0
  12. package/package.json +3 -1
  13. package/reference/live-mode-integration.md +80 -0
  14. package/reference/registry.json +14 -0
  15. package/reference/schemas/events.schema.json +1 -1
  16. package/reference/schemas/live-session.schema.json +64 -0
  17. package/reference/skill-metadata.md +117 -0
  18. package/scripts/lib/live/bandit-feed.cjs +64 -0
  19. package/scripts/lib/live/events.cjs +86 -0
  20. package/scripts/lib/live/harness-mode.cjs +93 -0
  21. package/scripts/lib/live/postcheck.cjs +158 -0
  22. package/scripts/lib/live/runtime.cjs +233 -0
  23. package/scripts/lib/live/scope-guard.cjs +145 -0
  24. package/scripts/lib/live/session-store.cjs +364 -0
  25. package/scripts/lib/manifest/schemas/skills.schema.json +42 -1
  26. package/scripts/lib/manifest/skills.json +415 -83
  27. package/scripts/lib/pin/cli.cjs +145 -0
  28. package/scripts/lib/pin/harness-detect.cjs +75 -0
  29. package/scripts/lib/pin/store.cjs +288 -0
  30. package/skills/figma-extract/SKILL.md +1 -1
  31. package/skills/graphify/SKILL.md +1 -1
  32. package/skills/list-pins/SKILL.md +27 -0
  33. package/skills/live/SKILL.md +98 -0
  34. package/skills/pin/SKILL.md +37 -0
  35. package/skills/unpin/SKILL.md +31 -0
@@ -0,0 +1,145 @@
1
+ 'use strict';
2
+ /**
3
+ * scripts/lib/pin/cli.cjs — Phase 46 (Skill UX Polish).
4
+ *
5
+ * Thin CLI over scripts/lib/pin/store.cjs. projectRoot is always process.cwd().
6
+ *
7
+ * Usage:
8
+ * node cli.cjs pin <skill> [--user]
9
+ * node cli.cjs unpin <skill>
10
+ * node cli.cjs list
11
+ *
12
+ * Exit codes:
13
+ * 0 action succeeded (at least one file written/removed, or a non-empty list)
14
+ * 1 nothing done (no harness dirs / nothing to remove / empty list)
15
+ * 2 error (bad usage, unknown skill, unexpected failure)
16
+ *
17
+ * Dependency-free CommonJS. Ships in the npm package; runtime-safe.
18
+ */
19
+
20
+ const path = require('path');
21
+
22
+ const { pinSkill, unpinSkill, listPins } = require('./store.cjs');
23
+
24
+ function out(msg) {
25
+ process.stdout.write(msg + '\n');
26
+ }
27
+ function err(msg) {
28
+ process.stderr.write(msg + '\n');
29
+ }
30
+
31
+ function usage() {
32
+ return [
33
+ 'gdd pin - manage pinned skill aliases across installed harness dirs',
34
+ '',
35
+ 'Usage:',
36
+ ' node cli.cjs pin <skill> [--user]',
37
+ ' node cli.cjs unpin <skill>',
38
+ ' node cli.cjs list',
39
+ '',
40
+ 'Exit codes: 0 ok / 1 nothing done / 2 error.',
41
+ ].join('\n');
42
+ }
43
+
44
+ function runPin(skillId, opts) {
45
+ const projectRoot = process.cwd();
46
+ let res;
47
+ try {
48
+ res = pinSkill({ projectRoot, skillId, user: Boolean(opts.user) });
49
+ } catch (e) {
50
+ err(`pin: ${e.message}`);
51
+ return 2;
52
+ }
53
+ if (res.written.length === 0) {
54
+ err(`pin: no harness skills dirs found under ${projectRoot}${opts.user ? '' : ' (try --user to create them)'}.`);
55
+ for (const s of res.skipped) err(` skipped ${s.config_dir}: ${s.reason}`);
56
+ return 1;
57
+ }
58
+ out(`Pinned "${skillId}" into ${res.written.length} harness dir(s):`);
59
+ for (const w of res.written) out(` ${w.config_dir} -> ${path.relative(projectRoot, w.path)}`);
60
+ for (const s of res.skipped) err(` skipped ${s.config_dir}: ${s.reason}`);
61
+ return 0;
62
+ }
63
+
64
+ function runUnpin(skillId) {
65
+ const projectRoot = process.cwd();
66
+ let res;
67
+ try {
68
+ res = unpinSkill({ projectRoot, skillId });
69
+ } catch (e) {
70
+ err(`unpin: ${e.message}`);
71
+ return 2;
72
+ }
73
+ for (const r of res.refused) err(` refused ${r.config_dir}: ${r.reason}`);
74
+ if (res.removed.length === 0) {
75
+ err(`unpin: no pinned "${skillId}" stubs removed.`);
76
+ return 1;
77
+ }
78
+ out(`Unpinned "${skillId}" from ${res.removed.length} harness dir(s):`);
79
+ for (const r of res.removed) out(` ${r.config_dir} -> ${path.relative(projectRoot, r.path)}`);
80
+ return 0;
81
+ }
82
+
83
+ function runList() {
84
+ const projectRoot = process.cwd();
85
+ let pins;
86
+ try {
87
+ pins = listPins(projectRoot);
88
+ } catch (e) {
89
+ err(`list: ${e.message}`);
90
+ return 2;
91
+ }
92
+ if (pins.length === 0) {
93
+ out('No pinned skills found.');
94
+ return 1;
95
+ }
96
+ out(`Pinned skills (${pins.length}):`);
97
+ for (const p of pins) {
98
+ out(` [${p.config_dir}] ${p.alias} -> source=${p.source} (pinned ${p.pinnedAt})`);
99
+ }
100
+ return 0;
101
+ }
102
+
103
+ /**
104
+ * Pure entry point. argv is the slice AFTER node + script (process.argv.slice(2)).
105
+ * Returns an exit code; never calls process.exit (so tests can call it directly).
106
+ */
107
+ function main(argv) {
108
+ const args = Array.isArray(argv) ? argv.slice() : [];
109
+ const cmd = args.shift();
110
+
111
+ if (!cmd || cmd === '--help' || cmd === '-h') {
112
+ out(usage());
113
+ return cmd ? 0 : 2;
114
+ }
115
+
116
+ if (cmd === 'list') {
117
+ if (args.length) { err(`list: unexpected argument "${args[0]}"`); return 2; }
118
+ return runList();
119
+ }
120
+
121
+ if (cmd === 'pin' || cmd === 'unpin') {
122
+ const opts = { user: false };
123
+ const positionals = [];
124
+ for (const a of args) {
125
+ if (a === '--user') opts.user = true;
126
+ else if (a.startsWith('--')) { err(`${cmd}: unknown flag ${a}`); return 2; }
127
+ else positionals.push(a);
128
+ }
129
+ const skillId = positionals[0];
130
+ if (!skillId) { err(`${cmd}: missing <skill> argument`); return 2; }
131
+ if (positionals.length > 1) { err(`${cmd}: unexpected argument "${positionals[1]}"`); return 2; }
132
+ if (cmd === 'unpin' && opts.user) { err('unpin: --user is not valid for unpin'); return 2; }
133
+ return cmd === 'pin' ? runPin(skillId, opts) : runUnpin(skillId);
134
+ }
135
+
136
+ err(`unknown command: ${cmd}`);
137
+ err(usage());
138
+ return 2;
139
+ }
140
+
141
+ if (require.main === module) {
142
+ process.exit(main(process.argv.slice(2)));
143
+ }
144
+
145
+ module.exports = { main };
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+ /**
3
+ * scripts/lib/pin/harness-detect.cjs — Phase 46 (Skill UX Polish).
4
+ *
5
+ * Locates the per-harness `skills/` directories under a project root so the pin
6
+ * store knows where to write / scan pinned skill stubs. Each harness record in
7
+ * scripts/lib/manifest/harnesses.cjs carries a `config_dir` (e.g. ".claude",
8
+ * ".cursor", ".codex"); the candidate skills dir for a harness is
9
+ * `<projectRoot>/<config_dir>/skills`.
10
+ *
11
+ * Two surfaces:
12
+ * detectHarnessSkillDirs(projectRoot) -> only the candidates that EXIST on disk
13
+ * harnessSkillDirCandidates(projectRoot)-> ALL candidates (existing or not), for
14
+ * the --user / create flows that may need
15
+ * to materialize a missing dir.
16
+ *
17
+ * Dependency-free CommonJS. Cross-platform: all path joins go through `path`,
18
+ * never a hardcoded separator. Ships inside the npm package, so it must stay
19
+ * runtime-safe (no dev-only requires).
20
+ */
21
+
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+
25
+ const harnesses = require('../manifest/harnesses.cjs');
26
+
27
+ /**
28
+ * Build the full candidate list (one entry per harness record), regardless of
29
+ * whether the directory currently exists. The candidate skills dir for a harness
30
+ * is `<projectRoot>/<config_dir>/skills`.
31
+ *
32
+ * @param {string} projectRoot absolute or relative project root
33
+ * @returns {Array<{ id: string, config_dir: string, skillsDir: string }>}
34
+ */
35
+ function harnessSkillDirCandidates(projectRoot) {
36
+ if (!projectRoot || typeof projectRoot !== 'string') {
37
+ throw new TypeError('harnessSkillDirCandidates: projectRoot must be a non-empty string');
38
+ }
39
+ const out = [];
40
+ const seen = new Set();
41
+ for (const h of harnesses) {
42
+ if (!h || !h.config_dir) continue;
43
+ // De-dupe on config_dir so two records pointing at the same dir don't double up.
44
+ if (seen.has(h.config_dir)) continue;
45
+ seen.add(h.config_dir);
46
+ out.push({
47
+ id: h.id,
48
+ config_dir: h.config_dir,
49
+ skillsDir: path.join(projectRoot, h.config_dir, 'skills'),
50
+ });
51
+ }
52
+ return out;
53
+ }
54
+
55
+ /**
56
+ * Filter the candidate list to the harness skills dirs that actually exist as
57
+ * directories under projectRoot.
58
+ *
59
+ * @param {string} projectRoot absolute or relative project root
60
+ * @returns {Array<{ id: string, config_dir: string, skillsDir: string }>}
61
+ */
62
+ function detectHarnessSkillDirs(projectRoot) {
63
+ return harnessSkillDirCandidates(projectRoot).filter((c) => {
64
+ try {
65
+ return fs.statSync(c.skillsDir).isDirectory();
66
+ } catch {
67
+ return false;
68
+ }
69
+ });
70
+ }
71
+
72
+ module.exports = {
73
+ detectHarnessSkillDirs,
74
+ harnessSkillDirCandidates,
75
+ };
@@ -0,0 +1,288 @@
1
+ 'use strict';
2
+ /**
3
+ * scripts/lib/pin/store.cjs — Phase 46 (Skill UX Polish).
4
+ *
5
+ * Core for "pinning" a gdd skill: writing a small standalone shortcut alias
6
+ * (a SKILL.md stub) into every installed harness `skills/` dir so the skill is
7
+ * directly discoverable as its own command in each runtime, plus the inverse
8
+ * (unpin) and an inventory (listPins).
9
+ *
10
+ * The pin marker is exactly:
11
+ * <!-- gdd-pinned-skill source=<skillId> -->
12
+ * and is the FIRST line of every pinned stub. unpin only ever deletes files
13
+ * carrying this marker, so a hand-written / unrelated SKILL.md is never removed.
14
+ *
15
+ * Metadata (name, description, argument-hint, tools) is pulled from the manifest
16
+ * SoT via readSkills() — NEVER scraped from live frontmatter — so a pinned stub
17
+ * always reflects the canonical record.
18
+ *
19
+ * Writes are atomic: contents go to `<dest>.tmp` then fs.renameSync to the final
20
+ * path (rename is atomic within a filesystem), so a crash mid-write never leaves
21
+ * a half-written SKILL.md.
22
+ *
23
+ * Dependency-free CommonJS. Cross-platform via `path`. Ships in the npm package,
24
+ * so it stays runtime-safe (no dev-only requires).
25
+ */
26
+
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+
30
+ const { readSkills } = require('../manifest/index.cjs');
31
+ const { detectHarnessSkillDirs, harnessSkillDirCandidates } = require('./harness-detect.cjs');
32
+
33
+ const MARKER_PREFIX = '<!-- gdd-pinned-skill source=';
34
+ const MARKER_SUFFIX = ' -->';
35
+
36
+ /** Build the exact marker line for a skill id. */
37
+ function markerFor(skillId) {
38
+ return `${MARKER_PREFIX}${skillId}${MARKER_SUFFIX}`;
39
+ }
40
+
41
+ /**
42
+ * Extract the `source=<id>` skill id from a marker line, or null if the line is
43
+ * not a gdd pin marker. Tolerates surrounding whitespace.
44
+ */
45
+ function parseMarker(line) {
46
+ if (typeof line !== 'string') return null;
47
+ const trimmed = line.trim();
48
+ if (!trimmed.startsWith(MARKER_PREFIX) || !trimmed.endsWith(MARKER_SUFFIX)) return null;
49
+ const inner = trimmed.slice(MARKER_PREFIX.length, trimmed.length - MARKER_SUFFIX.length);
50
+ const id = inner.trim();
51
+ return id.length ? id : null;
52
+ }
53
+
54
+ /** First non-empty line of a text blob (trimmed), or '' if none. */
55
+ function firstNonEmptyLine(text) {
56
+ const lines = String(text).replace(/\r\n/g, '\n').split('\n');
57
+ for (const l of lines) {
58
+ if (l.trim().length) return l;
59
+ }
60
+ return '';
61
+ }
62
+
63
+ /** Look up a skill record from the manifest SoT by id, or null. */
64
+ function lookupSkill(skillId) {
65
+ const { skills } = readSkills();
66
+ for (const r of skills || []) {
67
+ if (r && r.name === skillId) return r;
68
+ }
69
+ return null;
70
+ }
71
+
72
+ /** Double-quote a YAML scalar, escaping backslashes and quotes. */
73
+ function quote(s) {
74
+ return '"' + String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
75
+ }
76
+
77
+ /**
78
+ * Render the pinned stub contents for a skill record. Layout:
79
+ * <marker line>
80
+ * ---
81
+ * name: gdd-<id>
82
+ * description: "<desc>"
83
+ * argument-hint: "<hint>" (only when the record has one)
84
+ * tools: <tools> (only when the record has tools)
85
+ * ---
86
+ * <one-line body pointing at the source skill>
87
+ *
88
+ * `name` mirrors the generator: `gdd-<id>` unless the record overrides via
89
+ * `frontmatter_name`.
90
+ */
91
+ function renderStub(skillId, rec) {
92
+ const fmName = rec.frontmatter_name || `gdd-${skillId}`;
93
+ const lines = [];
94
+ lines.push(markerFor(skillId));
95
+ lines.push('---');
96
+ lines.push(`name: ${fmName}`);
97
+ lines.push(`description: ${quote(rec.description || '')}`);
98
+ if (rec.argument_hint != null && String(rec.argument_hint).length) {
99
+ lines.push(`argument-hint: ${quote(rec.argument_hint)}`);
100
+ }
101
+ if (rec.tools != null && String(rec.tools).length) {
102
+ lines.push(`tools: ${rec.tools}`);
103
+ }
104
+ lines.push('---');
105
+ lines.push('');
106
+ lines.push(`Pinned alias for the gdd \`${skillId}\` skill. Run the canonical \`${fmName}\` skill; this stub only makes it directly discoverable in this harness.`);
107
+ lines.push('');
108
+ return lines.join('\n');
109
+ }
110
+
111
+ /** Atomic write: write to `<dest>.tmp` then rename into place. */
112
+ function atomicWrite(dest, contents) {
113
+ const dir = path.dirname(dest);
114
+ fs.mkdirSync(dir, { recursive: true });
115
+ const tmp = `${dest}.tmp`;
116
+ fs.writeFileSync(tmp, contents, 'utf8');
117
+ try {
118
+ fs.renameSync(tmp, dest);
119
+ } catch (e) {
120
+ // Clean up the temp file on failure so we never leave a stray .tmp behind.
121
+ try { fs.unlinkSync(tmp); } catch { /* ignore */ }
122
+ throw e;
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Pin a skill across harness skills dirs.
128
+ *
129
+ * @param {object} args
130
+ * @param {string} args.projectRoot
131
+ * @param {string} args.skillId
132
+ * @param {Array<string>} [args.harnesses] optional allow-list of config_dir or harness id; when omitted, all detected dirs
133
+ * @param {boolean} [args.user] when true, materialize ALL candidate dirs (not just existing ones)
134
+ * @returns {{ skillId: string, written: Array<{ id, config_dir, path }>, skipped: Array<{ id, config_dir, reason }> }}
135
+ */
136
+ function pinSkill(args) {
137
+ const { projectRoot, skillId } = args || {};
138
+ if (!projectRoot) throw new TypeError('pinSkill: projectRoot is required');
139
+ if (!skillId) throw new TypeError('pinSkill: skillId is required');
140
+
141
+ const rec = lookupSkill(skillId);
142
+ if (!rec) {
143
+ throw new Error(`pinSkill: "${skillId}" is not a known skill in scripts/lib/manifest/skills.json`);
144
+ }
145
+
146
+ const all = args.user
147
+ ? harnessSkillDirCandidates(projectRoot)
148
+ : detectHarnessSkillDirs(projectRoot);
149
+
150
+ const filter = Array.isArray(args.harnesses) && args.harnesses.length
151
+ ? new Set(args.harnesses)
152
+ : null;
153
+ const targets = filter
154
+ ? all.filter((c) => filter.has(c.config_dir) || filter.has(c.id))
155
+ : all;
156
+
157
+ const contents = renderStub(skillId, rec);
158
+ const written = [];
159
+ const skipped = [];
160
+ for (const t of targets) {
161
+ const dest = path.join(t.skillsDir, skillId, 'SKILL.md');
162
+ try {
163
+ atomicWrite(dest, contents);
164
+ written.push({ id: t.id, config_dir: t.config_dir, path: dest });
165
+ } catch (e) {
166
+ skipped.push({ id: t.id, config_dir: t.config_dir, reason: e.message });
167
+ }
168
+ }
169
+ return { skillId, written, skipped };
170
+ }
171
+
172
+ /**
173
+ * Unpin a skill: delete pinned stubs across harness dirs. REFUSES (skips with a
174
+ * warning) any SKILL.md whose first non-empty line is not the gdd pin marker, so
175
+ * a hand-authored skill is never deleted.
176
+ *
177
+ * @param {object} args
178
+ * @param {string} args.projectRoot
179
+ * @param {string} args.skillId
180
+ * @returns {{ skillId: string, removed: Array<{ id, config_dir, path }>, refused: Array<{ id, config_dir, path, reason }>, missing: Array<{ id, config_dir, path }> }}
181
+ */
182
+ function unpinSkill(args) {
183
+ const { projectRoot, skillId } = args || {};
184
+ if (!projectRoot) throw new TypeError('unpinSkill: projectRoot is required');
185
+ if (!skillId) throw new TypeError('unpinSkill: skillId is required');
186
+
187
+ // Look across every candidate harness dir (existing or not) so we can clean up
188
+ // stubs even if the surrounding harness dir was partially removed.
189
+ const candidates = harnessSkillDirCandidates(projectRoot);
190
+ const removed = [];
191
+ const refused = [];
192
+ const missing = [];
193
+
194
+ for (const c of candidates) {
195
+ const file = path.join(c.skillsDir, skillId, 'SKILL.md');
196
+ let content;
197
+ try {
198
+ content = fs.readFileSync(file, 'utf8');
199
+ } catch {
200
+ missing.push({ id: c.id, config_dir: c.config_dir, path: file });
201
+ continue;
202
+ }
203
+ const marker = parseMarker(firstNonEmptyLine(content));
204
+ if (marker == null) {
205
+ refused.push({
206
+ id: c.id,
207
+ config_dir: c.config_dir,
208
+ path: file,
209
+ reason: 'first non-empty line lacks the gdd-pinned-skill marker - refusing to delete',
210
+ });
211
+ continue;
212
+ }
213
+ try {
214
+ fs.unlinkSync(file);
215
+ // Remove the now-empty alias dir if nothing else lives there.
216
+ const aliasDir = path.dirname(file);
217
+ try {
218
+ if (fs.readdirSync(aliasDir).length === 0) fs.rmdirSync(aliasDir);
219
+ } catch { /* leave non-empty dir alone */ }
220
+ removed.push({ id: c.id, config_dir: c.config_dir, path: file });
221
+ } catch (e) {
222
+ refused.push({ id: c.id, config_dir: c.config_dir, path: file, reason: e.message });
223
+ }
224
+ }
225
+ return { skillId, removed, refused, missing };
226
+ }
227
+
228
+ /**
229
+ * List pinned skills across harness skills dirs.
230
+ *
231
+ * Scans each existing harness skills dir for `<alias>/SKILL.md` files whose first
232
+ * non-empty line carries the gdd pin marker.
233
+ *
234
+ * @param {string} projectRoot
235
+ * @returns {Array<{ id: string, config_dir: string, alias: string, source: string, pinnedAt: string }>}
236
+ * `id` is the harness id, `alias` is the on-disk directory name, `source` is the
237
+ * pinned source skill id from the marker, `pinnedAt` is the file mtime ISO string.
238
+ */
239
+ function listPins(projectRoot) {
240
+ if (!projectRoot) throw new TypeError('listPins: projectRoot is required');
241
+ const out = [];
242
+ for (const dir of detectHarnessSkillDirs(projectRoot)) {
243
+ let entries;
244
+ try {
245
+ entries = fs.readdirSync(dir.skillsDir, { withFileTypes: true });
246
+ } catch {
247
+ continue;
248
+ }
249
+ for (const e of entries) {
250
+ if (!e.isDirectory()) continue;
251
+ const file = path.join(dir.skillsDir, e.name, 'SKILL.md');
252
+ let content;
253
+ let stat;
254
+ try {
255
+ content = fs.readFileSync(file, 'utf8');
256
+ stat = fs.statSync(file);
257
+ } catch {
258
+ continue;
259
+ }
260
+ const source = parseMarker(firstNonEmptyLine(content));
261
+ if (source == null) continue;
262
+ out.push({
263
+ id: dir.id,
264
+ config_dir: dir.config_dir,
265
+ alias: e.name,
266
+ source,
267
+ pinnedAt: stat.mtime.toISOString(),
268
+ });
269
+ }
270
+ }
271
+ // Stable order: by config_dir then alias for deterministic output.
272
+ out.sort((a, b) => (a.config_dir === b.config_dir
273
+ ? a.alias.localeCompare(b.alias)
274
+ : a.config_dir.localeCompare(b.config_dir)));
275
+ return out;
276
+ }
277
+
278
+ module.exports = {
279
+ pinSkill,
280
+ unpinSkill,
281
+ listPins,
282
+ // exported for the CLI + tests
283
+ markerFor,
284
+ parseMarker,
285
+ renderStub,
286
+ MARKER_PREFIX,
287
+ MARKER_SUFFIX,
288
+ };
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: gdd-figma-extract
3
- description: Off-context Figma design-system extraction into a compact local digest (DESIGN.md + tokens.json + components.json). Pulls the file via the Figma REST API and digests it without the raw JSON ever entering the model context.
3
+ description: "Off-context Figma design-system extraction into a compact local digest (DESIGN.md + tokens.json + components.json). Pulls the file via the Figma REST API and digests it without the raw JSON ever entering the model context."
4
4
  ---
5
5
 
6
6
  # gdd-figma-extract
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: gdd-graphify
3
- description: Manage the Graphify knowledge graph for the current project. Build, query, status, diff. When available, design-planner and design-integration-checker use the graph for pre-search consultation.
3
+ description: "Manage the Graphify knowledge graph for the current project. Build, query, status, diff. When available, design-planner and design-integration-checker use the graph for pre-search consultation."
4
4
  ---
5
5
 
6
6
  # gdd-graphify
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: gdd-list-pins
3
+ description: "Lists pinned skill aliases per harness with their source skill and pin timestamp. Use when you want to see which gdd skills have been pinned as standalone shortcuts and where."
4
+ tools: Read, Bash
5
+ ---
6
+
7
+ # /gdd:list-pins
8
+
9
+ **Role:** Show every pinned skill alias across the installed harness `skills/` directories. For each one, report the harness it lives in, the on-disk alias directory name, the source skill it points at (from the `<!-- gdd-pinned-skill source=<skill> -->` marker), and when it was pinned (the file modification time).
10
+
11
+ ## Steps
12
+
13
+ 1. **Run the list CLI.** Invoke the shipped script (it takes no arguments). The plugin root resolves via `CLAUDE_PLUGIN_ROOT` (falling back to the current directory when that variable is absent):
14
+
15
+ ```bash
16
+ node "${CLAUDE_PLUGIN_ROOT:-$(pwd)}/scripts/lib/pin/cli.cjs" list
17
+ ```
18
+
19
+ The CLI scans each harness `skills/` directory under the current project, finds the stubs carrying the gdd pin marker, and prints one line per pinned alias in the form `[<config-dir>] <alias> -> source=<skill> (pinned <timestamp>)`.
20
+
21
+ 2. **Report the result.** Relay the CLI output verbatim. Exit codes: 0 means one or more pinned aliases were found, 1 means none were found (nothing has been pinned yet), 2 means an error.
22
+
23
+ ## Do Not
24
+
25
+ - Do not scan the harness directories by hand. The CLI already enforces the marker check, so only genuine gdd pins are listed.
26
+
27
+ ## LIST-PINS COMPLETE
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: gdd-live
3
+ description: "Live in-browser design mode. The user picks a DOM element on a running dev server (via the Claude Preview MCP), the agent generates N design variants in one batch, they hot-swap in place through HMR or preview_eval using a data-gdd-variant marker, the user accepts or discards, and the whole pick-generate-accept loop persists to .design/live-sessions so it survives a crash or resume. Use when the user wants to iterate on the look of a live component against a real running server, asks to try variants on a page, or runs the live command with a url; falls back to a screenshot-only degraded mode on harnesses without MCP support."
4
+ argument-hint: "[--variants N] [--resume <session-id>] [url]"
5
+ tools: Read, Write, Edit, Bash, Glob, Grep, Task
6
+ user-invocable: true
7
+ ---
8
+
9
+ # gdd-live - Live In-Browser Design Mode
10
+
11
+ Pick a DOM element on a running dev server, generate competing design variants, hot-swap them in place, and accept the winner as a real source edit. Every step persists to `.design/live-sessions/<id>.json` so the session survives a crash or a later resume.
12
+
13
+ The browser-side runtime, the harness-mode gate, the session store, the events feed, the post-check, the scope guard, and the bandit feed are all separate modules under `scripts/lib/live/`. This skill describes the loop and names the module that owns each step; it does not import them.
14
+
15
+ For the full surface (the Preview MCP tools, the six `live_*` events, the session file, the bandit feed, degraded mode, the scope guard), see `../../reference/live-mode-integration.md`. For the SKILL.md structural contract, see `../../reference/skill-authoring-contract.md`.
16
+
17
+ ---
18
+
19
+ ## Arguments
20
+
21
+ - `[url]` - the page to drive. Optional. When omitted, detect the dev server and use its root.
22
+ - `--variants N` - how many variants to generate per pick. Default 3.
23
+ - `--resume <session-id>` - reattach to an in-progress session in `.design/live-sessions/`.
24
+
25
+ ---
26
+
27
+ ## BOOT
28
+
29
+ 1. Probe the Preview MCP per `../../connections/preview.md`: `ToolSearch({ query: "Claude_Preview" })`, then `mcp__Claude_Preview__preview_list`. Empty ToolSearch means the MCP is not loaded.
30
+ 2. Resolve the harness live mode. The capability signal is `capability_matrix.mcp_support` in `scripts/lib/manifest/harnesses.json`, projected by `scripts/lib/live/harness-mode.cjs` (`liveModeFor(harnessId)`). A `puppeteer` result means full live mode; a `degraded` result means screenshot-only.
31
+ 3. If `mcp_support` is false for this harness, or Preview is unavailable, enter DEGRADED mode and say so plainly: variants are generated and captured as static screenshots, with no in-page hot-swap. Skip the INJECT and PICK steps; generate against the file the user names instead.
32
+ 4. Detect the dev server. Look for Vite, Next, Bun, or a static server (check `package.json` scripts plus a `preview_list` entry). Record the server descriptor on the session.
33
+ 5. Open or create the session via `scripts/lib/live/session-store.cjs` (`.design/live-sessions/<id>.json`). On `--resume`, load the named session (see RESUME).
34
+
35
+ ---
36
+
37
+ ## INJECT
38
+
39
+ Inject the browser runtime once. Read `RUNTIME_JS` from `scripts/lib/live/runtime.cjs` and evaluate it in the page with `mcp__Claude_Preview__preview_eval`. The runtime is an idempotent IIFE bound to `window.__gddLive`, so a re-inject after navigation rebinds the same singleton rather than stacking listeners. It installs the pick handler and the variant-swap helpers, and stamps the live variant on the element via the `data-gdd-variant` attribute.
40
+
41
+ ---
42
+
43
+ ## PICK
44
+
45
+ 1. Arm the picker (`window.__gddLive.pick()`), then guide the user to click the target element. Use `preview_click` and `preview_inspect` to confirm the element and read its computed styles and bounding box.
46
+ 2. Read the pick report back. Its fields are documented in `pickReportShape` (selector, tagName, classList, boundingRect, computedStyle subset, current variant). The selector strategy prefers id, then a data-testid, then a tag plus class plus nth-of-type path.
47
+ 3. Emit a `live_pick` event through `scripts/lib/live/events.cjs` and append a `pick` entry to the session.
48
+
49
+ ---
50
+
51
+ ## GENERATE (one batch)
52
+
53
+ 1. Load the relevant Phase 45 canonical reference index FIRST, so variants are grounded in real guidance: the domain index that matches the picked element (for example `../../reference/spatial.md` for layout, `../../reference/interaction.md` for components and a11y, `../../reference/color.md` for color, `../../reference/typography.md` for type, `../../reference/motion.md` for animation).
54
+ 2. Generate all N variants in ONE batch (default 3), each a distinct, hypothesis-tagged design direction for the picked element. Do not generate them one at a time.
55
+ 3. For each variant: write the change atomically to the implicated source file, then make it live. With HMR running, the file write is enough; otherwise apply the variant in place with `window.__gddLive.swapVariant({ n, style, html })`, which sets `data-gdd-variant="n"` and applies the variant's style or markup.
56
+
57
+ ---
58
+
59
+ ## POST-CHECK
60
+
61
+ Run the post-check on each variant via `scripts/lib/live/postcheck.cjs`, which invokes `gdd-detect`. Show the findings inline next to each variant. A variant that trips a finding is flagged, NOT auto-rejected: the user still decides. Append a `live_postcheck` event per variant.
62
+
63
+ ---
64
+
65
+ ## ACCEPT / DISCARD
66
+
67
+ - ACCEPT one variant: apply the chosen variant as the canonical source edit, and revert the others in the page (`window.__gddLive.revert()` on each non-chosen element). Emit a `live_accept` event and feed the outcome to the design-variants bandit via `scripts/lib/live/bandit-feed.cjs` (a dev-time signal). Append an `accept` entry.
68
+ - DISCARD: revert every variant in the page back to its captured original and leave the source untouched. Emit a `live_discard` event and append a `discard` entry.
69
+
70
+ Either way, persist the result through `scripts/lib/live/session-store.cjs` before continuing.
71
+
72
+ ---
73
+
74
+ ## PERSIST
75
+
76
+ Every step (boot, pick, generate, post-check, accept, discard) is written to the session file through `scripts/lib/live/session-store.cjs` as it happens. The on-disk event log uses the `pick`, `generate`, `accept`, `discard` kinds; the telemetry stream uses the six `live_*` event types. Writes are atomic, so an interrupted step never leaves a half-written session.
77
+
78
+ ---
79
+
80
+ ## RESUME
81
+
82
+ With `--resume <session-id>`, load the named session from `.design/live-sessions/`. Only an `in_progress` session is resumable. Offer the user two choices: continue from the last recorded event (report what that was, for example "last pick was the primary button"), or start fresh (open a new session and leave the old one intact). Never silently replay completed events.
83
+
84
+ ---
85
+
86
+ ## SCOPE GUARD
87
+
88
+ Never write outside the source files implicated by the picked element. Run every proposed write through `scripts/lib/live/scope-guard.cjs`, which maps the picked selector to its owning source files and rejects edits that fall outside them. If a variant would need a change beyond that scope (a shared token, a parent layout, a new dependency), stop and surface it to the user rather than widening the blast radius.
89
+
90
+ ## Constraints
91
+
92
+ - Do NOT edit files outside the picked element's implicated sources (enforced by the scope guard).
93
+ - Do NOT generate variants one at a time; generate the full batch, then swap.
94
+ - Do NOT auto-reject a variant on a post-check finding; flag it and let the user decide.
95
+ - In DEGRADED mode, state up front that hot-swap is unavailable and fall back to screenshots.
96
+ - Persist before every user-facing prompt so a crash never loses accepted work.
97
+
98
+ ## LIVE COMPLETE