@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,407 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* scripts/lib/install/converters/codex-plugin.cjs — Phase 28.8 (Plan 28-8-C1).
|
|
5
|
+
*
|
|
6
|
+
* Codex Plugin distribution-channel converter. Emits a Codex-plugin-shape
|
|
7
|
+
* bundle (`.codex-plugin/plugin.json` + verbatim-copied `skills/` tree)
|
|
8
|
+
* from our `skills/` canonical source. Consumed by Plan 28-8-X1's
|
|
9
|
+
* scripts/build-distribution-bundles.cjs (downstream — this module is
|
|
10
|
+
* the contract, X1 wires the build pipeline).
|
|
11
|
+
*
|
|
12
|
+
* Per CONTEXT D-05 (additive): this is a NEW kind, alongside the
|
|
13
|
+
* existing scripts/lib/install/converters/codex.cjs (Phase 28.7
|
|
14
|
+
* file-drop AGENTS.md surface). codex.cjs is UNCHANGED. Tier-1 and
|
|
15
|
+
* Tier-2 surfaces coexist as documented in
|
|
16
|
+
* .planning/research/codex-plugins-2026-05-19.md § vs AGENTS.md.
|
|
17
|
+
*
|
|
18
|
+
* Per CONTEXT D-06 (skills are shared source): skill content is copied
|
|
19
|
+
* verbatim during bundle emission — Codex consumes the same SKILL.md
|
|
20
|
+
* shape we already produce for Phase 28.5 authoring contract. No
|
|
21
|
+
* per-skill content rewriting in this converter.
|
|
22
|
+
*
|
|
23
|
+
* Per CONTEXT D-14 (no new catalog): we do NOT emit a Codex-specific
|
|
24
|
+
* marketplace.json — Codex's legacy-compat path consumes our existing
|
|
25
|
+
* .claude-plugin/marketplace.json directly.
|
|
26
|
+
*
|
|
27
|
+
* GDD-original pattern (no gsd-build/get-shit-done counterpart): Tier-2
|
|
28
|
+
* distribution channels do not exist in the upstream multi-runtime install
|
|
29
|
+
* reference. Mirrors the cursor-marketplace.cjs sibling (Plan 28-8-B1).
|
|
30
|
+
*
|
|
31
|
+
* Pure / side-effect-free for `buildManifest`. `convert` performs
|
|
32
|
+
* filesystem writes (it's a bundle emitter) and is the impure boundary.
|
|
33
|
+
* All test invocations use tmpdir per CONTEXT D-10.
|
|
34
|
+
*
|
|
35
|
+
* Exports:
|
|
36
|
+
* - `buildManifest(sources)` — pure function, returns the Codex manifest
|
|
37
|
+
* object ready to `JSON.stringify(obj, null, 2)`.
|
|
38
|
+
* - `convert({ skillsDir, outDir, manifest })` — file-emission function
|
|
39
|
+
* for `build-distribution-bundles.cjs`. The only side-effect surface;
|
|
40
|
+
* touches only paths under `outDir`.
|
|
41
|
+
* - `MANIFEST_REQUIRED_FIELDS` — frozen 3-tuple of required spec fields.
|
|
42
|
+
* - `CURATED_KEYWORDS` — frozen 10-tag default keyword subset.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
const fs = require('node:fs');
|
|
46
|
+
const path = require('node:path');
|
|
47
|
+
|
|
48
|
+
// Per research § Top-level fields: name, version, description are the only
|
|
49
|
+
// strictly-required spec fields. All other manifest fields are optional.
|
|
50
|
+
const MANIFEST_REQUIRED_FIELDS = Object.freeze(['name', 'version', 'description']);
|
|
51
|
+
|
|
52
|
+
// Curated keyword subset for Codex marketplace card display.
|
|
53
|
+
// Per research § Schema Mapping `keywords` row: keep to ~10 design-relevant
|
|
54
|
+
// terms (our package.json carries 50+ tags). The intersection of these tags
|
|
55
|
+
// with package.json#keywords drives `curateKeywords()` below.
|
|
56
|
+
const CURATED_KEYWORDS = Object.freeze([
|
|
57
|
+
'design',
|
|
58
|
+
'ui',
|
|
59
|
+
'ux',
|
|
60
|
+
'frontend',
|
|
61
|
+
'pipeline',
|
|
62
|
+
'design-system',
|
|
63
|
+
'accessibility',
|
|
64
|
+
'figma',
|
|
65
|
+
'wcag',
|
|
66
|
+
'agent-sdk',
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
// ── Private helpers ────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
function stripNpmScope(name) {
|
|
72
|
+
if (typeof name !== 'string') return name;
|
|
73
|
+
return name.replace(/^@[^/]+\//, '');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function stripGitSuffix(url) {
|
|
77
|
+
if (typeof url !== 'string') return url;
|
|
78
|
+
return url.replace(/\.git$/, '');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function truncate(str, n) {
|
|
82
|
+
if (typeof str !== 'string') return str;
|
|
83
|
+
if (str.length <= n) return str;
|
|
84
|
+
// Prefer ending at sentence boundary, then word boundary, then hard cut.
|
|
85
|
+
const head = str.slice(0, n);
|
|
86
|
+
const sentenceEnd = head.lastIndexOf('. ');
|
|
87
|
+
if (sentenceEnd > n * 0.6) return head.slice(0, sentenceEnd + 1);
|
|
88
|
+
const wordEnd = head.lastIndexOf(' ');
|
|
89
|
+
if (wordEnd > n * 0.6) return head.slice(0, wordEnd);
|
|
90
|
+
return head;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function capitalize(str) {
|
|
94
|
+
if (typeof str !== 'string' || str.length === 0) return str;
|
|
95
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Curate keywords to the ≤10-element CURATED_KEYWORDS intersection with
|
|
100
|
+
* the source array. If the whitelist isn't a subset of source, fall back
|
|
101
|
+
* to `source.slice(0, 10)`. Always returns a fresh array (callers may
|
|
102
|
+
* mutate without polluting the frozen module constant).
|
|
103
|
+
*/
|
|
104
|
+
function curateKeywords(arr) {
|
|
105
|
+
if (!Array.isArray(arr)) return CURATED_KEYWORDS.slice();
|
|
106
|
+
const sourceSet = new Set(arr);
|
|
107
|
+
const intersected = CURATED_KEYWORDS.filter((k) => sourceSet.has(k));
|
|
108
|
+
if (intersected.length > 0) return intersected;
|
|
109
|
+
return arr.slice(0, 10);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Copy a directory tree recursively. Vanilla fs only — no deps. Mirrors
|
|
114
|
+
* the helper used by cursor-marketplace.cjs (Plan 28-8-B1).
|
|
115
|
+
*/
|
|
116
|
+
function copyDirRecursive(src, dest) {
|
|
117
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
118
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
119
|
+
const srcPath = path.join(src, entry.name);
|
|
120
|
+
const destPath = path.join(dest, entry.name);
|
|
121
|
+
if (entry.isDirectory()) {
|
|
122
|
+
copyDirRecursive(srcPath, destPath);
|
|
123
|
+
} else if (entry.isFile()) {
|
|
124
|
+
fs.copyFileSync(srcPath, destPath);
|
|
125
|
+
}
|
|
126
|
+
// symlinks + other: ignored (skills tree is regular files only).
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── Public exports ─────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Build the Codex Plugin manifest object from GDD source artifacts.
|
|
134
|
+
* Pure function — no fs, env, or path access.
|
|
135
|
+
*
|
|
136
|
+
* Field-by-field source mapping per research § Schema Mapping:
|
|
137
|
+
*
|
|
138
|
+
* name ← marketplaceJson.plugins[0].name (canonical, kebab-case)
|
|
139
|
+
* → claudePlugin.name → stripNpmScope(packageJson.name)
|
|
140
|
+
* version ← packageJson.version (verbatim, lockstep per D-08)
|
|
141
|
+
* description ← packageJson.description (verbatim)
|
|
142
|
+
* author ← claudePlugin.author (canonical, has url)
|
|
143
|
+
* → marketplaceJson.plugins[0].author → packageJson.author
|
|
144
|
+
* homepage ← packageJson.homepage
|
|
145
|
+
* repository ← stripGitSuffix(packageJson.repository.url)
|
|
146
|
+
* license ← packageJson.license
|
|
147
|
+
* keywords ← curateKeywords(packageJson.keywords) → ≤10 entries
|
|
148
|
+
* skills ← static "./skills/"
|
|
149
|
+
* mcpServers ← inline { gdd-mcp: { command: "npx", args: [...] } }
|
|
150
|
+
* interface ← 9 sub-fields per Schema Mapping table:
|
|
151
|
+
* displayName, shortDescription, longDescription,
|
|
152
|
+
* developerName, category, capabilities, websiteURL,
|
|
153
|
+
* defaultPrompt, brandColor
|
|
154
|
+
*
|
|
155
|
+
* OMITTED (per research § Manifest Format "Omitted fields"):
|
|
156
|
+
* apps, hooks (off-by-default), interface.privacyPolicyURL,
|
|
157
|
+
* interface.termsOfServiceURL, interface.composerIcon, interface.logo,
|
|
158
|
+
* interface.screenshots
|
|
159
|
+
*
|
|
160
|
+
* @param {Object} sources Source metadata.
|
|
161
|
+
* @param {Object} sources.packageJson Parsed package.json.
|
|
162
|
+
* @param {Object} [sources.claudePlugin] Parsed .claude-plugin/plugin.json.
|
|
163
|
+
* @param {Object} [sources.marketplaceJson] Parsed .claude-plugin/marketplace.json.
|
|
164
|
+
* @param {string} [sources.readmeFirstPara] README.md first paragraph
|
|
165
|
+
* for interface.longDescription.
|
|
166
|
+
* @returns {Object} Manifest object ready
|
|
167
|
+
* to JSON.stringify with 2-space indent.
|
|
168
|
+
*/
|
|
169
|
+
function buildManifest(sources) {
|
|
170
|
+
if (!sources || typeof sources !== 'object') {
|
|
171
|
+
throw new Error('codex-plugin.buildManifest: sources is required');
|
|
172
|
+
}
|
|
173
|
+
const { packageJson, claudePlugin, marketplaceJson, readmeFirstPara } = sources;
|
|
174
|
+
|
|
175
|
+
if (!packageJson || typeof packageJson !== 'object') {
|
|
176
|
+
throw new Error('codex-plugin.buildManifest: sources.packageJson is required');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// name — required, kebab-case. Priority: marketplaceJson > claudePlugin >
|
|
180
|
+
// package.json (with scope stripped).
|
|
181
|
+
let name;
|
|
182
|
+
if (
|
|
183
|
+
marketplaceJson
|
|
184
|
+
&& Array.isArray(marketplaceJson.plugins)
|
|
185
|
+
&& marketplaceJson.plugins[0]
|
|
186
|
+
&& typeof marketplaceJson.plugins[0].name === 'string'
|
|
187
|
+
) {
|
|
188
|
+
name = marketplaceJson.plugins[0].name;
|
|
189
|
+
} else if (claudePlugin && typeof claudePlugin.name === 'string') {
|
|
190
|
+
name = claudePlugin.name;
|
|
191
|
+
} else if (typeof packageJson.name === 'string') {
|
|
192
|
+
name = stripNpmScope(packageJson.name);
|
|
193
|
+
} else {
|
|
194
|
+
throw new Error('codex-plugin.buildManifest: name is required (no source)');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// version — required, semver-shaped.
|
|
198
|
+
if (typeof packageJson.version !== 'string' || !/^\d+\.\d+\.\d+/.test(packageJson.version)) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
'codex-plugin.buildManifest: packageJson.version is required and must be semver-shaped'
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
const version = packageJson.version;
|
|
204
|
+
|
|
205
|
+
// description — required, free text.
|
|
206
|
+
if (typeof packageJson.description !== 'string' || packageJson.description.length === 0) {
|
|
207
|
+
throw new Error('codex-plugin.buildManifest: packageJson.description is required');
|
|
208
|
+
}
|
|
209
|
+
const description = packageJson.description;
|
|
210
|
+
|
|
211
|
+
// author — prefer claudePlugin (has url), then marketplace, then package.json.
|
|
212
|
+
let author;
|
|
213
|
+
if (
|
|
214
|
+
claudePlugin
|
|
215
|
+
&& claudePlugin.author
|
|
216
|
+
&& typeof claudePlugin.author === 'object'
|
|
217
|
+
&& typeof claudePlugin.author.name === 'string'
|
|
218
|
+
) {
|
|
219
|
+
author = Object.assign({}, claudePlugin.author);
|
|
220
|
+
} else if (
|
|
221
|
+
marketplaceJson
|
|
222
|
+
&& Array.isArray(marketplaceJson.plugins)
|
|
223
|
+
&& marketplaceJson.plugins[0]
|
|
224
|
+
&& marketplaceJson.plugins[0].author
|
|
225
|
+
&& typeof marketplaceJson.plugins[0].author === 'object'
|
|
226
|
+
) {
|
|
227
|
+
author = Object.assign({}, marketplaceJson.plugins[0].author);
|
|
228
|
+
} else if (typeof packageJson.author === 'string') {
|
|
229
|
+
author = { name: packageJson.author };
|
|
230
|
+
} else if (
|
|
231
|
+
packageJson.author
|
|
232
|
+
&& typeof packageJson.author === 'object'
|
|
233
|
+
&& typeof packageJson.author.name === 'string'
|
|
234
|
+
) {
|
|
235
|
+
author = Object.assign({}, packageJson.author);
|
|
236
|
+
} else {
|
|
237
|
+
author = { name: 'unknown' };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// homepage — verbatim, omit if absent.
|
|
241
|
+
const homepage =
|
|
242
|
+
typeof packageJson.homepage === 'string' && packageJson.homepage.length > 0
|
|
243
|
+
? packageJson.homepage
|
|
244
|
+
: undefined;
|
|
245
|
+
|
|
246
|
+
// repository — string or object form, strip trailing .git for cleaner display.
|
|
247
|
+
let repository;
|
|
248
|
+
if (packageJson.repository) {
|
|
249
|
+
let rawUrl;
|
|
250
|
+
if (typeof packageJson.repository === 'string') {
|
|
251
|
+
rawUrl = packageJson.repository;
|
|
252
|
+
} else if (
|
|
253
|
+
typeof packageJson.repository === 'object'
|
|
254
|
+
&& typeof packageJson.repository.url === 'string'
|
|
255
|
+
) {
|
|
256
|
+
rawUrl = packageJson.repository.url;
|
|
257
|
+
}
|
|
258
|
+
if (rawUrl) {
|
|
259
|
+
repository = stripGitSuffix(rawUrl);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// license — verbatim, omit if absent.
|
|
264
|
+
const license =
|
|
265
|
+
typeof packageJson.license === 'string' && packageJson.license.length > 0
|
|
266
|
+
? packageJson.license
|
|
267
|
+
: undefined;
|
|
268
|
+
|
|
269
|
+
// keywords — curated ≤10-tag subset.
|
|
270
|
+
const keywords = curateKeywords(packageJson.keywords || []);
|
|
271
|
+
|
|
272
|
+
// skills — static path string per build doc complete-manifest example.
|
|
273
|
+
const skills = './skills/';
|
|
274
|
+
|
|
275
|
+
// mcpServers — inline object form (D-14 minimalism: no separate .mcp.json
|
|
276
|
+
// artifact in this plan). The bin name `gdd-mcp` is verified against
|
|
277
|
+
// package.json#bin during integration.
|
|
278
|
+
const mcpServers = {
|
|
279
|
+
'gdd-mcp': {
|
|
280
|
+
command: 'npx',
|
|
281
|
+
args: ['-y', `--package=${packageJson.name}`, 'gdd-mcp'],
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// interface — 9-field install-surface metadata per Schema Mapping table.
|
|
286
|
+
const developerName = (author && typeof author.name === 'string')
|
|
287
|
+
? author.name
|
|
288
|
+
: 'hegemonart';
|
|
289
|
+
|
|
290
|
+
const categoryRaw =
|
|
291
|
+
(marketplaceJson
|
|
292
|
+
&& Array.isArray(marketplaceJson.plugins)
|
|
293
|
+
&& marketplaceJson.plugins[0]
|
|
294
|
+
&& typeof marketplaceJson.plugins[0].category === 'string')
|
|
295
|
+
? marketplaceJson.plugins[0].category
|
|
296
|
+
: 'design';
|
|
297
|
+
const category = capitalize(categoryRaw);
|
|
298
|
+
|
|
299
|
+
const interfaceObj = {
|
|
300
|
+
displayName: 'Get Design Done',
|
|
301
|
+
shortDescription: truncate(description, 120),
|
|
302
|
+
longDescription: (typeof readmeFirstPara === 'string' && readmeFirstPara.length > 0)
|
|
303
|
+
? readmeFirstPara
|
|
304
|
+
: description,
|
|
305
|
+
developerName,
|
|
306
|
+
category,
|
|
307
|
+
capabilities: ['Read', 'Write'],
|
|
308
|
+
websiteURL: homepage || '',
|
|
309
|
+
defaultPrompt: [
|
|
310
|
+
'Run /gdd:brief to start a design cycle.',
|
|
311
|
+
'Use $gdd-explore to audit a screen.',
|
|
312
|
+
],
|
|
313
|
+
brandColor: '#10A37F',
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// Assemble in documented order. Omit undefined fields so JSON.stringify
|
|
317
|
+
// produces a clean diff (matches cursor-marketplace.cjs convention).
|
|
318
|
+
const manifest = {};
|
|
319
|
+
manifest.name = name;
|
|
320
|
+
manifest.version = version;
|
|
321
|
+
manifest.description = description;
|
|
322
|
+
manifest.author = author;
|
|
323
|
+
if (homepage !== undefined) manifest.homepage = homepage;
|
|
324
|
+
if (repository !== undefined) manifest.repository = repository;
|
|
325
|
+
if (license !== undefined) manifest.license = license;
|
|
326
|
+
manifest.keywords = keywords;
|
|
327
|
+
manifest.skills = skills;
|
|
328
|
+
manifest.mcpServers = mcpServers;
|
|
329
|
+
manifest.interface = interfaceObj;
|
|
330
|
+
|
|
331
|
+
return manifest;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Convert/emit the codex-plugin bundle into a destination directory.
|
|
336
|
+
* Called by build-distribution-bundles.cjs (Plan 28-8-X1).
|
|
337
|
+
*
|
|
338
|
+
* Per CONTEXT D-06, `skills/` is the shared source — this converter emits
|
|
339
|
+
* the marketplace bundle as:
|
|
340
|
+
*
|
|
341
|
+
* <outDir>/
|
|
342
|
+
* .codex-plugin/
|
|
343
|
+
* plugin.json ← the manifest object, JSON.stringified
|
|
344
|
+
* skills/
|
|
345
|
+
* <each skill copied verbatim from input.skillsDir>
|
|
346
|
+
*
|
|
347
|
+
* Codex consumes Claude-compatible SKILL.md (Phase 28.5 contract is
|
|
348
|
+
* already mattpocock-shaped, which Codex accepts per research § vs
|
|
349
|
+
* AGENTS.md) so no per-skill content transform is required at the
|
|
350
|
+
* Tier-2 bundle layer. The Tier-1 codex.cjs converter remains
|
|
351
|
+
* responsible for any per-runtime SKILL.md rewrites needed by the
|
|
352
|
+
* file-drop install path; those rewrites are irrelevant to a marketplace
|
|
353
|
+
* bundle.
|
|
354
|
+
*
|
|
355
|
+
* Idempotent: rerunning with the same inputs produces identical files.
|
|
356
|
+
* Touches only paths under `outDir`. The source `skillsDir` is read-only.
|
|
357
|
+
*
|
|
358
|
+
* @param {Object} input
|
|
359
|
+
* @param {string} input.skillsDir Path to source skills/ tree.
|
|
360
|
+
* @param {string} input.outDir Path to destination bundle directory.
|
|
361
|
+
* @param {Object} input.manifest Manifest object from buildManifest().
|
|
362
|
+
* @returns {{ manifestPath: string, outDir: string }}
|
|
363
|
+
*/
|
|
364
|
+
function convert(input) {
|
|
365
|
+
if (!input || typeof input !== 'object') {
|
|
366
|
+
throw new Error('codex-plugin.convert: input is required');
|
|
367
|
+
}
|
|
368
|
+
const { skillsDir, outDir, manifest } = input;
|
|
369
|
+
if (typeof skillsDir !== 'string' || skillsDir.length === 0) {
|
|
370
|
+
throw new Error('codex-plugin.convert: input.skillsDir is required');
|
|
371
|
+
}
|
|
372
|
+
if (typeof outDir !== 'string' || outDir.length === 0) {
|
|
373
|
+
throw new Error('codex-plugin.convert: input.outDir is required');
|
|
374
|
+
}
|
|
375
|
+
if (!manifest || typeof manifest !== 'object') {
|
|
376
|
+
throw new Error('codex-plugin.convert: input.manifest is required');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Validate required fields before writing.
|
|
380
|
+
for (const field of MANIFEST_REQUIRED_FIELDS) {
|
|
381
|
+
if (!manifest[field]) {
|
|
382
|
+
throw new Error(`codex-plugin: manifest missing required field "${field}"`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Ensure output dir exists.
|
|
387
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
388
|
+
|
|
389
|
+
// Write manifest at <outDir>/.codex-plugin/plugin.json.
|
|
390
|
+
const manifestDir = path.join(outDir, '.codex-plugin');
|
|
391
|
+
fs.mkdirSync(manifestDir, { recursive: true });
|
|
392
|
+
const manifestPath = path.join(manifestDir, 'plugin.json');
|
|
393
|
+
fs.writeFileSync(
|
|
394
|
+
manifestPath,
|
|
395
|
+
JSON.stringify(manifest, null, 2) + '\n',
|
|
396
|
+
'utf8'
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
// Copy skills/ tree verbatim (D-06: skills are shared source, no rewriting).
|
|
400
|
+
if (fs.existsSync(skillsDir)) {
|
|
401
|
+
copyDirRecursive(skillsDir, path.join(outDir, 'skills'));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return { manifestPath, outDir };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
module.exports = { buildManifest, convert, MANIFEST_REQUIRED_FIELDS, CURATED_KEYWORDS };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* scripts/lib/install/converters/codex.cjs — Phase 28.7 (Plan 28.7-04).
|
|
5
|
+
*
|
|
6
|
+
* Codex SKILL.md converter. Translates Claude-shape source into Codex's
|
|
7
|
+
* expected shape:
|
|
8
|
+
*
|
|
9
|
+
* - Frontmatter `name:` normalized to `gdd-<skill>` (no double-prefix).
|
|
10
|
+
* - Slash references in prose rewritten from `/gdd-<name>` to
|
|
11
|
+
* `$gdd-<name>` (Codex's shell-variable form) via
|
|
12
|
+
* `../runtime-slash.cjs#formatGddSlash`. Skill names are lowercased
|
|
13
|
+
* on emission per Codex shell-var convention.
|
|
14
|
+
* - Tool names in code fences rewritten via `CODEX_TOOL_MAP` from
|
|
15
|
+
* `reference/codex-tools.md` (Phase 21 D-06 reuse):
|
|
16
|
+
* Read → read_file
|
|
17
|
+
* Write/Edit → apply_patch
|
|
18
|
+
* Bash/Grep → shell
|
|
19
|
+
* Glob → shell
|
|
20
|
+
* WebSearch → web_search
|
|
21
|
+
* WebFetch → shell
|
|
22
|
+
* Task is left untouched (Phase 21 codex-tools.md "Known gaps").
|
|
23
|
+
* - A 1-line HTML adapter header is injected at the top of the body
|
|
24
|
+
* to record that this file was auto-generated from Claude source.
|
|
25
|
+
*
|
|
26
|
+
* Architecture ported from gsd-build/get-shit-done (MIT) — per Phase
|
|
27
|
+
* 28.7 D-02 (port architecture, not source). See NOTICE for upstream
|
|
28
|
+
* attribution. gsd-build's equivalent is `convertClaudeCommandToCodexSkill`
|
|
29
|
+
* in bin/install.js; our modular factor delegates rewrites to ./shared.cjs
|
|
30
|
+
* and pulls the tool map from the same module's CODEX_TOOL_MAP export.
|
|
31
|
+
*
|
|
32
|
+
* Pure / side-effect-free: no fs, no env, no path. `convert` is a
|
|
33
|
+
* deterministic string → string transform.
|
|
34
|
+
*
|
|
35
|
+
* Per Phase 21 codex-tools.md: tool-name rewrites are operative on the
|
|
36
|
+
* code-fenced INVOCATION form (`Bash(command=...)`) only. Prose mentions
|
|
37
|
+
* of the Claude vocabulary ("Use the Bash tool to...") round-trip
|
|
38
|
+
* unchanged — they're documentation, not invocations.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const shared = require('./shared.cjs');
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Convert Claude-source SKILL.md content for the Codex runtime.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} content Full source SKILL.md content (frontmatter + body).
|
|
47
|
+
* @param {string} skillName The bare skill name (e.g. `'help'`, `'explore'`).
|
|
48
|
+
* @param {{ runtime?: string }} [opts] Optional context — `runtime` defaults
|
|
49
|
+
* to `'codex'`. Currently informational only.
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
function convert(content, skillName, opts) {
|
|
53
|
+
const { frontmatter, body } = shared.extractFrontmatterAndBody(content);
|
|
54
|
+
const fm = shared.buildFrontmatter(frontmatter, skillName, 'gdd-');
|
|
55
|
+
let out = shared.rewriteSlashRefs(body, 'codex');
|
|
56
|
+
out = shared.rewriteCodeFenceTools(out, shared.CODEX_TOOL_MAP);
|
|
57
|
+
out = shared.ensureAdapterHeader(out, 'Codex');
|
|
58
|
+
return fm + out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = { convert };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* scripts/lib/install/converters/copilot.cjs — Phase 28.7 (Plan 28.7-04).
|
|
5
|
+
*
|
|
6
|
+
* GitHub Copilot SKILL.md converter. Translates Claude-shape source
|
|
7
|
+
* into Copilot's expected shape:
|
|
8
|
+
*
|
|
9
|
+
* - Frontmatter `name:` normalized to `gdd-<skill>` (no double-prefix).
|
|
10
|
+
* - Slash references in prose pass through as `/gdd-<name>` —
|
|
11
|
+
* Copilot accepts the Claude shape. Mixed-shape inputs are
|
|
12
|
+
* normalized via the runtime-slash module.
|
|
13
|
+
* - Tool names in code fences pass through unchanged — Copilot
|
|
14
|
+
* accepts the Claude vocabulary (Read/Write/Bash/Edit/Grep/Glob).
|
|
15
|
+
* - A 1-line HTML adapter header is injected at the top of the body
|
|
16
|
+
* to record that this file was auto-generated from Claude source.
|
|
17
|
+
*
|
|
18
|
+
* Architecture ported from gsd-build/get-shit-done (MIT) — per Phase
|
|
19
|
+
* 28.7 D-02 (port architecture, not source). See NOTICE for upstream
|
|
20
|
+
* attribution. gsd-build's equivalent function is
|
|
21
|
+
* `convertClaudeCommandToCopilotSkill` in bin/install.js; our modular
|
|
22
|
+
* factor delegates the actual rewrites to ./shared.cjs.
|
|
23
|
+
*
|
|
24
|
+
* Pure / side-effect-free: no fs, no env, no path. `convert` is a
|
|
25
|
+
* deterministic string → string transform.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const shared = require('./shared.cjs');
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Convert Claude-source SKILL.md content for the Copilot runtime.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} content Full source SKILL.md content (frontmatter + body).
|
|
34
|
+
* @param {string} skillName The bare skill name (e.g. `'help'`, `'explore'`).
|
|
35
|
+
* @param {{ runtime?: string }} [opts] Optional context — `runtime` defaults
|
|
36
|
+
* to `'copilot'`. Currently informational only.
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
function convert(content, skillName, opts) {
|
|
40
|
+
const { frontmatter, body } = shared.extractFrontmatterAndBody(content);
|
|
41
|
+
const fm = shared.buildFrontmatter(frontmatter, skillName, 'gdd-');
|
|
42
|
+
let out = shared.rewriteSlashRefs(body, 'copilot');
|
|
43
|
+
out = shared.ensureAdapterHeader(out, 'Copilot');
|
|
44
|
+
return fm + out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { convert };
|