@undeemed/get-shit-done-codex 1.20.8 → 1.20.10
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/AGENTS.md +29 -29
- package/README.md +102 -30
- package/agents/gsd-debugger.md +53 -8
- package/agents/gsd-planner.md +86 -5
- package/agents/gsd-verifier.md +15 -0
- package/bin/install.js +524 -37
- package/commands/gsd/add-tests.md +41 -0
- package/commands/gsd/debug.md +3 -0
- package/commands/gsd/join-discord.md +1 -1
- package/commands/gsd/plan-phase.md +2 -1
- package/get-shit-done/bin/gsd-tools.cjs +39 -4
- package/get-shit-done/bin/lib/commands.cjs +5 -8
- package/get-shit-done/bin/lib/core.cjs +22 -9
- package/get-shit-done/bin/lib/init.cjs +17 -1
- package/get-shit-done/bin/lib/milestone.cjs +2 -1
- package/get-shit-done/bin/lib/phase.cjs +18 -20
- package/get-shit-done/bin/lib/roadmap.cjs +7 -7
- package/get-shit-done/bin/lib/state.cjs +216 -27
- package/get-shit-done/bin/lib/verify.cjs +9 -8
- package/get-shit-done/templates/DEBUG.md +7 -2
- package/get-shit-done/templates/VALIDATION.md +18 -46
- package/get-shit-done/templates/retrospective.md +54 -0
- package/get-shit-done/workflows/add-tests.md +350 -0
- package/get-shit-done/workflows/complete-milestone.md +63 -0
- package/get-shit-done/workflows/discuss-phase.md +2 -0
- package/get-shit-done/workflows/help.md +3 -0
- package/package.json +2 -1
package/bin/install.js
CHANGED
|
@@ -36,6 +36,45 @@ const args = process.argv.slice(2);
|
|
|
36
36
|
const hasGlobal = args.includes('--global') || args.includes('-g');
|
|
37
37
|
const hasLocal = args.includes('--local') || args.includes('-l');
|
|
38
38
|
const hasHelp = args.includes('--help') || args.includes('-h');
|
|
39
|
+
const hasVerify = args.includes('--verify');
|
|
40
|
+
const hasRepair = args.includes('--repair');
|
|
41
|
+
const hasNoVersionCheck = args.includes('--no-version-check') || process.env.GSD_SKIP_VERSION_CHECK === '1';
|
|
42
|
+
const hasMigrate = args.includes('--migrate');
|
|
43
|
+
const hasSkipMigrate = args.includes('--skip-migrate') || args.includes('--no-migrate');
|
|
44
|
+
const isInteractiveTerminal = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
45
|
+
const CODEX_MODES = new Set(['prompts', 'skills']);
|
|
46
|
+
const hasCodexModeArg = args.some((arg) =>
|
|
47
|
+
arg.startsWith('--codex-mode=') || arg === '--codex-mode' || arg === '-m'
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
function parseCodexModeArg(argv) {
|
|
51
|
+
const explicit = argv.find((arg) => arg.startsWith('--codex-mode='));
|
|
52
|
+
const flagIndex = argv.findIndex((arg) => arg === '--codex-mode' || arg === '-m');
|
|
53
|
+
let mode = 'skills';
|
|
54
|
+
|
|
55
|
+
if (explicit) {
|
|
56
|
+
mode = explicit.split('=')[1] || '';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (flagIndex !== -1) {
|
|
60
|
+
const next = argv[flagIndex + 1];
|
|
61
|
+
if (!next || next.startsWith('-')) {
|
|
62
|
+
console.error(` ${yellow}--codex-mode requires one of: prompts, skills${reset}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
mode = next;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
mode = String(mode).toLowerCase().trim();
|
|
69
|
+
if (!CODEX_MODES.has(mode)) {
|
|
70
|
+
console.error(` ${yellow}Invalid --codex-mode '${mode}'. Expected prompts or skills.${reset}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return mode;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const codexMode = hasHelp ? 'skills' : parseCodexModeArg(args);
|
|
39
78
|
|
|
40
79
|
console.log(banner);
|
|
41
80
|
|
|
@@ -45,6 +84,12 @@ if (hasHelp) {
|
|
|
45
84
|
${yellow}Options:${reset}
|
|
46
85
|
${cyan}-g, --global${reset} Install globally (to ~/.codex/)
|
|
47
86
|
${cyan}-l, --local${reset} Install locally (to current directory)
|
|
87
|
+
${cyan}-m, --codex-mode <mode>${reset} Command mode: skills or prompts
|
|
88
|
+
${cyan}--migrate${reset} Apply detected legacy surface cleanup without prompting
|
|
89
|
+
${cyan}--skip-migrate${reset} Keep legacy surface files when migration is detected
|
|
90
|
+
${cyan}--verify${reset} Verify current install integrity
|
|
91
|
+
${cyan}--repair${reset} Repair failed verification checks
|
|
92
|
+
${cyan}--no-version-check${reset} Skip npm version lookup
|
|
48
93
|
${cyan}-h, --help${reset} Show this help message
|
|
49
94
|
|
|
50
95
|
${yellow}Examples:${reset}
|
|
@@ -54,24 +99,45 @@ if (hasHelp) {
|
|
|
54
99
|
${dim}# Install to current project only${reset}
|
|
55
100
|
npx ${NPM_PACKAGE} --local
|
|
56
101
|
|
|
102
|
+
${dim}# Install native Codex skills only${reset}
|
|
103
|
+
npx ${NPM_PACKAGE} --global --codex-mode skills
|
|
104
|
+
|
|
105
|
+
${dim}# Install prompt aliases only${reset}
|
|
106
|
+
npx ${NPM_PACKAGE} --global --codex-mode prompts
|
|
107
|
+
|
|
108
|
+
${dim}# Verify global installation${reset}
|
|
109
|
+
npx ${NPM_PACKAGE} --verify --global
|
|
110
|
+
|
|
111
|
+
${dim}# Verify and auto-repair local installation${reset}
|
|
112
|
+
npx ${NPM_PACKAGE} --verify --repair --local
|
|
113
|
+
|
|
114
|
+
${dim}# Force migration cleanup in non-interactive runs${reset}
|
|
115
|
+
npx ${NPM_PACKAGE} --global --migrate
|
|
116
|
+
|
|
57
117
|
${yellow}Notes:${reset}
|
|
58
118
|
- Installs AGENTS.md into the target directory
|
|
59
|
-
-
|
|
119
|
+
- If --codex-mode is omitted in interactive mode, installer prompts to choose
|
|
120
|
+
- Non-interactive runs default to skills mode
|
|
121
|
+
- If legacy surface files are detected, installer asks before removing them
|
|
60
122
|
- Installs get-shit-done/ workflow files
|
|
61
123
|
`);
|
|
62
124
|
process.exit(0);
|
|
63
125
|
}
|
|
64
126
|
|
|
65
|
-
function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
127
|
+
function applyPathPrefixReplacements(content, pathPrefix) {
|
|
128
|
+
if (pathPrefix === '~/.codex/') {
|
|
129
|
+
return content
|
|
130
|
+
.replace(/\.\/\.codex\//g, '~/.codex/')
|
|
131
|
+
.replace(/~\/\.codex\//g, '~/.codex/');
|
|
132
|
+
}
|
|
70
133
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
134
|
+
return content
|
|
135
|
+
.replace(/\.\/\.codex\//g, pathPrefix)
|
|
136
|
+
.replace(/~\/\.codex\//g, pathPrefix);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function applyReplacements(content, pathPrefix) {
|
|
140
|
+
content = applyPathPrefixReplacements(content, pathPrefix);
|
|
75
141
|
|
|
76
142
|
content = content.replace(/Codex CLI/g, 'Codex CLI');
|
|
77
143
|
content = content.replace(/Codex/g, 'Codex');
|
|
@@ -86,6 +152,208 @@ function applyReplacements(content, pathPrefix) {
|
|
|
86
152
|
return content;
|
|
87
153
|
}
|
|
88
154
|
|
|
155
|
+
function convertPromptRefsToSkillRefs(content) {
|
|
156
|
+
let converted = content.replace(/\/prompts:gsd-([a-z0-9-]+)/gi, (_, commandName) => `$gsd-${String(commandName).toLowerCase()}`);
|
|
157
|
+
converted = converted.replace(/\/gsd:([a-z0-9-]+)/gi, (_, commandName) => `$gsd-${String(commandName).toLowerCase()}`);
|
|
158
|
+
converted = converted.replace(/\/gsd-help\b/gi, '$gsd-help');
|
|
159
|
+
return converted;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function convertSkillRefsToPromptRefs(content) {
|
|
163
|
+
return content.replace(/\$gsd-([a-z0-9-]+)/gi, (_, commandName) => `/prompts:gsd-${String(commandName).toLowerCase()}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function convertCommandRefsToSkillMentions(content) {
|
|
167
|
+
return convertPromptRefsToSkillRefs(content).replace(/\$ARGUMENTS\b/g, '{{GSD_ARGS}}');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function rewriteAgentInvocationLine(content, replacement) {
|
|
171
|
+
return content
|
|
172
|
+
.replace('Invoke them with `/gsd:command-name`:', replacement)
|
|
173
|
+
.replace('Invoke them with `/prompts:gsd-command-name`:', replacement)
|
|
174
|
+
.replace('Invoke them with `$gsd-command-name`:', replacement);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function adaptAgentsForCodexMode(content, mode) {
|
|
178
|
+
if (mode === 'skills') {
|
|
179
|
+
let adapted = convertPromptRefsToSkillRefs(content);
|
|
180
|
+
adapted = rewriteAgentInvocationLine(adapted, 'Invoke them with `$gsd-command-name`:');
|
|
181
|
+
return adapted;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (mode === 'prompts') {
|
|
185
|
+
let adapted = convertSkillRefsToPromptRefs(content);
|
|
186
|
+
adapted = rewriteAgentInvocationLine(adapted, 'Invoke them with `/prompts:gsd-command-name`:');
|
|
187
|
+
return adapted;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return content;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function extractFrontmatterAndBody(content) {
|
|
194
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
195
|
+
if (!match) {
|
|
196
|
+
return { frontmatter: null, body: content };
|
|
197
|
+
}
|
|
198
|
+
return { frontmatter: match[1], body: content.slice(match[0].length) };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function extractFrontmatterField(frontmatter, fieldName) {
|
|
202
|
+
if (!frontmatter) return null;
|
|
203
|
+
const match = frontmatter.match(new RegExp(`^${fieldName}:\\s*(.+)$`, 'mi'));
|
|
204
|
+
if (!match) return null;
|
|
205
|
+
return match[1].trim().replace(/^['"]|['"]$/g, '');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function yamlQuote(value) {
|
|
209
|
+
return JSON.stringify(String(value));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function toSingleLine(value) {
|
|
213
|
+
return String(value).replace(/\s+/g, ' ').trim();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function getCodexSkillAdapterHeader(skillName) {
|
|
217
|
+
return `<codex_skill_adapter>
|
|
218
|
+
Codex native skill mode:
|
|
219
|
+
- AGENTS-first: treat AGENTS.md as the persistent source of truth for behavior and constraints.
|
|
220
|
+
- Invoke this workflow with \`$${skillName}\`.
|
|
221
|
+
- Treat user text after \`$${skillName}\` as \`{{GSD_ARGS}}\` (empty when omitted).
|
|
222
|
+
- Any legacy \`Task(...)\` notation in referenced docs means "spawn a specialized subagent" in Codex.
|
|
223
|
+
</codex_skill_adapter>`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function convertCommandToCodexSkill(commandContent, skillName, pathPrefix) {
|
|
227
|
+
const replaced = applyReplacements(commandContent, pathPrefix);
|
|
228
|
+
const converted = convertCommandRefsToSkillMentions(replaced);
|
|
229
|
+
const { frontmatter, body } = extractFrontmatterAndBody(converted);
|
|
230
|
+
const description = toSingleLine(
|
|
231
|
+
extractFrontmatterField(frontmatter, 'description') || `Run GSD workflow ${skillName}.`
|
|
232
|
+
);
|
|
233
|
+
const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
|
|
234
|
+
|
|
235
|
+
return `---
|
|
236
|
+
name: ${yamlQuote(skillName)}
|
|
237
|
+
description: ${yamlQuote(description)}
|
|
238
|
+
metadata:
|
|
239
|
+
short-description: ${yamlQuote(shortDescription)}
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
${getCodexSkillAdapterHeader(skillName)}
|
|
243
|
+
|
|
244
|
+
${body.trimStart()}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function installPrompts(commandsDir, promptsDir, markdownEntries, pathPrefix) {
|
|
248
|
+
fs.mkdirSync(promptsDir, { recursive: true });
|
|
249
|
+
for (const entry of markdownEntries) {
|
|
250
|
+
const srcPath = path.join(commandsDir, entry);
|
|
251
|
+
const destPath = path.join(promptsDir, `gsd-${entry}`);
|
|
252
|
+
const content = applyReplacements(fs.readFileSync(srcPath, 'utf8'), pathPrefix);
|
|
253
|
+
fs.writeFileSync(destPath, content, 'utf8');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function installCodexSkills(commandsDir, skillsDir, markdownEntries, pathPrefix) {
|
|
258
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
259
|
+
|
|
260
|
+
const existingSkills = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
261
|
+
for (const entry of existingSkills) {
|
|
262
|
+
if (entry.isDirectory() && entry.name.startsWith('gsd-')) {
|
|
263
|
+
fs.rmSync(path.join(skillsDir, entry.name), { recursive: true, force: true });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
for (const entry of markdownEntries) {
|
|
268
|
+
const commandPath = path.join(commandsDir, entry);
|
|
269
|
+
const commandContent = fs.readFileSync(commandPath, 'utf8');
|
|
270
|
+
const commandName = entry.replace(/\.md$/i, '');
|
|
271
|
+
const skillName = `gsd-${commandName}`;
|
|
272
|
+
const skillDir = path.join(skillsDir, skillName);
|
|
273
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
274
|
+
fs.writeFileSync(
|
|
275
|
+
path.join(skillDir, 'SKILL.md'),
|
|
276
|
+
convertCommandToCodexSkill(commandContent, skillName, pathPrefix),
|
|
277
|
+
'utf8'
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function removePromptAliases(promptsDir) {
|
|
283
|
+
if (!fs.existsSync(promptsDir)) return 0;
|
|
284
|
+
const entries = fs.readdirSync(promptsDir);
|
|
285
|
+
let removed = 0;
|
|
286
|
+
for (const entry of entries) {
|
|
287
|
+
if (/^gsd-.*\.md$/i.test(entry)) {
|
|
288
|
+
fs.rmSync(path.join(promptsDir, entry), { force: true });
|
|
289
|
+
removed++;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return removed;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function removeSkillAliases(skillsDir) {
|
|
296
|
+
if (!fs.existsSync(skillsDir)) return 0;
|
|
297
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
298
|
+
let removed = 0;
|
|
299
|
+
for (const entry of entries) {
|
|
300
|
+
if (entry.isDirectory() && entry.name.startsWith('gsd-')) {
|
|
301
|
+
fs.rmSync(path.join(skillsDir, entry.name), { recursive: true, force: true });
|
|
302
|
+
removed++;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return removed;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function detectMigrationPlan(codexDir, mode) {
|
|
309
|
+
const promptsDir = path.join(codexDir, 'prompts');
|
|
310
|
+
const skillsDir = path.join(codexDir, 'skills');
|
|
311
|
+
const promptFiles = listPromptCommandFiles(promptsDir);
|
|
312
|
+
const skillNames = listSkillNames(skillsDir);
|
|
313
|
+
|
|
314
|
+
const promptCountToRemove = mode === 'skills' ? promptFiles.length : 0;
|
|
315
|
+
const skillCountToRemove = mode === 'prompts' ? skillNames.length : 0;
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
mode,
|
|
319
|
+
promptsDir,
|
|
320
|
+
skillsDir,
|
|
321
|
+
promptCountToRemove,
|
|
322
|
+
skillCountToRemove,
|
|
323
|
+
hasChanges: promptCountToRemove > 0 || skillCountToRemove > 0,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function describeMigrationPlan(plan) {
|
|
328
|
+
const items = [];
|
|
329
|
+
if (plan.promptCountToRemove > 0) {
|
|
330
|
+
items.push(`${plan.promptCountToRemove} legacy prompt alias file(s) in prompts/`);
|
|
331
|
+
}
|
|
332
|
+
if (plan.skillCountToRemove > 0) {
|
|
333
|
+
items.push(`${plan.skillCountToRemove} legacy skill director${plan.skillCountToRemove === 1 ? 'y' : 'ies'} in skills/`);
|
|
334
|
+
}
|
|
335
|
+
return items.join(' and ');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function applyMigrationPlan(plan) {
|
|
339
|
+
let removedPrompts = 0;
|
|
340
|
+
let removedSkills = 0;
|
|
341
|
+
|
|
342
|
+
if (plan.promptCountToRemove > 0) {
|
|
343
|
+
removedPrompts = removePromptAliases(plan.promptsDir);
|
|
344
|
+
}
|
|
345
|
+
if (plan.skillCountToRemove > 0) {
|
|
346
|
+
removedSkills = removeSkillAliases(plan.skillsDir);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (removedPrompts > 0 || removedSkills > 0) {
|
|
350
|
+
const changes = [];
|
|
351
|
+
if (removedPrompts > 0) changes.push(`${removedPrompts} prompt alias file(s)`);
|
|
352
|
+
if (removedSkills > 0) changes.push(`${removedSkills} skill director${removedSkills === 1 ? 'y' : 'ies'}`);
|
|
353
|
+
console.log(` ${green}✓${reset} Migration applied: removed ${changes.join(', ')}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
89
357
|
function copyWithPathReplacement(srcDir, destDir, pathPrefix) {
|
|
90
358
|
fs.mkdirSync(destDir, { recursive: true });
|
|
91
359
|
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
@@ -131,34 +399,137 @@ function showCachedVersionWarning() {
|
|
|
131
399
|
}
|
|
132
400
|
}
|
|
133
401
|
|
|
134
|
-
function
|
|
135
|
-
const src = path.join(__dirname, '..');
|
|
402
|
+
function getInstallContext(isGlobal) {
|
|
136
403
|
const codexDir = isGlobal ? path.join(os.homedir(), '.codex') : process.cwd();
|
|
137
|
-
|
|
138
|
-
|
|
404
|
+
return {
|
|
405
|
+
codexDir,
|
|
406
|
+
locationLabel: isGlobal ? '~/.codex' : '.',
|
|
407
|
+
pathPrefix: isGlobal ? '~/.codex/' : './',
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function listPromptCommandFiles(promptsDir) {
|
|
412
|
+
if (!fs.existsSync(promptsDir)) return [];
|
|
413
|
+
return fs.readdirSync(promptsDir)
|
|
414
|
+
.filter((name) => /^gsd-.*\.md$/i.test(name))
|
|
415
|
+
.sort();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function listSkillNames(skillsDir) {
|
|
419
|
+
if (!fs.existsSync(skillsDir)) return [];
|
|
420
|
+
return fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
421
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith('gsd-'))
|
|
422
|
+
.filter((entry) => fs.existsSync(path.join(skillsDir, entry.name, 'SKILL.md')))
|
|
423
|
+
.map((entry) => entry.name)
|
|
424
|
+
.sort();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function detectInstalledMode(promptCount, skillCount) {
|
|
428
|
+
if (promptCount > 0 && skillCount > 0) return 'mixed';
|
|
429
|
+
if (promptCount > 0) return 'prompts';
|
|
430
|
+
if (skillCount > 0) return 'skills';
|
|
431
|
+
return 'none';
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function verifyInstall(isGlobal, expectedMode, strictMode = false) {
|
|
435
|
+
const src = path.join(__dirname, '..');
|
|
436
|
+
const { codexDir, locationLabel } = getInstallContext(isGlobal);
|
|
437
|
+
const promptsDir = path.join(codexDir, 'prompts');
|
|
438
|
+
const skillsDir = path.join(codexDir, 'skills');
|
|
439
|
+
const workflowRoot = path.join(codexDir, 'get-shit-done');
|
|
440
|
+
const versionFile = path.join(workflowRoot, 'VERSION');
|
|
441
|
+
const expectedCount = fs.readdirSync(path.join(src, 'commands', 'gsd'))
|
|
442
|
+
.filter((entry) => entry.endsWith('.md'))
|
|
443
|
+
.length;
|
|
444
|
+
|
|
445
|
+
const promptFiles = listPromptCommandFiles(promptsDir);
|
|
446
|
+
const skillNames = listSkillNames(skillsDir);
|
|
447
|
+
const detectedMode = detectInstalledMode(promptFiles.length, skillNames.length);
|
|
448
|
+
const modeToCheck = strictMode ? expectedMode : (detectedMode === 'none' ? expectedMode : detectedMode);
|
|
449
|
+
|
|
450
|
+
const checks = [];
|
|
451
|
+
const addCheck = (ok, label, detail) => checks.push({ ok, label, detail });
|
|
452
|
+
|
|
453
|
+
addCheck(fs.existsSync(codexDir), 'Config directory exists', codexDir);
|
|
454
|
+
addCheck(fs.existsSync(path.join(codexDir, 'AGENTS.md')), 'AGENTS.md installed', path.join(codexDir, 'AGENTS.md'));
|
|
455
|
+
addCheck(fs.existsSync(workflowRoot), 'get-shit-done assets installed', workflowRoot);
|
|
456
|
+
addCheck(fs.existsSync(path.join(workflowRoot, 'workflows')), 'Workflow directory installed', path.join(workflowRoot, 'workflows'));
|
|
457
|
+
addCheck(fs.existsSync(path.join(workflowRoot, 'templates')), 'Template directory installed', path.join(workflowRoot, 'templates'));
|
|
458
|
+
addCheck(fs.existsSync(versionFile), 'VERSION file installed', versionFile);
|
|
459
|
+
|
|
460
|
+
if (fs.existsSync(versionFile)) {
|
|
461
|
+
const version = fs.readFileSync(versionFile, 'utf8').split(/\r?\n/)[0].trim();
|
|
462
|
+
addCheck(version === pkg.version, 'VERSION matches installer package', `${version || '(empty)'} vs ${pkg.version}`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (modeToCheck === 'prompts') {
|
|
466
|
+
addCheck(promptFiles.length === expectedCount, 'Prompt aliases complete', `${promptFiles.length}/${expectedCount}`);
|
|
467
|
+
}
|
|
468
|
+
if (modeToCheck === 'skills') {
|
|
469
|
+
addCheck(skillNames.length === expectedCount, 'Native skills complete', `${skillNames.length}/${expectedCount}`);
|
|
470
|
+
}
|
|
471
|
+
if (modeToCheck === 'mixed') {
|
|
472
|
+
addCheck(
|
|
473
|
+
false,
|
|
474
|
+
'Single command surface required',
|
|
475
|
+
'Both skills and prompt aliases found. Choose one mode and run install with --migrate.'
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
if (modeToCheck === 'none') {
|
|
479
|
+
addCheck(false, 'At least one command surface installed', 'No skills/ or prompts/ entries found');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
console.log(` Verifying ${cyan}${locationLabel}${reset} (detected mode: ${cyan}${detectedMode}${reset}, check mode: ${cyan}${modeToCheck}${reset})`);
|
|
483
|
+
for (const check of checks) {
|
|
484
|
+
const icon = check.ok ? `${green}✓${reset}` : `${yellow}✗${reset}`;
|
|
485
|
+
const detail = check.detail ? ` ${dim}(${check.detail})${reset}` : '';
|
|
486
|
+
console.log(` ${icon} ${check.label}${detail}`);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const ok = checks.every((check) => check.ok);
|
|
490
|
+
if (ok) {
|
|
491
|
+
console.log(`\n ${green}Integrity check passed.${reset}`);
|
|
492
|
+
} else {
|
|
493
|
+
console.log(`\n ${yellow}Integrity check failed.${reset}`);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return { ok, detectedMode, checkedMode: modeToCheck };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function installCore(isGlobal, mode, migrationPlan, applyMigration) {
|
|
500
|
+
const src = path.join(__dirname, '..');
|
|
501
|
+
const { codexDir, locationLabel, pathPrefix } = getInstallContext(isGlobal);
|
|
502
|
+
const installPromptsEnabled = mode === 'prompts';
|
|
503
|
+
const installSkillsEnabled = mode === 'skills';
|
|
139
504
|
|
|
140
505
|
console.log(` Installing to ${cyan}${locationLabel}${reset}\n`);
|
|
141
506
|
fs.mkdirSync(codexDir, { recursive: true });
|
|
507
|
+
if (applyMigration && migrationPlan && migrationPlan.hasChanges) {
|
|
508
|
+
applyMigrationPlan(migrationPlan);
|
|
509
|
+
}
|
|
142
510
|
|
|
143
511
|
const agentsSrc = path.join(src, 'AGENTS.md');
|
|
144
512
|
const agentsDest = path.join(codexDir, 'AGENTS.md');
|
|
145
|
-
|
|
513
|
+
let agentsContent = applyReplacements(fs.readFileSync(agentsSrc, 'utf8'), pathPrefix);
|
|
514
|
+
agentsContent = adaptAgentsForCodexMode(agentsContent, mode);
|
|
146
515
|
fs.writeFileSync(agentsDest, agentsContent, 'utf8');
|
|
147
516
|
console.log(` ${green}✓${reset} Installed AGENTS.md`);
|
|
148
517
|
|
|
149
|
-
const promptsDir = path.join(codexDir, 'prompts');
|
|
150
|
-
fs.mkdirSync(promptsDir, { recursive: true });
|
|
151
|
-
|
|
152
518
|
const gsdSrc = path.join(src, 'commands', 'gsd');
|
|
153
519
|
const entries = fs.readdirSync(gsdSrc);
|
|
154
520
|
const markdownEntries = entries.filter((entry) => entry.endsWith('.md'));
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
521
|
+
|
|
522
|
+
if (installPromptsEnabled) {
|
|
523
|
+
const promptsDir = path.join(codexDir, 'prompts');
|
|
524
|
+
installPrompts(gsdSrc, promptsDir, markdownEntries, pathPrefix);
|
|
525
|
+
console.log(` ${green}✓${reset} Installed prompts/gsd-*.md (${markdownEntries.length} commands)`);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (installSkillsEnabled) {
|
|
529
|
+
const skillsDir = path.join(codexDir, 'skills');
|
|
530
|
+
installCodexSkills(gsdSrc, skillsDir, markdownEntries, pathPrefix);
|
|
531
|
+
console.log(` ${green}✓${reset} Installed skills/gsd-*/SKILL.md (${markdownEntries.length} skills)`);
|
|
160
532
|
}
|
|
161
|
-
console.log(` ${green}✓${reset} Installed prompts/gsd-*.md (${markdownEntries.length} commands)`);
|
|
162
533
|
|
|
163
534
|
const skillSrc = path.join(src, 'get-shit-done');
|
|
164
535
|
const skillDest = path.join(codexDir, 'get-shit-done');
|
|
@@ -172,20 +543,100 @@ function install(isGlobal) {
|
|
|
172
543
|
|
|
173
544
|
${yellow}For Codex (CLI + Desktop):${reset}
|
|
174
545
|
- AGENTS.md: ${cyan}${codexDir}/AGENTS.md${reset}
|
|
175
|
-
|
|
546
|
+
${installPromptsEnabled ? `- Prompt commands: ${cyan}${codexDir}/prompts/${reset}` : ''}
|
|
547
|
+
${installSkillsEnabled ? `- Native skills: ${cyan}${codexDir}/skills/gsd-*/SKILL.md${reset}` : ''}
|
|
176
548
|
|
|
177
549
|
${yellow}Getting Started:${reset}
|
|
178
550
|
1. Run ${cyan}codex${reset} (CLI) or ${cyan}codex app${reset} (Desktop)
|
|
179
|
-
2. Use ${cyan}/prompts:gsd-help${reset} to list commands
|
|
180
|
-
3. Start with ${cyan}/prompts:gsd-new-project${reset}
|
|
551
|
+
2. Use ${cyan}${installSkillsEnabled ? '$gsd-help' : '/prompts:gsd-help'}${reset} to list commands
|
|
552
|
+
3. Start with ${cyan}${installSkillsEnabled ? '$gsd-new-project' : '/prompts:gsd-new-project'}${reset}
|
|
181
553
|
|
|
182
554
|
${yellow}Staying Updated:${reset}
|
|
183
|
-
- In Codex: ${cyan}/prompts:gsd-update${reset}
|
|
555
|
+
- In Codex: ${cyan}${installSkillsEnabled ? '$gsd-update' : '/prompts:gsd-update'}${reset}
|
|
184
556
|
- In terminal: ${cyan}npx ${NPM_PACKAGE_LATEST}${reset}
|
|
185
557
|
`);
|
|
186
558
|
}
|
|
187
559
|
|
|
188
|
-
function
|
|
560
|
+
function install(isGlobal, mode = codexMode, done = () => {}) {
|
|
561
|
+
const { codexDir } = getInstallContext(isGlobal);
|
|
562
|
+
const migrationPlan = detectMigrationPlan(codexDir, mode);
|
|
563
|
+
|
|
564
|
+
if (!migrationPlan.hasChanges) {
|
|
565
|
+
installCore(isGlobal, mode, migrationPlan, false);
|
|
566
|
+
done();
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const summary = describeMigrationPlan(migrationPlan);
|
|
571
|
+
console.log(` ${yellow}Migration detected:${reset} ${summary}`);
|
|
572
|
+
|
|
573
|
+
if (hasMigrate) {
|
|
574
|
+
console.log(` ${green}✓${reset} Migration approved by --migrate`);
|
|
575
|
+
installCore(isGlobal, mode, migrationPlan, true);
|
|
576
|
+
done();
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (hasSkipMigrate) {
|
|
581
|
+
console.log(` ${yellow}Skipping migration due to --skip-migrate.${reset}`);
|
|
582
|
+
installCore(isGlobal, mode, migrationPlan, false);
|
|
583
|
+
done();
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (!isInteractiveTerminal) {
|
|
588
|
+
console.log(` ${yellow}Skipping migration in non-interactive mode.${reset}`);
|
|
589
|
+
console.log(` ${dim}Re-run with --migrate to apply cleanup or --skip-migrate to keep legacy files explicitly.${reset}`);
|
|
590
|
+
installCore(isGlobal, mode, migrationPlan, false);
|
|
591
|
+
done();
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const rl = readline.createInterface({
|
|
596
|
+
input: process.stdin,
|
|
597
|
+
output: process.stdout
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
rl.question(` Remove legacy files now? ${dim}[Y/n]${reset}: `, (answer) => {
|
|
601
|
+
rl.close();
|
|
602
|
+
const normalized = answer.trim().toLowerCase();
|
|
603
|
+
const applyMigration = normalized === '' || normalized === 'y' || normalized === 'yes';
|
|
604
|
+
if (!applyMigration) {
|
|
605
|
+
console.log(` ${yellow}Keeping legacy files.${reset}`);
|
|
606
|
+
}
|
|
607
|
+
installCore(isGlobal, mode, migrationPlan, applyMigration);
|
|
608
|
+
done();
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function promptCodexMode(done) {
|
|
613
|
+
if (hasCodexModeArg || !isInteractiveTerminal) {
|
|
614
|
+
done(codexMode);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const rl = readline.createInterface({
|
|
619
|
+
input: process.stdin,
|
|
620
|
+
output: process.stdout
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
console.log(` ${yellow}Which command surface do you want?${reset}
|
|
624
|
+
|
|
625
|
+
${cyan}1${reset}) Skills ${dim}($gsd-*, recommended)${reset}
|
|
626
|
+
${cyan}2${reset}) Prompts ${dim}(/prompts:gsd-*)${reset}
|
|
627
|
+
`);
|
|
628
|
+
|
|
629
|
+
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
|
630
|
+
rl.close();
|
|
631
|
+
const normalized = answer.trim().toLowerCase();
|
|
632
|
+
const mode = normalized === '2' || normalized === 'prompt' || normalized === 'prompts'
|
|
633
|
+
? 'prompts'
|
|
634
|
+
: 'skills';
|
|
635
|
+
done(mode);
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function promptLocation(done) {
|
|
189
640
|
const rl = readline.createInterface({
|
|
190
641
|
input: process.stdin,
|
|
191
642
|
output: process.stdout
|
|
@@ -200,21 +651,57 @@ function promptLocation() {
|
|
|
200
651
|
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
|
201
652
|
rl.close();
|
|
202
653
|
const choice = answer.trim() || '1';
|
|
203
|
-
|
|
654
|
+
done(choice !== '2');
|
|
204
655
|
});
|
|
205
656
|
}
|
|
206
657
|
|
|
207
|
-
showCachedVersionWarning();
|
|
208
|
-
|
|
209
658
|
if (hasGlobal && hasLocal) {
|
|
210
659
|
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
|
|
211
660
|
process.exit(1);
|
|
212
661
|
}
|
|
213
662
|
|
|
214
|
-
if (
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
663
|
+
if (hasRepair && !hasVerify) {
|
|
664
|
+
console.error(` ${yellow}--repair requires --verify${reset}`);
|
|
665
|
+
process.exit(1);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (hasMigrate && hasSkipMigrate) {
|
|
669
|
+
console.error(` ${yellow}Cannot combine --migrate and --skip-migrate${reset}`);
|
|
670
|
+
process.exit(1);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (!hasNoVersionCheck && !hasVerify) {
|
|
674
|
+
showCachedVersionWarning();
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (hasVerify) {
|
|
678
|
+
const isGlobal = hasLocal ? false : true;
|
|
679
|
+
const strictMode = hasCodexModeArg;
|
|
680
|
+
let result = verifyInstall(isGlobal, codexMode, strictMode);
|
|
681
|
+
|
|
682
|
+
if (!result.ok && hasRepair) {
|
|
683
|
+
const repairMode = codexMode;
|
|
684
|
+
console.log(`\n ${yellow}Repairing install using mode '${repairMode}'...${reset}\n`);
|
|
685
|
+
install(isGlobal, repairMode, () => {
|
|
686
|
+
console.log('');
|
|
687
|
+
const repaired = verifyInstall(isGlobal, repairMode, true);
|
|
688
|
+
process.exit(repaired.ok ? 0 : 1);
|
|
689
|
+
});
|
|
690
|
+
} else {
|
|
691
|
+
process.exit(result.ok ? 0 : 1);
|
|
692
|
+
}
|
|
218
693
|
} else {
|
|
219
|
-
|
|
694
|
+
const installWithSelectedMode = (isGlobal) => {
|
|
695
|
+
promptCodexMode((selectedMode) => {
|
|
696
|
+
install(isGlobal, selectedMode);
|
|
697
|
+
});
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
if (hasGlobal) {
|
|
701
|
+
installWithSelectedMode(true);
|
|
702
|
+
} else if (hasLocal) {
|
|
703
|
+
installWithSelectedMode(false);
|
|
704
|
+
} else {
|
|
705
|
+
promptLocation(installWithSelectedMode);
|
|
706
|
+
}
|
|
220
707
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gsd:add-tests
|
|
3
|
+
description: Generate tests for a completed phase based on UAT criteria and implementation
|
|
4
|
+
argument-hint: "<phase> [additional instructions]"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Read
|
|
7
|
+
- Write
|
|
8
|
+
- Edit
|
|
9
|
+
- Bash
|
|
10
|
+
- Glob
|
|
11
|
+
- Grep
|
|
12
|
+
- Task
|
|
13
|
+
- AskUserQuestion
|
|
14
|
+
argument-instructions: |
|
|
15
|
+
Parse the argument as a phase number (integer, decimal, or letter-suffix), plus optional free-text instructions.
|
|
16
|
+
Example: /gsd:add-tests 12
|
|
17
|
+
Example: /gsd:add-tests 12 focus on edge cases in the pricing module
|
|
18
|
+
---
|
|
19
|
+
<objective>
|
|
20
|
+
Generate unit and E2E tests for a completed phase, using its SUMMARY.md, CONTEXT.md, and VERIFICATION.md as specifications.
|
|
21
|
+
|
|
22
|
+
Analyzes implementation files, classifies them into TDD (unit), E2E (browser), or Skip categories, presents a test plan for user approval, then generates tests following RED-GREEN conventions.
|
|
23
|
+
|
|
24
|
+
Output: Test files committed with message `test(phase-{N}): add unit and E2E tests from add-tests command`
|
|
25
|
+
</objective>
|
|
26
|
+
|
|
27
|
+
<execution_context>
|
|
28
|
+
@~/.claude/get-shit-done/workflows/add-tests.md
|
|
29
|
+
</execution_context>
|
|
30
|
+
|
|
31
|
+
<context>
|
|
32
|
+
Phase: $ARGUMENTS
|
|
33
|
+
|
|
34
|
+
@.planning/STATE.md
|
|
35
|
+
@.planning/ROADMAP.md
|
|
36
|
+
</context>
|
|
37
|
+
|
|
38
|
+
<process>
|
|
39
|
+
Execute the add-tests workflow from @~/.claude/get-shit-done/workflows/add-tests.md end-to-end.
|
|
40
|
+
Preserve all workflow gates (classification approval, test plan approval, RED-GREEN verification, gap reporting).
|
|
41
|
+
</process>
|