@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,366 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* scripts/lib/install/doctor-cursor-marketplace.cjs — Phase 28.8 (Plan B2).
|
|
5
|
+
*
|
|
6
|
+
* Cursor Marketplace doctor-mode reporter. Pure, read-only function that
|
|
7
|
+
* surfaces the maintainer's local Cursor Marketplace publish state to
|
|
8
|
+
* `scripts/install.cjs --doctor`.
|
|
9
|
+
*
|
|
10
|
+
* Phase 28.8 D-16: Cursor Marketplace is multi-step publish (submit →
|
|
11
|
+
* review → publish). This reporter reads `.cursor-plugin/plugin.json`
|
|
12
|
+
* (shipped artifact, B1) and the maintainer-local
|
|
13
|
+
* `.cursor-plugin/marketplace-state.json` (gitignored — local-only,
|
|
14
|
+
* never committed) and emits a structured status. Read-only; no writes,
|
|
15
|
+
* no network. Tmpdir-safe per D-10.
|
|
16
|
+
*
|
|
17
|
+
* Design pattern (for Plan 28-8-C2 + 28-8-X2 to mirror): each Tier-2
|
|
18
|
+
* channel ships its own pure reporter; the aggregator in install.cjs
|
|
19
|
+
* (Plan B2 today, X2 in the final wave) composes them. B2's reporter
|
|
20
|
+
* has no dependencies on other channels' state — the aggregator is the
|
|
21
|
+
* only knowledge boundary that needs C2 + B2 awareness.
|
|
22
|
+
*
|
|
23
|
+
* Exports:
|
|
24
|
+
* - `reportCursorMarketplace({ projectRoot })` — structured status.
|
|
25
|
+
* - `MARKETPLACE_STATES` — frozen enum of the 4 D-16 status values.
|
|
26
|
+
* - `formatCursorMarketplaceReport(report)` — text formatter (also used
|
|
27
|
+
* by install.cjs --doctor; kept here so all rendering logic stays
|
|
28
|
+
* adjacent to the data shape).
|
|
29
|
+
* - `validateManifest(parsedManifest)` — light shape validator for the
|
|
30
|
+
* parsed manifest. Mirrors B1's `buildManifest` defensive throws but
|
|
31
|
+
* in inverse direction (validate parsed → not assemble from sources).
|
|
32
|
+
* Separate from B1's converter because that one constructs manifests
|
|
33
|
+
* from canonical sources; the doctor receives a possibly-stale or
|
|
34
|
+
* hand-edited manifest from disk.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const fs = require('node:fs');
|
|
38
|
+
const path = require('node:path');
|
|
39
|
+
|
|
40
|
+
const MARKETPLACE_STATES = Object.freeze({
|
|
41
|
+
NOT_SUBMITTED: 'not-submitted',
|
|
42
|
+
SUBMITTED_PENDING: 'submitted-pending',
|
|
43
|
+
APPROVED_PUBLISHED: 'approved-published',
|
|
44
|
+
REJECTED: 'rejected',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const KNOWN_STATUS_VALUES = new Set([
|
|
48
|
+
MARKETPLACE_STATES.NOT_SUBMITTED,
|
|
49
|
+
MARKETPLACE_STATES.SUBMITTED_PENDING,
|
|
50
|
+
MARKETPLACE_STATES.APPROVED_PUBLISHED,
|
|
51
|
+
MARKETPLACE_STATES.REJECTED,
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validate a parsed `.cursor-plugin/plugin.json` object against the
|
|
56
|
+
* 8-field shape B1 emits. Returns `{valid, errors}` — never throws.
|
|
57
|
+
*
|
|
58
|
+
* @param {*} parsed Parsed JSON object.
|
|
59
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
60
|
+
*/
|
|
61
|
+
function validateManifest(parsed) {
|
|
62
|
+
const errors = [];
|
|
63
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
64
|
+
return { valid: false, errors: ['manifest is not a JSON object'] };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (typeof parsed.name !== 'string' || parsed.name.length === 0) {
|
|
68
|
+
errors.push('name must be a non-empty string');
|
|
69
|
+
}
|
|
70
|
+
if (typeof parsed.description !== 'string' || parsed.description.length === 0) {
|
|
71
|
+
errors.push('description must be a non-empty string');
|
|
72
|
+
}
|
|
73
|
+
if (typeof parsed.version !== 'string' || !/^\d+\.\d+\.\d+/.test(parsed.version)) {
|
|
74
|
+
errors.push('version must be semver-shaped (x.y.z)');
|
|
75
|
+
}
|
|
76
|
+
if (
|
|
77
|
+
!parsed.author
|
|
78
|
+
|| typeof parsed.author !== 'object'
|
|
79
|
+
|| Array.isArray(parsed.author)
|
|
80
|
+
|| typeof parsed.author.name !== 'string'
|
|
81
|
+
|| parsed.author.name.length === 0
|
|
82
|
+
) {
|
|
83
|
+
errors.push('author.name must be a non-empty string');
|
|
84
|
+
}
|
|
85
|
+
if (!Array.isArray(parsed.keywords) || parsed.keywords.length === 0) {
|
|
86
|
+
errors.push('keywords must be a non-empty array');
|
|
87
|
+
} else {
|
|
88
|
+
for (const k of parsed.keywords) {
|
|
89
|
+
if (typeof k !== 'string' || k.length === 0) {
|
|
90
|
+
errors.push('keywords must contain only non-empty strings');
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Optional fields, but if present must match shape.
|
|
97
|
+
if (parsed.homepage !== undefined && typeof parsed.homepage !== 'string') {
|
|
98
|
+
errors.push('homepage must be a string if present');
|
|
99
|
+
}
|
|
100
|
+
if (parsed.repository !== undefined && typeof parsed.repository !== 'string') {
|
|
101
|
+
errors.push('repository must be a string if present');
|
|
102
|
+
}
|
|
103
|
+
if (parsed.license !== undefined && typeof parsed.license !== 'string') {
|
|
104
|
+
errors.push('license must be a string if present');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { valid: errors.length === 0, errors };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Safely read + parse a JSON file. Returns `{exists, parsed, error}`.
|
|
112
|
+
* @param {string} filePath
|
|
113
|
+
* @returns {{ exists: boolean, parsed: *, error: string|null }}
|
|
114
|
+
*/
|
|
115
|
+
function readJsonFileSafe(filePath) {
|
|
116
|
+
let raw;
|
|
117
|
+
try {
|
|
118
|
+
raw = fs.readFileSync(filePath, 'utf8');
|
|
119
|
+
} catch (e) {
|
|
120
|
+
if (e && e.code === 'ENOENT') {
|
|
121
|
+
return { exists: false, parsed: null, error: null };
|
|
122
|
+
}
|
|
123
|
+
return { exists: false, parsed: null, error: 'read failed: ' + e.message };
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
return { exists: true, parsed: JSON.parse(raw), error: null };
|
|
127
|
+
} catch (e) {
|
|
128
|
+
return { exists: true, parsed: null, error: 'JSON parse failed: ' + e.message };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Build a one-line guidance string per state.
|
|
134
|
+
* @param {{ state: string, marketplaceUrl: string|null, rejectionReason: string|null }} r
|
|
135
|
+
* @returns {string}
|
|
136
|
+
*/
|
|
137
|
+
function buildGuidance(r) {
|
|
138
|
+
switch (r.state) {
|
|
139
|
+
case MARKETPLACE_STATES.NOT_SUBMITTED:
|
|
140
|
+
return 'submit publisher application at cursor.com/marketplace/publish; see docs/cursor-marketplace-field-test.md';
|
|
141
|
+
case MARKETPLACE_STATES.SUBMITTED_PENDING:
|
|
142
|
+
return 'await Cursor team review approval; no published SLA per D-16';
|
|
143
|
+
case MARKETPLACE_STATES.APPROVED_PUBLISHED:
|
|
144
|
+
return 'plugin is live at ' + (r.marketplaceUrl || '<marketplace-url>');
|
|
145
|
+
case MARKETPLACE_STATES.REJECTED:
|
|
146
|
+
return 'address rejection reason: ' + (r.rejectionReason || '<unspecified>')
|
|
147
|
+
+ '; re-submit per docs/cursor-marketplace-field-test.md';
|
|
148
|
+
default:
|
|
149
|
+
return '';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Read-only Cursor Marketplace status reporter. Reads
|
|
155
|
+
* `.cursor-plugin/plugin.json` and `.cursor-plugin/marketplace-state.json`
|
|
156
|
+
* under `projectRoot` (no writes, no network).
|
|
157
|
+
*
|
|
158
|
+
* @param {{ projectRoot: string }} opts
|
|
159
|
+
* @returns {{
|
|
160
|
+
* state: 'not-submitted'|'submitted-pending'|'approved-published'|'rejected',
|
|
161
|
+
* manifestPresent: boolean,
|
|
162
|
+
* manifestVersion: string|null,
|
|
163
|
+
* packageVersion: string|null,
|
|
164
|
+
* versionMatch: boolean,
|
|
165
|
+
* manifestSchemaValid: boolean,
|
|
166
|
+
* manifestSchemaErrors: string[],
|
|
167
|
+
* marketplaceUrl: string|null,
|
|
168
|
+
* submittedAt: string|null,
|
|
169
|
+
* approvedAt: string|null,
|
|
170
|
+
* rejectionReason: string|null,
|
|
171
|
+
* guidance: string,
|
|
172
|
+
* }}
|
|
173
|
+
*/
|
|
174
|
+
function reportCursorMarketplace(opts) {
|
|
175
|
+
if (!opts || typeof opts !== 'object' || typeof opts.projectRoot !== 'string') {
|
|
176
|
+
throw new Error('reportCursorMarketplace: opts.projectRoot is required');
|
|
177
|
+
}
|
|
178
|
+
const projectRoot = opts.projectRoot;
|
|
179
|
+
|
|
180
|
+
const manifestPath = path.join(projectRoot, '.cursor-plugin', 'plugin.json');
|
|
181
|
+
const statePath = path.join(projectRoot, '.cursor-plugin', 'marketplace-state.json');
|
|
182
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
183
|
+
|
|
184
|
+
// 1) Manifest read
|
|
185
|
+
const manifestRead = readJsonFileSafe(manifestPath);
|
|
186
|
+
let manifestPresent = false;
|
|
187
|
+
let manifestVersion = null;
|
|
188
|
+
let manifestSchemaValid = false;
|
|
189
|
+
let manifestSchemaErrors = [];
|
|
190
|
+
if (!manifestRead.exists) {
|
|
191
|
+
manifestSchemaErrors = ['manifest absent'];
|
|
192
|
+
} else if (manifestRead.error) {
|
|
193
|
+
manifestPresent = true;
|
|
194
|
+
manifestSchemaErrors = [manifestRead.error];
|
|
195
|
+
} else {
|
|
196
|
+
manifestPresent = true;
|
|
197
|
+
const validation = validateManifest(manifestRead.parsed);
|
|
198
|
+
manifestSchemaValid = validation.valid;
|
|
199
|
+
manifestSchemaErrors = validation.errors;
|
|
200
|
+
if (typeof manifestRead.parsed.version === 'string') {
|
|
201
|
+
manifestVersion = manifestRead.parsed.version;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 2) Package version read (tolerate missing).
|
|
206
|
+
let packageVersion = null;
|
|
207
|
+
const pkgRead = readJsonFileSafe(pkgPath);
|
|
208
|
+
if (pkgRead.exists && !pkgRead.error
|
|
209
|
+
&& pkgRead.parsed && typeof pkgRead.parsed.version === 'string') {
|
|
210
|
+
packageVersion = pkgRead.parsed.version;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 3) Version match (only true when both present and equal).
|
|
214
|
+
const versionMatch = Boolean(
|
|
215
|
+
manifestVersion && packageVersion && manifestVersion === packageVersion
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// 4) State read. Maintainer-typo safety: unknown status THROWS.
|
|
219
|
+
const stateRead = readJsonFileSafe(statePath);
|
|
220
|
+
let state = MARKETPLACE_STATES.NOT_SUBMITTED;
|
|
221
|
+
let marketplaceUrl = null;
|
|
222
|
+
let submittedAt = null;
|
|
223
|
+
let approvedAt = null;
|
|
224
|
+
let rejectionReason = null;
|
|
225
|
+
|
|
226
|
+
if (stateRead.exists && stateRead.error) {
|
|
227
|
+
// Malformed JSON — surface loudly per T-04 in threat register.
|
|
228
|
+
throw new Error(
|
|
229
|
+
'cursor-marketplace doctor: marketplace-state.json malformed: '
|
|
230
|
+
+ stateRead.error
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
if (stateRead.exists && stateRead.parsed && typeof stateRead.parsed === 'object') {
|
|
234
|
+
const s = stateRead.parsed.status;
|
|
235
|
+
if (typeof s !== 'string') {
|
|
236
|
+
throw new Error(
|
|
237
|
+
'cursor-marketplace doctor: marketplace-state.json is missing "status" field'
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (!KNOWN_STATUS_VALUES.has(s)) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
'cursor-marketplace doctor: unknown marketplace-state.json status: ' + s
|
|
243
|
+
+ ' (expected one of: not-submitted, submitted-pending, approved-published, rejected)'
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
state = s;
|
|
247
|
+
if (typeof stateRead.parsed['submitted-at'] === 'string') {
|
|
248
|
+
submittedAt = stateRead.parsed['submitted-at'];
|
|
249
|
+
}
|
|
250
|
+
if (typeof stateRead.parsed['approved-at'] === 'string') {
|
|
251
|
+
approvedAt = stateRead.parsed['approved-at'];
|
|
252
|
+
}
|
|
253
|
+
if (typeof stateRead.parsed['marketplace-url'] === 'string') {
|
|
254
|
+
marketplaceUrl = stateRead.parsed['marketplace-url'];
|
|
255
|
+
}
|
|
256
|
+
if (typeof stateRead.parsed.reason === 'string') {
|
|
257
|
+
rejectionReason = stateRead.parsed.reason;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const result = {
|
|
262
|
+
state,
|
|
263
|
+
manifestPresent,
|
|
264
|
+
manifestVersion,
|
|
265
|
+
packageVersion,
|
|
266
|
+
versionMatch,
|
|
267
|
+
manifestSchemaValid,
|
|
268
|
+
manifestSchemaErrors,
|
|
269
|
+
marketplaceUrl,
|
|
270
|
+
submittedAt,
|
|
271
|
+
approvedAt,
|
|
272
|
+
rejectionReason,
|
|
273
|
+
guidance: '',
|
|
274
|
+
};
|
|
275
|
+
result.guidance = buildGuidance(result);
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Format the doctor report as multi-line text for stdout. Pure — no IO.
|
|
281
|
+
*
|
|
282
|
+
* Output shape (per plan <interfaces>):
|
|
283
|
+
*
|
|
284
|
+
* === Cursor Marketplace status ===
|
|
285
|
+
* Manifest: .cursor-plugin/plugin.json (v1.28.8) ✓ matches package.json
|
|
286
|
+
* Schema validity: valid
|
|
287
|
+
* Application: submitted-pending (submitted 2026-05-22)
|
|
288
|
+
* Next step: await Cursor team review approval; no published SLA per D-16
|
|
289
|
+
*
|
|
290
|
+
* @param {ReturnType<typeof reportCursorMarketplace>} r
|
|
291
|
+
* @returns {string}
|
|
292
|
+
*/
|
|
293
|
+
function formatCursorMarketplaceReport(r) {
|
|
294
|
+
const lines = ['=== Cursor Marketplace status ==='];
|
|
295
|
+
|
|
296
|
+
// Manifest line
|
|
297
|
+
let manifestLine;
|
|
298
|
+
if (!r.manifestPresent) {
|
|
299
|
+
manifestLine = ' Manifest: absent (-) ✗ create .cursor-plugin/plugin.json (B1)';
|
|
300
|
+
} else {
|
|
301
|
+
const ver = r.manifestVersion ? 'v' + r.manifestVersion : 'unknown';
|
|
302
|
+
let matchGlyph;
|
|
303
|
+
let matchText;
|
|
304
|
+
if (r.packageVersion === null) {
|
|
305
|
+
matchGlyph = '-';
|
|
306
|
+
matchText = 'package.json missing (no compare)';
|
|
307
|
+
} else if (r.versionMatch) {
|
|
308
|
+
matchGlyph = '✓';
|
|
309
|
+
matchText = 'matches package.json';
|
|
310
|
+
} else {
|
|
311
|
+
matchGlyph = '✗';
|
|
312
|
+
matchText = 'mismatch — package.json is v' + r.packageVersion;
|
|
313
|
+
}
|
|
314
|
+
manifestLine = ' Manifest: .cursor-plugin/plugin.json (' + ver + ') '
|
|
315
|
+
+ matchGlyph + ' ' + matchText;
|
|
316
|
+
}
|
|
317
|
+
lines.push(manifestLine);
|
|
318
|
+
|
|
319
|
+
// Schema validity line
|
|
320
|
+
let schemaLine;
|
|
321
|
+
if (!r.manifestPresent) {
|
|
322
|
+
schemaLine = ' Schema validity: -';
|
|
323
|
+
} else if (r.manifestSchemaValid) {
|
|
324
|
+
schemaLine = ' Schema validity: valid';
|
|
325
|
+
} else {
|
|
326
|
+
const errs = (r.manifestSchemaErrors || []).join('; ') || 'invalid';
|
|
327
|
+
schemaLine = ' Schema validity: invalid: ' + errs;
|
|
328
|
+
}
|
|
329
|
+
lines.push(schemaLine);
|
|
330
|
+
|
|
331
|
+
// Application line — state + context fragment
|
|
332
|
+
let appContext;
|
|
333
|
+
switch (r.state) {
|
|
334
|
+
case MARKETPLACE_STATES.NOT_SUBMITTED:
|
|
335
|
+
appContext = '-';
|
|
336
|
+
break;
|
|
337
|
+
case MARKETPLACE_STATES.SUBMITTED_PENDING:
|
|
338
|
+
appContext = r.submittedAt
|
|
339
|
+
? 'submitted ' + r.submittedAt.slice(0, 10)
|
|
340
|
+
: 'submitted-at unrecorded';
|
|
341
|
+
break;
|
|
342
|
+
case MARKETPLACE_STATES.APPROVED_PUBLISHED:
|
|
343
|
+
appContext = r.marketplaceUrl
|
|
344
|
+
? 'live at ' + r.marketplaceUrl
|
|
345
|
+
: 'live (url unrecorded)';
|
|
346
|
+
break;
|
|
347
|
+
case MARKETPLACE_STATES.REJECTED:
|
|
348
|
+
appContext = r.rejectionReason || 'reason unrecorded';
|
|
349
|
+
break;
|
|
350
|
+
default:
|
|
351
|
+
appContext = '-';
|
|
352
|
+
}
|
|
353
|
+
lines.push(' Application: ' + r.state + ' (' + appContext + ')');
|
|
354
|
+
|
|
355
|
+
// Next step / guidance
|
|
356
|
+
lines.push(' Next step: ' + (r.guidance || '-'));
|
|
357
|
+
|
|
358
|
+
return lines.join('\n');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
module.exports = {
|
|
362
|
+
reportCursorMarketplace,
|
|
363
|
+
formatCursorMarketplaceReport,
|
|
364
|
+
validateManifest,
|
|
365
|
+
MARKETPLACE_STATES,
|
|
366
|
+
};
|