@hegemonart/get-design-done 1.28.6 → 1.28.8
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 +81 -0
- package/README.de.md +14 -0
- package/README.fr.md +14 -0
- package/README.it.md +14 -0
- package/README.ja.md +14 -0
- package/README.ko.md +14 -0
- package/README.md +18 -0
- package/README.zh-CN.md +14 -0
- package/SKILL.md +10 -10
- package/package.json +3 -1
- package/scripts/build-distribution-bundles.cjs +549 -0
- package/scripts/install.cjs +68 -0
- package/scripts/lib/install/config-dir.cjs +26 -0
- package/scripts/lib/install/converters/antigravity.cjs +48 -0
- package/scripts/lib/install/converters/augment.cjs +68 -0
- package/scripts/lib/install/converters/cline.cjs +206 -0
- package/scripts/lib/install/converters/codebuddy.cjs +55 -0
- package/scripts/lib/install/converters/codex-plugin.cjs +407 -0
- package/scripts/lib/install/converters/codex.cjs +61 -0
- package/scripts/lib/install/converters/copilot.cjs +47 -0
- package/scripts/lib/install/converters/cursor-marketplace.cjs +309 -0
- package/scripts/lib/install/converters/cursor.cjs +49 -0
- package/scripts/lib/install/converters/gemini.cjs +116 -0
- package/scripts/lib/install/converters/kilo.cjs +62 -0
- package/scripts/lib/install/converters/opencode.cjs +64 -0
- package/scripts/lib/install/converters/qwen.cjs +51 -0
- package/scripts/lib/install/converters/shared.cjs +377 -0
- package/scripts/lib/install/converters/trae.cjs +47 -0
- package/scripts/lib/install/converters/windsurf.cjs +47 -0
- package/scripts/lib/install/doctor-codex-plugin.cjs +388 -0
- package/scripts/lib/install/doctor-cursor-marketplace.cjs +366 -0
- package/scripts/lib/install/doctor-tier2.cjs +586 -0
- package/scripts/lib/install/installer.cjs +529 -47
- package/scripts/lib/install/merge.cjs +31 -1
- package/scripts/lib/install/runtime-artifact-layout.cjs +431 -0
- package/scripts/lib/install/runtime-homes.cjs +225 -0
- package/scripts/lib/install/runtime-slash.cjs +172 -0
- package/scripts/lib/install/runtimes.cjs +73 -32
- package/scripts/lint-agentskills-spec.cjs +457 -0
- package/skills/compare/SKILL.md +2 -2
- package/skills/compare/compare-rubric.md +1 -1
- package/skills/darkmode/SKILL.md +2 -2
- package/skills/darkmode/darkmode-audit-procedure.md +1 -1
- package/skills/figma-write/SKILL.md +2 -2
- package/skills/graphify/SKILL.md +2 -2
- package/skills/style/SKILL.md +2 -2
- package/skills/style/style-doc-procedure.md +1 -1
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* scripts/lint-agentskills-spec.cjs — agentskills.io spec lint.
|
|
5
|
+
*
|
|
6
|
+
* Phase 28.8 Plan 28-8-A1 (D-13 `lint-only` outcome).
|
|
7
|
+
* See .planning/research/agentskills-io-2026-05-19.md § Implementation Implications
|
|
8
|
+
* → Plan 28-8-A1 — what to ship — for the source-of-truth rule list.
|
|
9
|
+
*
|
|
10
|
+
* Walks `skills/<name>/SKILL.md` and applies the following rules per skill:
|
|
11
|
+
*
|
|
12
|
+
* R1 (FAIL) — frontmatter contains a non-empty `name`.
|
|
13
|
+
* R2 (FAIL) — `name` matches /^[a-z0-9]+(-[a-z0-9]+)*$/ AND length ≤ 64.
|
|
14
|
+
* R3 (FAIL) — `name` matches the parent directory (allow bare slug OR `gdd-`-prefixed
|
|
15
|
+
* slug, because source-tree uses bare and install-tree uses prefixed per
|
|
16
|
+
* Phase 28.7 D-05).
|
|
17
|
+
* R4 (FAIL) — `description` is non-empty AND ≤ 1024 chars (spec hard cap).
|
|
18
|
+
* R5 (FAIL) — SKILL.md body line count ≤ 500 (spec readability guidance).
|
|
19
|
+
*
|
|
20
|
+
* W1 (WARN) — both `tools` and `allowed-tools` are present. `allowed-tools` is marked
|
|
21
|
+
* Experimental in the spec; pick one form to avoid drift.
|
|
22
|
+
* W2 (WARN) — description length > 200 chars (Phase 28.5 D-01 advisory; distinct
|
|
23
|
+
* from R4's 1024-char hard cap).
|
|
24
|
+
* W3 — reserved slot (covered today by R2). No emission.
|
|
25
|
+
*
|
|
26
|
+
* CLI:
|
|
27
|
+
* node scripts/lint-agentskills-spec.cjs # default: lint ./skills
|
|
28
|
+
* node scripts/lint-agentskills-spec.cjs <dir> # lint <dir>/<name>/SKILL.md
|
|
29
|
+
* node scripts/lint-agentskills-spec.cjs --json # emit JSON instead of table
|
|
30
|
+
* node scripts/lint-agentskills-spec.cjs --summary # one-line PASS/WARN/FAIL counts
|
|
31
|
+
* node scripts/lint-agentskills-spec.cjs --summary --json
|
|
32
|
+
* # JSON {pass,warn,fail} counts
|
|
33
|
+
*
|
|
34
|
+
* Exit codes:
|
|
35
|
+
* 0 — no FAIL rows (WARN rows do NOT fail the run)
|
|
36
|
+
* 1 — at least one FAIL row
|
|
37
|
+
* 2 — internal error (I/O failure, parse exception, bad CLI arg)
|
|
38
|
+
*
|
|
39
|
+
* Empty / missing skills directory:
|
|
40
|
+
* Prints `Lint: no skills found at <dir> — nothing to lint.` and exits 0.
|
|
41
|
+
* Under `--summary`, prints `PASS=0 WARN=0 FAIL=0` (or `{"pass":0,"warn":0,"fail":0}`
|
|
42
|
+
* with `--summary --json`) and exits 0 — empty dirs never fail the run.
|
|
43
|
+
*
|
|
44
|
+
* Exports (for tests + Plan 28-8-X2 in-process consumption):
|
|
45
|
+
* lint(skillsDir, opts?) → { rows, summary, emptyDir }
|
|
46
|
+
* lintSummary({sourceRoot}) → { pass, warn, fail } // Plan 28-8-X2 doctor seam
|
|
47
|
+
* main(argv) → number (exit code; pure — does NOT call process.exit)
|
|
48
|
+
* parseFrontmatter(content) → { frontmatter, body, hasFrontmatter }
|
|
49
|
+
* lintSkill(skillDir, skillName) → Array<{status, skill, rule, detail}>
|
|
50
|
+
*
|
|
51
|
+
* Plan 28-8-X2 wiring:
|
|
52
|
+
* `lintSummary` is consumed in-process by `scripts/lib/install/doctor-tier2.cjs`
|
|
53
|
+
* (Tier-2 doctor aggregator). It returns `{pass, warn, fail}` counts only — no
|
|
54
|
+
* table, no JSON, no IO beyond fs.readFileSync of SKILL.md files. The doctor wraps
|
|
55
|
+
* it as the agentskills.io channel state per D-13 (lint-only adoption).
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
const fs = require('fs');
|
|
59
|
+
const path = require('path');
|
|
60
|
+
|
|
61
|
+
const NAME_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
62
|
+
const NAME_MAX = 64;
|
|
63
|
+
const DESC_MAX = 1024;
|
|
64
|
+
const DESC_ADVISORY = 200;
|
|
65
|
+
const BODY_MAX_LINES = 500;
|
|
66
|
+
const DETAIL_TRUNCATE = 100;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parse YAML-ish frontmatter at the top of a markdown document.
|
|
70
|
+
*
|
|
71
|
+
* Zero-dep: handles only what our Phase 28.5 frontmatter contract emits:
|
|
72
|
+
* - leading `---\n` block delimited by `\n---\n`
|
|
73
|
+
* - scalar `key: value` lines (no nested maps, no arrays)
|
|
74
|
+
* - surrounding single or double quotes stripped from value
|
|
75
|
+
* - for values containing colons (URLs in description), the substring after the FIRST `:`
|
|
76
|
+
* is taken — so `description: "https://example.com"` works.
|
|
77
|
+
*
|
|
78
|
+
* Returns:
|
|
79
|
+
* { frontmatter: object, body: string, hasFrontmatter: boolean }
|
|
80
|
+
*
|
|
81
|
+
* If the opening delimiter is missing or the closing delimiter is not found, returns
|
|
82
|
+
* `hasFrontmatter: false` with `frontmatter: {}` and `body` set to the original content.
|
|
83
|
+
*/
|
|
84
|
+
function parseFrontmatter(content) {
|
|
85
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
86
|
+
if (!match) {
|
|
87
|
+
return { frontmatter: {}, body: content, hasFrontmatter: false };
|
|
88
|
+
}
|
|
89
|
+
const block = match[1];
|
|
90
|
+
const body = match[2];
|
|
91
|
+
const frontmatter = {};
|
|
92
|
+
const lines = block.split(/\r?\n/);
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
if (!line.trim()) continue;
|
|
95
|
+
const colonIdx = line.indexOf(':');
|
|
96
|
+
if (colonIdx < 0) continue;
|
|
97
|
+
const key = line.slice(0, colonIdx).trim();
|
|
98
|
+
let value = line.slice(colonIdx + 1).trim();
|
|
99
|
+
if (
|
|
100
|
+
value.length >= 2 &&
|
|
101
|
+
((value.startsWith('"') && value.endsWith('"')) ||
|
|
102
|
+
(value.startsWith("'") && value.endsWith("'")))
|
|
103
|
+
) {
|
|
104
|
+
value = value.slice(1, -1);
|
|
105
|
+
}
|
|
106
|
+
frontmatter[key] = value;
|
|
107
|
+
}
|
|
108
|
+
return { frontmatter, body, hasFrontmatter: true };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Lint a single skill directory. Returns one or more rows.
|
|
113
|
+
*
|
|
114
|
+
* Row shape: { status: 'PASS'|'WARN'|'FAIL', skill: string, rule: string, detail: string }
|
|
115
|
+
*
|
|
116
|
+
* If no rule fires, returns a single PASS row with rule='-' and detail='-'.
|
|
117
|
+
*/
|
|
118
|
+
function lintSkill(skillDir, skillName) {
|
|
119
|
+
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
120
|
+
let content;
|
|
121
|
+
try {
|
|
122
|
+
content = fs.readFileSync(skillPath, 'utf8');
|
|
123
|
+
} catch (err) {
|
|
124
|
+
if (err && err.code === 'ENOENT') {
|
|
125
|
+
return [
|
|
126
|
+
{
|
|
127
|
+
status: 'FAIL',
|
|
128
|
+
skill: skillName,
|
|
129
|
+
rule: 'IO',
|
|
130
|
+
detail: 'SKILL.md not found',
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
138
|
+
const rows = [];
|
|
139
|
+
|
|
140
|
+
const name = (frontmatter.name || '').trim();
|
|
141
|
+
const description = (frontmatter.description || '').trim();
|
|
142
|
+
const hasTools = Object.prototype.hasOwnProperty.call(frontmatter, 'tools');
|
|
143
|
+
const hasAllowedTools = Object.prototype.hasOwnProperty.call(frontmatter, 'allowed-tools');
|
|
144
|
+
|
|
145
|
+
// R1
|
|
146
|
+
if (!name) {
|
|
147
|
+
rows.push({
|
|
148
|
+
status: 'FAIL',
|
|
149
|
+
skill: skillName,
|
|
150
|
+
rule: 'R1',
|
|
151
|
+
detail: 'frontmatter missing or empty `name`',
|
|
152
|
+
});
|
|
153
|
+
} else {
|
|
154
|
+
// R2
|
|
155
|
+
if (!NAME_REGEX.test(name) || name.length > NAME_MAX) {
|
|
156
|
+
rows.push({
|
|
157
|
+
status: 'FAIL',
|
|
158
|
+
skill: skillName,
|
|
159
|
+
rule: 'R2',
|
|
160
|
+
detail: `name "${name}" fails slug regex /^[a-z0-9]+(-[a-z0-9]+)*$/ or exceeds ${NAME_MAX} chars`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
// R3 — name must match parent dir (bare or gdd-prefixed)
|
|
164
|
+
if (
|
|
165
|
+
name !== skillName &&
|
|
166
|
+
name !== `gdd-${skillName}` &&
|
|
167
|
+
skillName !== `gdd-${name}`
|
|
168
|
+
) {
|
|
169
|
+
rows.push({
|
|
170
|
+
status: 'FAIL',
|
|
171
|
+
skill: skillName,
|
|
172
|
+
rule: 'R3',
|
|
173
|
+
detail: `name "${name}" does not match parent dir "${skillName}" (allowed: bare slug or gdd-prefixed slug per Phase 28.7 D-05)`,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// R4
|
|
179
|
+
if (!description) {
|
|
180
|
+
rows.push({
|
|
181
|
+
status: 'FAIL',
|
|
182
|
+
skill: skillName,
|
|
183
|
+
rule: 'R4',
|
|
184
|
+
detail: 'description missing or empty',
|
|
185
|
+
});
|
|
186
|
+
} else if (description.length > DESC_MAX) {
|
|
187
|
+
rows.push({
|
|
188
|
+
status: 'FAIL',
|
|
189
|
+
skill: skillName,
|
|
190
|
+
rule: 'R4',
|
|
191
|
+
detail: `description: ${description.length} chars (>${DESC_MAX} hard cap)`,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// R5 — body line count
|
|
196
|
+
const bodyLines = body ? body.split(/\r?\n/).length : 0;
|
|
197
|
+
if (bodyLines > BODY_MAX_LINES) {
|
|
198
|
+
rows.push({
|
|
199
|
+
status: 'FAIL',
|
|
200
|
+
skill: skillName,
|
|
201
|
+
rule: 'R5',
|
|
202
|
+
detail: `body ${bodyLines} lines (>${BODY_MAX_LINES} spec guidance)`,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// W1 — both tools and allowed-tools present
|
|
207
|
+
if (hasTools && hasAllowedTools) {
|
|
208
|
+
rows.push({
|
|
209
|
+
status: 'WARN',
|
|
210
|
+
skill: skillName,
|
|
211
|
+
rule: 'W1',
|
|
212
|
+
detail: '`tools` and `allowed-tools` both present; spec marks `allowed-tools` Experimental — pick one to avoid drift',
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// W2 — description over advisory cap but under hard cap
|
|
217
|
+
if (
|
|
218
|
+
description &&
|
|
219
|
+
description.length > DESC_ADVISORY &&
|
|
220
|
+
description.length <= DESC_MAX
|
|
221
|
+
) {
|
|
222
|
+
rows.push({
|
|
223
|
+
status: 'WARN',
|
|
224
|
+
skill: skillName,
|
|
225
|
+
rule: 'W2',
|
|
226
|
+
detail: `description: ${description.length} chars (>${DESC_ADVISORY} advisory cap, Phase 28.5 D-01)`,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// W3 — reserved (covered by R2).
|
|
231
|
+
|
|
232
|
+
if (rows.length === 0) {
|
|
233
|
+
rows.push({
|
|
234
|
+
status: 'PASS',
|
|
235
|
+
skill: skillName,
|
|
236
|
+
rule: '-',
|
|
237
|
+
detail: '-',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return rows;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Walk a skills directory and lint each child subdirectory containing SKILL.md.
|
|
245
|
+
*
|
|
246
|
+
* Returns:
|
|
247
|
+
* { rows: Array, summary: {total, pass, warn, fail}, emptyDir: boolean }
|
|
248
|
+
*/
|
|
249
|
+
function lint(skillsDir, opts) {
|
|
250
|
+
const _opts = opts || {};
|
|
251
|
+
if (!fs.existsSync(skillsDir)) {
|
|
252
|
+
return {
|
|
253
|
+
rows: [],
|
|
254
|
+
summary: { total: 0, pass: 0, warn: 0, fail: 0 },
|
|
255
|
+
emptyDir: true,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
let stat;
|
|
259
|
+
try {
|
|
260
|
+
stat = fs.statSync(skillsDir);
|
|
261
|
+
} catch (err) {
|
|
262
|
+
return {
|
|
263
|
+
rows: [],
|
|
264
|
+
summary: { total: 0, pass: 0, warn: 0, fail: 0 },
|
|
265
|
+
emptyDir: true,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (!stat.isDirectory()) {
|
|
269
|
+
return {
|
|
270
|
+
rows: [],
|
|
271
|
+
summary: { total: 0, pass: 0, warn: 0, fail: 0 },
|
|
272
|
+
emptyDir: true,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
277
|
+
const skillDirs = entries
|
|
278
|
+
.filter((e) => e.isDirectory())
|
|
279
|
+
.map((e) => e.name)
|
|
280
|
+
.filter((name) => fs.existsSync(path.join(skillsDir, name, 'SKILL.md')))
|
|
281
|
+
.sort();
|
|
282
|
+
|
|
283
|
+
if (skillDirs.length === 0) {
|
|
284
|
+
return {
|
|
285
|
+
rows: [],
|
|
286
|
+
summary: { total: 0, pass: 0, warn: 0, fail: 0 },
|
|
287
|
+
emptyDir: true,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const rows = [];
|
|
292
|
+
for (const skillName of skillDirs) {
|
|
293
|
+
const skillDir = path.join(skillsDir, skillName);
|
|
294
|
+
const skillRows = lintSkill(skillDir, skillName);
|
|
295
|
+
for (const row of skillRows) rows.push(row);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const summary = {
|
|
299
|
+
total: skillDirs.length,
|
|
300
|
+
pass: rows.filter((r) => r.status === 'PASS').length,
|
|
301
|
+
warn: rows.filter((r) => r.status === 'WARN').length,
|
|
302
|
+
fail: rows.filter((r) => r.status === 'FAIL').length,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
return { rows, summary, emptyDir: false };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Plan 28-8-X2 seam — return only the PASS/WARN/FAIL counts as a flat object.
|
|
310
|
+
*
|
|
311
|
+
* Consumed in-process by `scripts/lib/install/doctor-tier2.cjs` (Tier-2 doctor
|
|
312
|
+
* aggregator). Wraps `lint()` and projects its `summary` onto a 3-field shape
|
|
313
|
+
* matching the X2 interface contract: `{ pass, warn, fail }`. Empty dirs
|
|
314
|
+
* yield `{ pass: 0, warn: 0, fail: 0 }`. The `total` field is dropped — callers
|
|
315
|
+
* compute it from `pass + warn + fail` if needed, matching D-13 lint-only contract.
|
|
316
|
+
*
|
|
317
|
+
* Pure: no IO beyond what `lint()` already does. Never calls process.exit.
|
|
318
|
+
*
|
|
319
|
+
* @param {{ sourceRoot?: string }} [opts] Optional. `sourceRoot` is the directory
|
|
320
|
+
* containing `skills/` (NOT the skills/
|
|
321
|
+
* directory itself — matches Phase 28.7
|
|
322
|
+
* `findInstallSourceRoot()` return contract).
|
|
323
|
+
* Defaults to `process.cwd()` when omitted.
|
|
324
|
+
* @returns {{ pass: number, warn: number, fail: number }}
|
|
325
|
+
*/
|
|
326
|
+
function lintSummary(opts) {
|
|
327
|
+
const _opts = opts || {};
|
|
328
|
+
const sourceRoot = _opts.sourceRoot || process.cwd();
|
|
329
|
+
const skillsDir = path.join(sourceRoot, 'skills');
|
|
330
|
+
const result = lint(skillsDir);
|
|
331
|
+
return {
|
|
332
|
+
pass: result.summary.pass,
|
|
333
|
+
warn: result.summary.warn,
|
|
334
|
+
fail: result.summary.fail,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Format the row set as an aligned plain-text table.
|
|
340
|
+
*
|
|
341
|
+
* Columns: STATUS SKILL RULE DETAIL
|
|
342
|
+
* DETAIL is truncated to DETAIL_TRUNCATE chars with a `…` suffix for terminal sanity.
|
|
343
|
+
*/
|
|
344
|
+
function formatTable(rows) {
|
|
345
|
+
const headers = ['STATUS', 'SKILL', 'RULE', 'DETAIL'];
|
|
346
|
+
const display = rows.map((r) => {
|
|
347
|
+
let detail = String(r.detail);
|
|
348
|
+
if (detail.length > DETAIL_TRUNCATE) {
|
|
349
|
+
detail = detail.slice(0, DETAIL_TRUNCATE) + '…';
|
|
350
|
+
}
|
|
351
|
+
return [r.status, r.skill, r.rule, detail];
|
|
352
|
+
});
|
|
353
|
+
const widths = headers.map((h, i) =>
|
|
354
|
+
Math.max(h.length, ...display.map((row) => row[i].length))
|
|
355
|
+
);
|
|
356
|
+
const pad = (cells) =>
|
|
357
|
+
cells.map((c, i) => c.padEnd(widths[i])).join(' ');
|
|
358
|
+
const sep = widths.map((w) => '-'.repeat(w)).join(' ');
|
|
359
|
+
const out = [pad(headers), sep];
|
|
360
|
+
for (const row of display) out.push(pad(row));
|
|
361
|
+
return out.join('\n');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Main CLI entry. Pure — returns exit code rather than calling process.exit.
|
|
366
|
+
*
|
|
367
|
+
* argv is the argv slice AFTER node + script (i.e. process.argv.slice(2)).
|
|
368
|
+
*/
|
|
369
|
+
function main(argv) {
|
|
370
|
+
try {
|
|
371
|
+
let skillsDir = './skills';
|
|
372
|
+
let jsonMode = false;
|
|
373
|
+
let summaryMode = false;
|
|
374
|
+
for (const arg of argv) {
|
|
375
|
+
if (arg === '--json') {
|
|
376
|
+
jsonMode = true;
|
|
377
|
+
} else if (arg === '--summary') {
|
|
378
|
+
summaryMode = true;
|
|
379
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
380
|
+
process.stdout.write(
|
|
381
|
+
'lint-agentskills-spec.cjs — agentskills.io spec lint over skills/<name>/SKILL.md\n' +
|
|
382
|
+
'\n' +
|
|
383
|
+
'Usage:\n' +
|
|
384
|
+
' node scripts/lint-agentskills-spec.cjs [<dir>] [--json]\n' +
|
|
385
|
+
' node scripts/lint-agentskills-spec.cjs [<dir>] --summary [--json]\n' +
|
|
386
|
+
'\n' +
|
|
387
|
+
'Modes:\n' +
|
|
388
|
+
' default Aligned table of all rows + final summary line\n' +
|
|
389
|
+
' --json JSON {rows, summary} object\n' +
|
|
390
|
+
' --summary One-line `PASS=N WARN=N FAIL=N`\n' +
|
|
391
|
+
' --summary --json JSON {pass, warn, fail} (Plan 28-8-X2 seam)\n' +
|
|
392
|
+
'\n' +
|
|
393
|
+
'Exit codes:\n' +
|
|
394
|
+
' 0 no FAIL rows (WARN rows do NOT fail the run)\n' +
|
|
395
|
+
' 1 at least one FAIL row\n' +
|
|
396
|
+
' 2 internal error\n'
|
|
397
|
+
);
|
|
398
|
+
return 0;
|
|
399
|
+
} else if (arg.startsWith('--')) {
|
|
400
|
+
process.stderr.write(`lint-agentskills-spec: unknown flag: ${arg}\n`);
|
|
401
|
+
return 2;
|
|
402
|
+
} else {
|
|
403
|
+
skillsDir = arg;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const result = lint(skillsDir);
|
|
408
|
+
|
|
409
|
+
// Plan 28-8-X2 — --summary mode short-circuits the table renderer for
|
|
410
|
+
// doctor-tier2 callers. Empty dir is treated as 0-everything (exits 0)
|
|
411
|
+
// matching the default-mode "no skills found" exit-0 contract.
|
|
412
|
+
if (summaryMode) {
|
|
413
|
+
const pass = result.summary.pass || 0;
|
|
414
|
+
const warn = result.summary.warn || 0;
|
|
415
|
+
const fail = result.summary.fail || 0;
|
|
416
|
+
if (jsonMode) {
|
|
417
|
+
process.stdout.write(JSON.stringify({ pass, warn, fail }) + '\n');
|
|
418
|
+
} else {
|
|
419
|
+
process.stdout.write(`PASS=${pass} WARN=${warn} FAIL=${fail}\n`);
|
|
420
|
+
}
|
|
421
|
+
return fail > 0 ? 1 : 0;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (result.emptyDir) {
|
|
425
|
+
process.stdout.write(
|
|
426
|
+
`Lint: no skills found at ${skillsDir} — nothing to lint.\n`
|
|
427
|
+
);
|
|
428
|
+
return 0;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (jsonMode) {
|
|
432
|
+
process.stdout.write(
|
|
433
|
+
JSON.stringify({ rows: result.rows, summary: result.summary }, null, 2) +
|
|
434
|
+
'\n'
|
|
435
|
+
);
|
|
436
|
+
} else {
|
|
437
|
+
process.stdout.write(formatTable(result.rows) + '\n');
|
|
438
|
+
const { total, pass, warn, fail } = result.summary;
|
|
439
|
+
process.stdout.write(
|
|
440
|
+
`\nLint summary: ${total} skills, ${pass} PASS, ${warn} WARN, ${fail} FAIL\n`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return result.summary.fail > 0 ? 1 : 0;
|
|
445
|
+
} catch (err) {
|
|
446
|
+
process.stderr.write(
|
|
447
|
+
`lint-agentskills-spec: internal error: ${err && err.message ? err.message : err}\n`
|
|
448
|
+
);
|
|
449
|
+
return 2;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (require.main === module) {
|
|
454
|
+
process.exit(main(process.argv.slice(2)));
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
module.exports = { lint, lintSummary, main, parseFrontmatter, lintSkill };
|
package/skills/compare/SKILL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: gdd-compare
|
|
3
3
|
description: "Compute the delta between the `DESIGN.md` baseline (from scan) and the `DESIGN-VERIFICATION.md` result (from verify), reporting per-category score delta, anti-pattern delta (resolved vs new), must-have pass/fail change, and design drift (regressions without covering tasks in `DESIGN-PLAN.md`). Use after `verify` to measure whether a design pipeline cycle actually improved the design. Writes `.design/COMPARE-REPORT.md`."
|
|
4
4
|
argument-hint: ""
|
|
5
5
|
user-invocable: true
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# gdd-compare — Baseline vs Result Delta
|
|
9
9
|
|
|
10
10
|
Standalone delta command. Computes the difference between the scan baseline (`DESIGN.md`) and the verification result (`DESIGN-VERIFICATION.md`), and flags design drift for any regression not covered by an explicit task in `DESIGN-PLAN.md`. Writes one artifact: `.design/COMPARE-REPORT.md`.
|
|
11
11
|
|
|
@@ -14,7 +14,7 @@ and the `COMPARE-REPORT.md` template the skill writes.
|
|
|
14
14
|
|
|
15
15
|
# Compare Rubric — Baseline vs Result Delta Methodology
|
|
16
16
|
|
|
17
|
-
Detailed methodology for the `
|
|
17
|
+
Detailed methodology for the `gdd-compare` standalone command — companion to
|
|
18
18
|
`../skills/compare/SKILL.md`. Read this file when executing a specific compare step (score
|
|
19
19
|
delta math, anti-pattern set arithmetic, drift coverage map, report layout). The SKILL.md
|
|
20
20
|
keeps the load-bearing pre-flight checks + step routing; this file holds the deep methodology.
|
package/skills/darkmode/SKILL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: gdd-darkmode
|
|
3
3
|
description: "Audit a project's dark mode implementation — detects architecture (CSS custom props, Tailwind `dark:` prefix, or JS class toggle), runs architecture-specific contrast / token-override / anti-pattern / meta-property checks, and writes a prioritized fix list to `.design/DARKMODE-AUDIT.md`. Use when the user wants to verify dark mode quality without re-running the full design pipeline. Read-only — no score writeback to `DESIGN.md`."
|
|
4
4
|
argument-hint: ""
|
|
5
5
|
user-invocable: true
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# gdd-darkmode — Dark Mode Audit
|
|
9
9
|
|
|
10
10
|
Standalone dark mode audit. Detects the project's dark mode architecture, runs architecture-specific checks across contrast, token completeness, anti-patterns, and meta properties, then writes a prioritized fix list to `.design/DARKMODE-AUDIT.md`.
|
|
11
11
|
|
|
@@ -14,7 +14,7 @@ snippets, and the `DARKMODE-AUDIT.md` report template.
|
|
|
14
14
|
|
|
15
15
|
# Dark Mode Audit Procedure
|
|
16
16
|
|
|
17
|
-
Detailed procedure for the `
|
|
17
|
+
Detailed procedure for the `gdd-darkmode` standalone audit — companion to
|
|
18
18
|
`../skills/darkmode/SKILL.md`. Read this file when executing a specific audit step
|
|
19
19
|
(architecture detection, contrast computation, anti-pattern grep, report layout). The
|
|
20
20
|
SKILL.md keeps the load-bearing pre-flight + step routing; this file holds the deep
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: gdd-figma-write
|
|
3
3
|
description: "Write design decisions from `.design/DESIGN-CONTEXT.md` back into the active Figma file by dispatching the `design-figma-writer` agent in one of three modes (annotate / tokenize / mappings). Use when the user has completed a design pipeline cycle and wants the decisions (layer comments, variable bindings, or Code Connect mappings) reflected in Figma. Operates proposal→confirm with `--dry-run` and `--confirm-shared` flags."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# gdd-figma-write
|
|
7
7
|
|
|
8
8
|
Dispatches the `design-figma-writer` agent to write design decisions back to the open Figma file. The shared probe pattern (ToolSearch → live call → STATE.md write) and connection handshake are documented at `../../reference/shared-preamble.md#connection-handshake-summary` and `../../connections/figma.md`.
|
|
9
9
|
|
package/skills/graphify/SKILL.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: gdd-graphify
|
|
3
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
|
|
7
7
|
|
|
8
8
|
Thin command wrapper around the GSD graphify tools integration.
|
|
9
9
|
|
package/skills/style/SKILL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: gdd-style
|
|
3
3
|
description: "Generate a component handoff doc at `.design/DESIGN-STYLE-<ComponentName>.md` by dispatching the `design-doc-writer` agent in one of two modes: post-pipeline (uses `DESIGN-SUMMARY.md`) or pre-pipeline fallback (uses `DESIGN.md` + source). Use when the user wants a single-component spec covering tokens, states, and AI-slop detection. Invoke with a ComponentName, or with no argument to list available components."
|
|
4
4
|
argument-hint: "[ComponentName]"
|
|
5
5
|
user-invocable: true
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# gdd-style — Component Handoff Doc Generator
|
|
9
9
|
|
|
10
10
|
Generates a per-component style spec at `.design/DESIGN-STYLE-[ComponentName].md`. This is a **standalone command**, not a pipeline stage.
|
|
11
11
|
|
|
@@ -15,7 +15,7 @@ the cross-skill output discipline.
|
|
|
15
15
|
|
|
16
16
|
# Style Doc Procedure
|
|
17
17
|
|
|
18
|
-
Detailed procedure for the `
|
|
18
|
+
Detailed procedure for the `gdd-style` standalone command — companion to
|
|
19
19
|
`../skills/style/SKILL.md`. Read this file when executing the agent-spawn step (Step 4 in the
|
|
20
20
|
skill) or when wiring the source-resolution fallback chain. The SKILL.md keeps the load-bearing
|
|
21
21
|
mode detection + decision tree; this file holds the deep methodology.
|