@maccesar/aiskills 1.7.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/README.md +531 -0
- package/bin/aiskills.js +76 -0
- package/lib/cache.js +49 -0
- package/lib/cleanup.js +77 -0
- package/lib/commands/auto-update.js +131 -0
- package/lib/commands/doctor.js +139 -0
- package/lib/commands/list.js +77 -0
- package/lib/commands/skills.js +263 -0
- package/lib/commands/status.js +94 -0
- package/lib/commands/uninstall.js +182 -0
- package/lib/commands/update.js +149 -0
- package/lib/config.js +90 -0
- package/lib/downloader.js +110 -0
- package/lib/hooks.js +74 -0
- package/lib/installer.js +114 -0
- package/lib/platform.js +112 -0
- package/lib/prompts/checkboxCancel.js +264 -0
- package/lib/prompts/selectCancel.js +204 -0
- package/lib/symlink.js +154 -0
- package/lib/utils.js +49 -0
- package/package.json +61 -0
- package/skills/humaniza/SKILL.md +51 -0
- package/skills/humaniza/agents/openai.yaml +4 -0
- package/skills/humaniza/references/ai-patterns-es.md +51 -0
- package/skills/humaniza/references/checklist.md +9 -0
- package/skills/humaniza/references/examples.md +17 -0
- package/skills/humaniza/references/lexicon-es-mx.md +36 -0
- package/skills/humaniza/references/modes-es-mx.md +41 -0
- package/skills/humaniza/references/voice-es-mx.md +24 -0
- package/skills/refactoring-ui/SKILL.md +59 -0
- package/skills/refactoring-ui/references/01-design-process.md +72 -0
- package/skills/refactoring-ui/references/02-visual-hierarchy.md +84 -0
- package/skills/refactoring-ui/references/03-layout-spacing.md +69 -0
- package/skills/refactoring-ui/references/04-typography.md +70 -0
- package/skills/refactoring-ui/references/05-color.md +96 -0
- package/skills/refactoring-ui/references/06-depth-shadows.md +74 -0
- package/skills/refactoring-ui/references/07-images.md +75 -0
- package/skills/refactoring-ui/references/08-finishing-touches.md +91 -0
- package/skills/stitch-showcase/SKILL.md +411 -0
- package/skills/stitch-showcase/references/01-navbar.md +52 -0
- package/skills/stitch-showcase/references/02-hero.md +56 -0
- package/skills/stitch-showcase/references/03-design-system.md +102 -0
- package/skills/stitch-showcase/references/04-screen-gallery.md +102 -0
- package/skills/stitch-showcase/references/05-viewer-web.md +105 -0
- package/skills/stitch-showcase/references/06-viewer-mobile.md +104 -0
- package/skills/stitch-showcase/references/07-theme-system.md +77 -0
- package/skills/stitch-showcase/references/08-type-detection.md +81 -0
- package/skills/stitch-showcase/references/09-quality-standards.md +126 -0
- package/skills/stitch-showcase/references/10-component-standardization.md +40 -0
- package/skills/stitch-showcase/references/11-component-catalog.md +70 -0
- package/skills/stitch-showcase/references/catalog-template.html +841 -0
- package/skills/stitch-showcase/references/index.html +299 -0
- package/skills/stitch-showcase/references/viewer.html +412 -0
- package/skills/stitch-showcase/scripts/__pycache__/build_showcase.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/component_utils.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/detect_components.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/extract_catalog.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/extract_text.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/extract_zips.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/parse_design_md.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/apply_canonical.py +238 -0
- package/skills/stitch-showcase/scripts/build_showcase.py +2103 -0
- package/skills/stitch-showcase/scripts/component_utils.py +398 -0
- package/skills/stitch-showcase/scripts/detect_components.py +284 -0
- package/skills/stitch-showcase/scripts/extract_catalog.py +913 -0
- package/skills/stitch-showcase/scripts/extract_text.py +268 -0
- package/skills/stitch-showcase/scripts/extract_zips.py +178 -0
- package/skills/stitch-showcase/scripts/parse_design_md.py +397 -0
- package/skills/vscode-extension-dev/SKILL.md +114 -0
- package/skills/vscode-extension-dev/references/api-patterns.md +625 -0
- package/skills/vscode-extension-dev/references/architecture.md +287 -0
- package/skills/vscode-extension-dev/references/package-json-schema.md +345 -0
- package/skills/vscode-extension-dev/references/publishing.md +251 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"""
|
|
2
|
+
parse_design_md.py — Extracts metadata from DESIGN.md for stitch-showcase.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python parse_design_md.py /path/to/DESIGN.md
|
|
6
|
+
→ prints JSON with project_name, type, colors, color_tokens, default_theme, font_family, screens, sections
|
|
7
|
+
"""
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse(design_md_path: str) -> dict:
|
|
15
|
+
path = Path(design_md_path)
|
|
16
|
+
if not path.exists():
|
|
17
|
+
return {
|
|
18
|
+
"project_name": "",
|
|
19
|
+
"type": "unknown",
|
|
20
|
+
"colors": {},
|
|
21
|
+
"color_tokens": {},
|
|
22
|
+
"default_theme": "light",
|
|
23
|
+
"font_family": None,
|
|
24
|
+
"screens": [],
|
|
25
|
+
"sections": [],
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
text = path.read_text(encoding="utf-8")
|
|
29
|
+
colors = _extract_colors(text)
|
|
30
|
+
color_tokens = _extract_color_tokens(text)
|
|
31
|
+
|
|
32
|
+
# Determine default_theme from surface token
|
|
33
|
+
surface_hex = color_tokens.get("surface") or color_tokens.get("background")
|
|
34
|
+
if not surface_hex:
|
|
35
|
+
# fall back to colors dict
|
|
36
|
+
for key in ("surface", "background", "bg"):
|
|
37
|
+
if key in colors:
|
|
38
|
+
surface_hex = colors[key]
|
|
39
|
+
break
|
|
40
|
+
default_theme = _surface_default_theme(surface_hex) if surface_hex else "light"
|
|
41
|
+
|
|
42
|
+
screens = _extract_screens(text)
|
|
43
|
+
sections = _extract_sections(text)
|
|
44
|
+
|
|
45
|
+
# Merge inline titles and descriptions from sections into the screens list.
|
|
46
|
+
# Sections can contain "- slug: Title | Description" or "- slug: description"
|
|
47
|
+
# entries that _extract_screens misses when slugs are only listed under ### headers.
|
|
48
|
+
section_titles = {}
|
|
49
|
+
section_descs = {}
|
|
50
|
+
for sec in sections:
|
|
51
|
+
for slug, title in sec.get("titles", {}).items():
|
|
52
|
+
section_titles[slug] = title
|
|
53
|
+
for slug, desc in sec.get("descriptions", {}).items():
|
|
54
|
+
section_descs[slug] = desc
|
|
55
|
+
|
|
56
|
+
if section_titles or section_descs:
|
|
57
|
+
existing_slugs = {s["slug"] for s in screens}
|
|
58
|
+
# Apply titles/descriptions to screens already in the list
|
|
59
|
+
for s in screens:
|
|
60
|
+
slug = s["slug"]
|
|
61
|
+
if not s.get("title") or s["title"] == _slug_to_title(slug):
|
|
62
|
+
if slug in section_titles:
|
|
63
|
+
s["title"] = section_titles[slug]
|
|
64
|
+
if not s.get("description") and slug in section_descs:
|
|
65
|
+
s["description"] = section_descs[slug]
|
|
66
|
+
# Add slugs that only appear inside sections (not in the flat screen list)
|
|
67
|
+
for sec in sections:
|
|
68
|
+
for slug in sec["slugs"]:
|
|
69
|
+
if slug not in existing_slugs:
|
|
70
|
+
screens.append({
|
|
71
|
+
"slug": slug,
|
|
72
|
+
"title": section_titles.get(slug) or _slug_to_title(slug),
|
|
73
|
+
"description": section_descs.get(slug, ""),
|
|
74
|
+
})
|
|
75
|
+
existing_slugs.add(slug)
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
"project_name": _extract_project_name(text),
|
|
79
|
+
"type": _detect_type(text),
|
|
80
|
+
"colors": colors,
|
|
81
|
+
"color_tokens": color_tokens,
|
|
82
|
+
"default_theme": default_theme,
|
|
83
|
+
"font_family": _extract_typography(text),
|
|
84
|
+
"screens": screens,
|
|
85
|
+
"sections": sections,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _extract_project_name(text: str) -> str:
|
|
90
|
+
"""First H1 heading in the document."""
|
|
91
|
+
m = re.search(r"^#\s+(.+)$", text, re.MULTILINE)
|
|
92
|
+
return m.group(1).strip() if m else ""
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _detect_type(text: str) -> str:
|
|
96
|
+
"""Returns 'mobile', 'web', or 'unknown'.
|
|
97
|
+
|
|
98
|
+
Priority:
|
|
99
|
+
1. Explicit ``## Type`` section with 'mobile' or 'web' on the next line
|
|
100
|
+
2. Keyword scoring across the full document
|
|
101
|
+
"""
|
|
102
|
+
# 1. Explicit ## Type section — authoritative if present
|
|
103
|
+
m = re.search(r"^##\s+Type\s*\n\s*(\S+)", text, re.MULTILINE | re.IGNORECASE)
|
|
104
|
+
if m:
|
|
105
|
+
val = m.group(1).strip().lower()
|
|
106
|
+
if val in ("mobile", "web"):
|
|
107
|
+
return val
|
|
108
|
+
|
|
109
|
+
# 2. Keyword scoring fallback
|
|
110
|
+
lower = text.lower()
|
|
111
|
+
mobile_keywords = ["móvil", "movil", "mobile", "ios", "android", "app móvil", "aplicación móvil"]
|
|
112
|
+
web_keywords = ["web", "dashboard", "escritorio", "desktop", "browser", "navegador"]
|
|
113
|
+
|
|
114
|
+
mobile_score = sum(1 for kw in mobile_keywords if kw in lower)
|
|
115
|
+
web_score = sum(1 for kw in web_keywords if kw in lower)
|
|
116
|
+
|
|
117
|
+
if mobile_score > web_score:
|
|
118
|
+
return "mobile"
|
|
119
|
+
if web_score > mobile_score:
|
|
120
|
+
return "web"
|
|
121
|
+
return "unknown"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _extract_colors(text: str) -> dict:
|
|
125
|
+
"""Extract name:value pairs from the colors section."""
|
|
126
|
+
colors = {}
|
|
127
|
+
color_section = re.search(
|
|
128
|
+
r"##\s+(?:Colores?|Colors?)\s*\n(.*?)(?=\n##|\Z)",
|
|
129
|
+
text, re.IGNORECASE | re.DOTALL
|
|
130
|
+
)
|
|
131
|
+
if not color_section:
|
|
132
|
+
return colors
|
|
133
|
+
|
|
134
|
+
for line in color_section.group(1).splitlines():
|
|
135
|
+
# Formats: "- Primary: #FDD900" or "Primary: #FDD900"
|
|
136
|
+
m = re.search(r"[-*]?\s*(.+?):\s*(#[0-9A-Fa-f]{3,8}|rgb\(.+?\)|[a-z]+)\s*$", line, re.IGNORECASE)
|
|
137
|
+
if m:
|
|
138
|
+
key = m.group(1).strip().lower()
|
|
139
|
+
val = m.group(2).strip()
|
|
140
|
+
colors[key] = val
|
|
141
|
+
|
|
142
|
+
return colors
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _extract_color_tokens(text: str) -> dict:
|
|
146
|
+
"""
|
|
147
|
+
Extract semantic color tokens in Stitch DESIGN.md format.
|
|
148
|
+
|
|
149
|
+
Matches: `token-name` (#XXXXXX) or token-name (#XXXXXX)
|
|
150
|
+
Returns dict with semantic roles:
|
|
151
|
+
accent → first token containing 'primary' (not 'on-primary')
|
|
152
|
+
surface → first token named 'surface' or containing 'background'/'bg'
|
|
153
|
+
+ all raw tokens by name
|
|
154
|
+
"""
|
|
155
|
+
tokens = {}
|
|
156
|
+
|
|
157
|
+
# Match backtick-wrapped: `token-name` (#XXXXXX)
|
|
158
|
+
for m in re.finditer(r"`([a-z][a-z0-9\-]+)`\s*\(#([0-9A-Fa-f]{6})\)", text, re.IGNORECASE):
|
|
159
|
+
tokens[m.group(1).lower()] = "#" + m.group(2).upper()
|
|
160
|
+
|
|
161
|
+
# Match bare: token-name (#XXXXXX) (only if not already captured)
|
|
162
|
+
for m in re.finditer(r"\b([a-z][a-z0-9\-]+)\s*\(#([0-9A-Fa-f]{6})\)", text, re.IGNORECASE):
|
|
163
|
+
key = m.group(1).lower()
|
|
164
|
+
if key not in tokens:
|
|
165
|
+
tokens[key] = "#" + m.group(2).upper()
|
|
166
|
+
|
|
167
|
+
# Build semantic roles
|
|
168
|
+
result = dict(tokens) # copy all raw tokens
|
|
169
|
+
|
|
170
|
+
# accent: first 'primary' token that isn't 'on-primary'
|
|
171
|
+
for name, val in tokens.items():
|
|
172
|
+
if "primary" in name and not name.startswith("on-"):
|
|
173
|
+
result["accent"] = val
|
|
174
|
+
break
|
|
175
|
+
|
|
176
|
+
# surface: 'surface' or 'background'/'bg'
|
|
177
|
+
for name, val in tokens.items():
|
|
178
|
+
if name == "surface" or "background" in name or name == "bg":
|
|
179
|
+
result["surface"] = val
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _surface_default_theme(surface_hex: str) -> str:
|
|
186
|
+
"""
|
|
187
|
+
Determine showcase default theme from app surface color luminance.
|
|
188
|
+
|
|
189
|
+
Dark app surface → use light showcase (for contrast).
|
|
190
|
+
Light app surface → use dark showcase (for contrast).
|
|
191
|
+
"""
|
|
192
|
+
hex_clean = surface_hex.lstrip("#")
|
|
193
|
+
if len(hex_clean) != 6:
|
|
194
|
+
return "light"
|
|
195
|
+
try:
|
|
196
|
+
r = int(hex_clean[0:2], 16)
|
|
197
|
+
g = int(hex_clean[2:4], 16)
|
|
198
|
+
b = int(hex_clean[4:6], 16)
|
|
199
|
+
except ValueError:
|
|
200
|
+
return "light"
|
|
201
|
+
|
|
202
|
+
luminance = 0.299 * r + 0.587 * g + 0.114 * b
|
|
203
|
+
if luminance < 100:
|
|
204
|
+
return "light" # dark app → light showcase
|
|
205
|
+
if luminance > 155:
|
|
206
|
+
return "dark" # light app → dark showcase
|
|
207
|
+
return "light"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _extract_typography(text: str) -> str | None:
|
|
211
|
+
"""
|
|
212
|
+
Extract primary font family name from the Typography section of DESIGN.md.
|
|
213
|
+
|
|
214
|
+
Returns font name (e.g. 'Inter') or None if not found.
|
|
215
|
+
"""
|
|
216
|
+
# Look for ## Typography or ## N. Typography section
|
|
217
|
+
typo_section = re.search(
|
|
218
|
+
r"##\s+(?:\d+\.\s+)?Typography\s*\n(.*?)(?=\n##|\Z)",
|
|
219
|
+
text, re.IGNORECASE | re.DOTALL
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
UI_LABELS = {"display", "headline", "title", "body", "label", "bold", "regular", "medium", "semibold"}
|
|
223
|
+
|
|
224
|
+
if typo_section:
|
|
225
|
+
section_text = typo_section.group(1)
|
|
226
|
+
# Look for bold text **FontName** in first few lines
|
|
227
|
+
for line in section_text.splitlines()[:6]:
|
|
228
|
+
m = re.search(r"\*\*([A-Z][a-zA-Z\s\+]+)\*\*", line)
|
|
229
|
+
if m:
|
|
230
|
+
candidate = m.group(1).strip()
|
|
231
|
+
words = candidate.split()
|
|
232
|
+
# Font names are 1-3 words, not UI labels
|
|
233
|
+
if 1 <= len(words) <= 3 and words[0].lower() not in UI_LABELS:
|
|
234
|
+
return candidate
|
|
235
|
+
|
|
236
|
+
# Broader search in full section
|
|
237
|
+
for m in re.finditer(r"\*\*([A-Z][a-zA-Z\s\+]+)\*\*", section_text):
|
|
238
|
+
candidate = m.group(1).strip()
|
|
239
|
+
words = candidate.split()
|
|
240
|
+
if 1 <= len(words) <= 3 and words[0].lower() not in UI_LABELS:
|
|
241
|
+
return candidate
|
|
242
|
+
|
|
243
|
+
# Fallback: search entire doc for font-family
|
|
244
|
+
m = re.search(r"font-family:\s*[\"']?([A-Z][a-zA-Z\s]+)[\"']?", text)
|
|
245
|
+
if m:
|
|
246
|
+
candidate = m.group(1).strip().rstrip(",;")
|
|
247
|
+
if candidate:
|
|
248
|
+
return candidate
|
|
249
|
+
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _extract_screens(text: str) -> list:
|
|
254
|
+
"""
|
|
255
|
+
Extract screen list with slug, title, description.
|
|
256
|
+
|
|
257
|
+
Supported formats:
|
|
258
|
+
1. Markdown table: | slug | title | description |
|
|
259
|
+
2. Numbered list: 1. splash_screen - Description
|
|
260
|
+
3. Bullet list: - splash_screen — Description
|
|
261
|
+
4. Simple pair: splash_screen: Description
|
|
262
|
+
"""
|
|
263
|
+
# Attempt 1: markdown table
|
|
264
|
+
table_screens = _parse_table(text)
|
|
265
|
+
if table_screens:
|
|
266
|
+
return table_screens
|
|
267
|
+
|
|
268
|
+
# Attempt 2: numbered or bullet list inside a Screens section
|
|
269
|
+
list_screens = _parse_screen_list(text)
|
|
270
|
+
if list_screens:
|
|
271
|
+
return list_screens
|
|
272
|
+
|
|
273
|
+
return []
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _parse_table(text: str) -> list:
|
|
277
|
+
"""Parse markdown table with slug/title/description columns."""
|
|
278
|
+
section = re.search(
|
|
279
|
+
r"##\s+(?:Pantallas?|Screens?)\s*\n(.*?)(?=\n##|\Z)",
|
|
280
|
+
text, re.IGNORECASE | re.DOTALL
|
|
281
|
+
)
|
|
282
|
+
if not section:
|
|
283
|
+
return []
|
|
284
|
+
|
|
285
|
+
section_text = section.group(1)
|
|
286
|
+
rows = []
|
|
287
|
+
|
|
288
|
+
for line in section_text.splitlines():
|
|
289
|
+
# Table rows must START with | (not bullet lines with "Title | Desc" format)
|
|
290
|
+
if not re.match(r"^\s*\|", line) or re.match(r"^\s*\|[-\s|]+\|\s*$", line):
|
|
291
|
+
continue
|
|
292
|
+
cols = [c.strip() for c in line.split("|") if c.strip()]
|
|
293
|
+
if len(cols) >= 2:
|
|
294
|
+
slug = _to_slug(cols[0])
|
|
295
|
+
title = cols[1] if len(cols) > 1 else _slug_to_title(slug)
|
|
296
|
+
desc = cols[2] if len(cols) > 2 else ""
|
|
297
|
+
rows.append({"slug": slug, "title": title, "description": desc})
|
|
298
|
+
|
|
299
|
+
# Skip header row if it contains "slug", "screen", etc.
|
|
300
|
+
if rows and rows[0]["slug"] in ("slug", "pantalla", "screen", "nombre", "name"):
|
|
301
|
+
rows = rows[1:]
|
|
302
|
+
|
|
303
|
+
return rows
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _parse_screen_list(text: str) -> list:
|
|
307
|
+
"""Parse numbered or bullet lists of screens."""
|
|
308
|
+
section = re.search(
|
|
309
|
+
r"##\s+(?:Pantallas?|Screens?)\s*\n(.*?)(?=\n##|\Z)",
|
|
310
|
+
text, re.IGNORECASE | re.DOTALL
|
|
311
|
+
)
|
|
312
|
+
if not section:
|
|
313
|
+
return []
|
|
314
|
+
|
|
315
|
+
screens = []
|
|
316
|
+
for line in section.group(1).splitlines():
|
|
317
|
+
# "1. slug_name - Description" or "- slug_name — Description" or "- slug: Title | Description"
|
|
318
|
+
m = re.match(r"^\s*(?:\d+\.|[-*])\s+([a-zA-Z0-9_\-]+)\s*[-—:]\s*(.+)$", line)
|
|
319
|
+
if m:
|
|
320
|
+
slug = _to_slug(m.group(1))
|
|
321
|
+
raw = m.group(2).strip()
|
|
322
|
+
if " | " in raw:
|
|
323
|
+
title_part, desc_part = raw.split(" | ", 1)
|
|
324
|
+
title = title_part.strip()
|
|
325
|
+
desc = desc_part.strip()
|
|
326
|
+
else:
|
|
327
|
+
title = _slug_to_title(slug)
|
|
328
|
+
desc = raw
|
|
329
|
+
screens.append({"slug": slug, "title": title, "description": desc})
|
|
330
|
+
|
|
331
|
+
return screens
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _extract_sections(text: str) -> list:
|
|
335
|
+
"""
|
|
336
|
+
Extract sections with their screen slugs if defined in DESIGN.md.
|
|
337
|
+
|
|
338
|
+
Expected format:
|
|
339
|
+
### Onboarding
|
|
340
|
+
- splash_screen
|
|
341
|
+
- login
|
|
342
|
+
"""
|
|
343
|
+
sections = []
|
|
344
|
+
section_block = re.search(
|
|
345
|
+
r"##\s+(?:Pantallas?|Screens?)\s*\n(.*?)(?=\n##\s+(?!#)|\Z)",
|
|
346
|
+
text, re.IGNORECASE | re.DOTALL
|
|
347
|
+
)
|
|
348
|
+
if not section_block:
|
|
349
|
+
return []
|
|
350
|
+
|
|
351
|
+
content = section_block.group(1)
|
|
352
|
+
current_section = None
|
|
353
|
+
|
|
354
|
+
for line in content.splitlines():
|
|
355
|
+
h3 = re.match(r"^###\s+(.+)$", line)
|
|
356
|
+
if h3:
|
|
357
|
+
current_section = {"name": h3.group(1).strip(), "slugs": []}
|
|
358
|
+
sections.append(current_section)
|
|
359
|
+
continue
|
|
360
|
+
|
|
361
|
+
if current_section:
|
|
362
|
+
m = re.match(r"^\s*[-*\d.]+\s*([a-zA-Z0-9_\-]+)\s*(?:[-—:]\s*(.+))?$", line)
|
|
363
|
+
if m:
|
|
364
|
+
slug = _to_slug(m.group(1))
|
|
365
|
+
current_section["slugs"].append(slug)
|
|
366
|
+
if m.group(2):
|
|
367
|
+
raw = m.group(2).strip()
|
|
368
|
+
# Support "Title | Description" format for mangled slugs
|
|
369
|
+
if " | " in raw:
|
|
370
|
+
title_part, desc_part = raw.split(" | ", 1)
|
|
371
|
+
current_section.setdefault("titles", {})[slug] = title_part.strip()
|
|
372
|
+
current_section.setdefault("descriptions", {})[slug] = desc_part.strip()
|
|
373
|
+
else:
|
|
374
|
+
current_section.setdefault("descriptions", {})[slug] = raw
|
|
375
|
+
|
|
376
|
+
return sections
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _to_slug(s: str) -> str:
|
|
380
|
+
"""Normalize to snake_case slug."""
|
|
381
|
+
s = s.strip().lower()
|
|
382
|
+
s = re.sub(r"[\s\-]+", "_", s)
|
|
383
|
+
s = re.sub(r"[^a-z0-9_]", "", s)
|
|
384
|
+
return s
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _slug_to_title(slug: str) -> str:
|
|
388
|
+
"""splash_screen → 'Splash Screen'"""
|
|
389
|
+
return slug.replace("_", " ").replace("-", " ").title()
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
if __name__ == "__main__":
|
|
393
|
+
if len(sys.argv) < 2:
|
|
394
|
+
print("Usage: python parse_design_md.py /path/to/DESIGN.md", file=sys.stderr)
|
|
395
|
+
sys.exit(1)
|
|
396
|
+
result = parse(sys.argv[1])
|
|
397
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vscode-extension-dev
|
|
3
|
+
description: >
|
|
4
|
+
Guide for building VS Code extensions from scratch. Use when the user is creating,
|
|
5
|
+
scaffolding, designing, debugging, testing, bundling, or publishing a VS Code extension.
|
|
6
|
+
Covers all major API patterns: TreeView, QuickPick, Webview, StatusBar, commands,
|
|
7
|
+
configuration, SecretStorage, progress indicators, and esbuild bundling.
|
|
8
|
+
when_to_use: >
|
|
9
|
+
- User wants to create a new VS Code extension
|
|
10
|
+
- User asks about VS Code extension APIs (TreeView, Webview, QuickPick, etc.)
|
|
11
|
+
- User needs help with package.json contributes, activationEvents, or keybindings
|
|
12
|
+
- User is debugging extension activation, disposables, or memory leaks
|
|
13
|
+
- User asks about bundling extensions with esbuild or webpack
|
|
14
|
+
- User wants to publish an extension to the VS Code Marketplace or Open VSX
|
|
15
|
+
- User asks about Webview CSP, nonce, or postMessage communication
|
|
16
|
+
- User asks about SecretStorage or credential management in extensions
|
|
17
|
+
- User needs help with extension testing (@vscode/test-electron)
|
|
18
|
+
source: "VS Code Extension API documentation (https://code.visualstudio.com/api)"
|
|
19
|
+
anti_hallucination_note: >
|
|
20
|
+
ALL guidance in this skill comes from the official VS Code Extension API docs
|
|
21
|
+
and established community patterns. Do NOT invent API methods, event names,
|
|
22
|
+
or configuration keys. If unsure whether an API exists, say so explicitly.
|
|
23
|
+
Always verify imports come from the 'vscode' module.
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# VS Code Extension Development Skill
|
|
27
|
+
|
|
28
|
+
You are a VS Code extension development advisor. Base ALL guidance on the reference files below — not training data.
|
|
29
|
+
|
|
30
|
+
## How to Use This Skill
|
|
31
|
+
|
|
32
|
+
1. Read the relevant reference file(s) before answering
|
|
33
|
+
2. Base ALL code on the reference content — not training data
|
|
34
|
+
3. Use real TypeScript imports and correct `vscode` API signatures
|
|
35
|
+
4. Do not invent API methods, events, or configuration keys not in the references
|
|
36
|
+
|
|
37
|
+
## Scaffolding Workflow
|
|
38
|
+
|
|
39
|
+
1. **Generate project**: `npx --package yo --package generator-code -- yo code`
|
|
40
|
+
2. **Choose template**: TypeScript extension (recommended)
|
|
41
|
+
3. **Choose bundler**: esbuild (recommended) or webpack
|
|
42
|
+
4. **Project structure** created — see `references/architecture.md` for layout
|
|
43
|
+
5. **Configure** `package.json` — see `references/package-json-schema.md`
|
|
44
|
+
6. **Implement** — see `references/api-patterns.md` for working examples
|
|
45
|
+
7. **Test** — see `references/architecture.md` for testing strategy
|
|
46
|
+
8. **Publish** — see `references/publishing.md` for full workflow
|
|
47
|
+
|
|
48
|
+
## UI Component Decision Matrix
|
|
49
|
+
|
|
50
|
+
| Need | Use | Why |
|
|
51
|
+
| --------------------------------- | -------------------- | ---------------------------------------------- |
|
|
52
|
+
| Hierarchical data in sidebar | TreeView | Native tree with expand/collapse, icons, badges |
|
|
53
|
+
| Quick selection from a list | QuickPick | Modal list with filtering, multi-select |
|
|
54
|
+
| Rich HTML interface | Webview Panel | Full HTML/CSS/JS, but heavier and needs CSP |
|
|
55
|
+
| Persistent status info | StatusBarItem | Always visible, clickable, lightweight |
|
|
56
|
+
| Simple text input | InputBox | Single-line input with validation |
|
|
57
|
+
| File/folder selection | showOpenDialog | Native OS file picker |
|
|
58
|
+
| Background task progress | withProgress | Notification or status bar progress |
|
|
59
|
+
|
|
60
|
+
## Key Patterns
|
|
61
|
+
|
|
62
|
+
### Lazy Activation
|
|
63
|
+
- Use `activationEvents` in `package.json` to defer activation
|
|
64
|
+
- Since VS Code 1.74+, commands in `contributes.commands` auto-generate `onCommand:` events
|
|
65
|
+
- Prefer specific events (`onLanguage:python`, `onView:myTreeView`) over `*`
|
|
66
|
+
- See `references/package-json-schema.md` for full activationEvents reference
|
|
67
|
+
|
|
68
|
+
### Disposable Management
|
|
69
|
+
- Push ALL subscriptions to `context.subscriptions` in `activate()`
|
|
70
|
+
- Use `deactivate()` only for async cleanup (closing connections, stopping servers)
|
|
71
|
+
- Never rely on garbage collection — always dispose explicitly
|
|
72
|
+
- See `references/api-patterns.md` for the cleanup pattern
|
|
73
|
+
|
|
74
|
+
### withProgress for Async Operations
|
|
75
|
+
- Use `ProgressLocation.Notification` for user-facing tasks
|
|
76
|
+
- Use `ProgressLocation.Window` for status bar progress
|
|
77
|
+
- Support cancellation via `CancellationToken`
|
|
78
|
+
- See `references/api-patterns.md` for working examples
|
|
79
|
+
|
|
80
|
+
### SecretStorage for Credentials
|
|
81
|
+
- Use `context.secrets` (SecretStorage API) — never store tokens in settings
|
|
82
|
+
- Fires `onDidChange` event when secrets change
|
|
83
|
+
- See `references/api-patterns.md` for the credential manager pattern
|
|
84
|
+
|
|
85
|
+
### Webview CSP and PostMessage
|
|
86
|
+
- Always set a Content Security Policy with nonce
|
|
87
|
+
- Use `webview.asWebviewUri()` for local resources
|
|
88
|
+
- Bidirectional communication via `postMessage` / `onDidReceiveMessage`
|
|
89
|
+
- See `references/api-patterns.md` for the full Webview pattern
|
|
90
|
+
|
|
91
|
+
### esbuild Bundling
|
|
92
|
+
- Bundle extension into a single file for faster activation
|
|
93
|
+
- Mark `vscode` as external (it's provided by the runtime)
|
|
94
|
+
- See `references/package-json-schema.md` for scripts configuration
|
|
95
|
+
|
|
96
|
+
## Reference Files
|
|
97
|
+
|
|
98
|
+
| File | Topics |
|
|
99
|
+
| ----------------------------------- | ------------------------------------------------------------------- |
|
|
100
|
+
| `references/package-json-schema.md` | contributes, activationEvents, engines, scripts, devDependencies |
|
|
101
|
+
| `references/api-patterns.md` | TreeView, Webview, QuickPick, StatusBar, SecretStorage, withProgress |
|
|
102
|
+
| `references/architecture.md` | Project structure, layered architecture, testing strategy |
|
|
103
|
+
| `references/publishing.md` | vsce, .vscodeignore, CI/CD, Open VSX, versioning |
|
|
104
|
+
|
|
105
|
+
## Anti-Patterns to Avoid
|
|
106
|
+
|
|
107
|
+
- Using `*` activation event in production (activates on every VS Code start)
|
|
108
|
+
- Storing secrets in `configuration` instead of `SecretStorage`
|
|
109
|
+
- Forgetting to dispose subscriptions (causes memory leaks)
|
|
110
|
+
- Missing CSP in Webviews (security vulnerability)
|
|
111
|
+
- Bundling `node_modules` instead of using esbuild/webpack
|
|
112
|
+
- Using synchronous file I/O in the extension host (blocks the UI)
|
|
113
|
+
- Registering commands without corresponding `contributes.commands` entries
|
|
114
|
+
- Hardcoding `vscode.workspace.rootPath` (deprecated — use `workspaceFolders`)
|