5-phase-workflow 1.9.5 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -410
- package/bin/install.js +385 -70
- package/bin/sync-agents.js +50 -11
- package/docs/findings.md +3 -3
- package/docs/workflow-guide.md +110 -1046
- package/package.json +6 -5
- package/src/agents/step-executor-agent.md +49 -0
- package/src/agents/step-orchestrator-agent.md +111 -0
- package/src/agents/verification-agent.md +78 -0
- package/src/commands/5/address-review-findings.md +69 -403
- package/src/commands/5/apply-review-findings.md +66 -0
- package/src/commands/5/configure.md +110 -76
- package/src/commands/5/discuss-feature.md +47 -57
- package/src/commands/5/eject.md +7 -6
- package/src/commands/5/implement.md +202 -0
- package/src/commands/5/plan.md +164 -0
- package/src/commands/5/reconfigure.md +32 -31
- package/src/commands/5/reply-pr-comments.md +46 -0
- package/src/commands/5/review.md +95 -0
- package/src/commands/5/split.md +190 -0
- package/src/commands/5/synchronize-agents.md +4 -4
- package/src/commands/5/triage-pr-comments.md +70 -0
- package/src/commands/5/update.md +8 -8
- package/src/hooks/check-updates.js +50 -7
- package/src/hooks/plan-guard.js +28 -22
- package/src/hooks/statusline.js +55 -4
- package/src/skills/configure-docs-index/SKILL.md +16 -21
- package/src/skills/configure-skills/SKILL.md +21 -24
- package/src/templates/AGENTS.md +94 -0
- package/src/templates/workflow/FIX-PLAN.md +1 -1
- package/src/templates/workflow/PLAN-COMPACT.md +42 -0
- package/src/templates/workflow/PLAN.md +58 -34
- package/src/templates/workflow/REVIEW-FINDINGS.md +7 -16
- package/src/templates/workflow/REVIEW-SUMMARY.md +5 -0
- package/src/templates/workflow/STATE.json +32 -3
- package/src/agents/component-executor.md +0 -57
- package/src/commands/5/implement-feature.md +0 -381
- package/src/commands/5/plan-feature.md +0 -293
- package/src/commands/5/plan-implementation.md +0 -333
- package/src/commands/5/quick-implement.md +0 -375
- package/src/commands/5/review-code.md +0 -212
- package/src/commands/5/verify-implementation.md +0 -277
- package/src/templates/workflow/FEATURE-SPEC.md +0 -100
- package/src/templates/workflow/VERIFICATION-REPORT.md +0 -103
package/bin/install.js
CHANGED
|
@@ -25,15 +25,60 @@ const log = {
|
|
|
25
25
|
header: (msg) => console.log(`\n${colors.bright}${msg}${colors.reset}\n`)
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
// Version comparison (semver)
|
|
29
|
-
// Uses parseInt to handle pre-release tags (e.g., "2-beta" → 2)
|
|
28
|
+
// Version comparison (semver, including pre-release ordering)
|
|
30
29
|
function compareVersions(v1, v2) {
|
|
31
|
-
const
|
|
32
|
-
const
|
|
30
|
+
const parsed1 = parseSemver(v1);
|
|
31
|
+
const parsed2 = parseSemver(v2);
|
|
32
|
+
|
|
33
33
|
for (let i = 0; i < 3; i++) {
|
|
34
|
-
if (
|
|
35
|
-
if (
|
|
34
|
+
if (parsed1.core[i] > parsed2.core[i]) return 1;
|
|
35
|
+
if (parsed1.core[i] < parsed2.core[i]) return -1;
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
return comparePrerelease(parsed1.prerelease, parsed2.prerelease);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseSemver(version) {
|
|
42
|
+
const normalized = String(version || '').trim().replace(/^v/, '').split('+')[0];
|
|
43
|
+
const prereleaseIndex = normalized.indexOf('-');
|
|
44
|
+
const corePart = prereleaseIndex === -1 ? normalized : normalized.slice(0, prereleaseIndex);
|
|
45
|
+
const prereleasePart = prereleaseIndex === -1 ? '' : normalized.slice(prereleaseIndex + 1);
|
|
46
|
+
const core = corePart.split('.').slice(0, 3).map(part => parseInt(part, 10) || 0);
|
|
47
|
+
while (core.length < 3) core.push(0);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
core,
|
|
51
|
+
prerelease: prereleasePart ? prereleasePart.split(/[.-]/) : []
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function comparePrerelease(pre1, pre2) {
|
|
56
|
+
if (pre1.length === 0 && pre2.length === 0) return 0;
|
|
57
|
+
if (pre1.length === 0) return 1;
|
|
58
|
+
if (pre2.length === 0) return -1;
|
|
59
|
+
|
|
60
|
+
const length = Math.max(pre1.length, pre2.length);
|
|
61
|
+
for (let i = 0; i < length; i++) {
|
|
62
|
+
const id1 = pre1[i];
|
|
63
|
+
const id2 = pre2[i];
|
|
64
|
+
if (id1 === undefined) return -1;
|
|
65
|
+
if (id2 === undefined) return 1;
|
|
66
|
+
if (id1 === id2) continue;
|
|
67
|
+
|
|
68
|
+
const id1Numeric = /^[0-9]+$/.test(id1);
|
|
69
|
+
const id2Numeric = /^[0-9]+$/.test(id2);
|
|
70
|
+
if (id1Numeric && id2Numeric) {
|
|
71
|
+
const n1 = parseInt(id1, 10);
|
|
72
|
+
const n2 = parseInt(id2, 10);
|
|
73
|
+
if (n1 > n2) return 1;
|
|
74
|
+
if (n1 < n2) return -1;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (id1Numeric) return -1;
|
|
78
|
+
if (id2Numeric) return 1;
|
|
79
|
+
return id1 > id2 ? 1 : -1;
|
|
80
|
+
}
|
|
81
|
+
|
|
37
82
|
return 0;
|
|
38
83
|
}
|
|
39
84
|
|
|
@@ -56,11 +101,15 @@ function getInstalledVersion(isGlobal) {
|
|
|
56
101
|
|
|
57
102
|
// Get package version from package.json
|
|
58
103
|
function getPackageVersion() {
|
|
59
|
-
const pkgPath = path.join(
|
|
104
|
+
const pkgPath = path.join(getPackageRoot(), 'package.json');
|
|
60
105
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
61
106
|
return pkg.version;
|
|
62
107
|
}
|
|
63
108
|
|
|
109
|
+
function getPackageRoot() {
|
|
110
|
+
return path.join(__dirname, '..');
|
|
111
|
+
}
|
|
112
|
+
|
|
64
113
|
// Get full version info
|
|
65
114
|
function getVersionInfo(targetPath, isGlobal) {
|
|
66
115
|
const exists = checkExistingInstallation(targetPath);
|
|
@@ -126,9 +175,9 @@ function parseArgs() {
|
|
|
126
175
|
// Show help message
|
|
127
176
|
function showHelp() {
|
|
128
177
|
console.log(`
|
|
129
|
-
${colors.bright}
|
|
178
|
+
${colors.bright}dev-workflow Installer${colors.reset}
|
|
130
179
|
|
|
131
|
-
Usage: npx
|
|
180
|
+
Usage: npx foifi [options]
|
|
132
181
|
|
|
133
182
|
Options:
|
|
134
183
|
--global, -g Install to ~/.claude/ (available across all projects)
|
|
@@ -141,13 +190,13 @@ Options:
|
|
|
141
190
|
--help, -h Show this help message
|
|
142
191
|
|
|
143
192
|
Examples:
|
|
144
|
-
npx
|
|
145
|
-
npx
|
|
146
|
-
npx
|
|
147
|
-
npx
|
|
148
|
-
npx
|
|
149
|
-
npx
|
|
150
|
-
npx
|
|
193
|
+
npx foifi # Install locally for Claude Code
|
|
194
|
+
npx foifi --global # Install globally for Claude Code
|
|
195
|
+
npx foifi --codex # Install locally for Codex CLI
|
|
196
|
+
npx foifi --codex -g # Install globally for Codex CLI
|
|
197
|
+
npx foifi --upgrade # Auto-update to latest version
|
|
198
|
+
npx foifi --check # Check version without updating
|
|
199
|
+
npx foifi --uninstall # Remove from current directory
|
|
151
200
|
`);
|
|
152
201
|
}
|
|
153
202
|
|
|
@@ -228,7 +277,11 @@ function copyDirMerge(src, dest) {
|
|
|
228
277
|
function getSourcePath() {
|
|
229
278
|
// When installed via npm, __dirname is <install-location>/bin
|
|
230
279
|
// Source files are in <install-location>/src
|
|
231
|
-
return path.join(
|
|
280
|
+
return path.join(getPackageRoot(), 'src');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function getPackageBinPath() {
|
|
284
|
+
return path.join(getPackageRoot(), 'bin');
|
|
232
285
|
}
|
|
233
286
|
|
|
234
287
|
// Copy directory recursively
|
|
@@ -251,6 +304,25 @@ function copyDir(src, dest) {
|
|
|
251
304
|
}
|
|
252
305
|
}
|
|
253
306
|
|
|
307
|
+
function copyWorkflowHelperBins(targetPath) {
|
|
308
|
+
const managed = getWorkflowManagedFiles();
|
|
309
|
+
if (!managed.binHelpers || managed.binHelpers.length === 0) return;
|
|
310
|
+
|
|
311
|
+
const binSrc = getPackageBinPath();
|
|
312
|
+
const binDest = path.join(targetPath, 'bin');
|
|
313
|
+
if (!fs.existsSync(binDest)) {
|
|
314
|
+
fs.mkdirSync(binDest, { recursive: true });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
for (const binHelper of managed.binHelpers) {
|
|
318
|
+
const src = path.join(binSrc, binHelper);
|
|
319
|
+
const dest = path.join(binDest, binHelper);
|
|
320
|
+
if (fs.existsSync(src)) {
|
|
321
|
+
fs.copyFileSync(src, dest);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
254
326
|
// Remove directory recursively
|
|
255
327
|
function removeDir(dir) {
|
|
256
328
|
if (fs.existsSync(dir)) {
|
|
@@ -280,11 +352,28 @@ const LEGACY_REMOVED_FILES = [
|
|
|
280
352
|
'agents/integration-agent.md',
|
|
281
353
|
'agents/step-fixer.md',
|
|
282
354
|
'agents/step-verifier.md',
|
|
283
|
-
'agents/verification-agent.md',
|
|
284
355
|
'templates/STACK.md',
|
|
285
356
|
'templates/STRUCTURE.md',
|
|
286
357
|
'templates/CONVENTIONS.md',
|
|
287
|
-
'templates/INTEGRATIONS.md'
|
|
358
|
+
'templates/INTEGRATIONS.md',
|
|
359
|
+
'commands/5/plan-feature.md',
|
|
360
|
+
'commands/5/plan-implementation.md',
|
|
361
|
+
'commands/5/implement-feature.md',
|
|
362
|
+
'commands/5/verify.md',
|
|
363
|
+
'commands/5/verify-implementation.md',
|
|
364
|
+
'commands/5/review-code.md',
|
|
365
|
+
'commands/5/quick-implement.md',
|
|
366
|
+
'agents/component-executor.md',
|
|
367
|
+
'templates/workflow/FEATURE-SPEC.md',
|
|
368
|
+
'templates/workflow/VERIFICATION-REPORT.md',
|
|
369
|
+
'skills/5-plan-feature',
|
|
370
|
+
'skills/5-plan-implementation',
|
|
371
|
+
'skills/5-implement-feature',
|
|
372
|
+
'skills/5-verify',
|
|
373
|
+
'skills/5-verify-implementation',
|
|
374
|
+
'skills/5-review-code',
|
|
375
|
+
'skills/5-quick-implement',
|
|
376
|
+
'skills/configure-project'
|
|
288
377
|
];
|
|
289
378
|
|
|
290
379
|
// Get list of workflow-owned files/directories (not user-created)
|
|
@@ -295,13 +384,14 @@ function getWorkflowManagedFiles() {
|
|
|
295
384
|
|
|
296
385
|
// Agents: separate agent files referenced by commands via agent: frontmatter
|
|
297
386
|
agents: [
|
|
298
|
-
'
|
|
387
|
+
'step-executor-agent.md',
|
|
388
|
+
'step-orchestrator-agent.md',
|
|
389
|
+
'verification-agent.md'
|
|
299
390
|
],
|
|
300
391
|
|
|
301
392
|
// Skills: specific skill directories
|
|
302
393
|
skills: [
|
|
303
394
|
'configure-docs-index',
|
|
304
|
-
'configure-project',
|
|
305
395
|
'configure-skills',
|
|
306
396
|
'generate-readme'
|
|
307
397
|
],
|
|
@@ -320,17 +410,22 @@ function getWorkflowManagedFiles() {
|
|
|
320
410
|
'configure-tables.md'
|
|
321
411
|
],
|
|
322
412
|
|
|
413
|
+
// Helper binaries: runtime scripts invoked by commands
|
|
414
|
+
binHelpers: [
|
|
415
|
+
'sync-agents.js'
|
|
416
|
+
],
|
|
417
|
+
|
|
323
418
|
// Templates: specific template files
|
|
324
419
|
templates: [
|
|
325
420
|
// Project documentation templates
|
|
421
|
+
'AGENTS.md',
|
|
326
422
|
'ARCHITECTURE.md',
|
|
327
423
|
'CONCERNS.md',
|
|
328
424
|
'TESTING.md',
|
|
329
425
|
// Workflow output templates
|
|
330
|
-
'workflow/FEATURE-SPEC.md',
|
|
331
426
|
'workflow/PLAN.md',
|
|
427
|
+
'workflow/PLAN-COMPACT.md',
|
|
332
428
|
'workflow/STATE.json',
|
|
333
|
-
'workflow/VERIFICATION-REPORT.md',
|
|
334
429
|
'workflow/REVIEW-FINDINGS.md',
|
|
335
430
|
'workflow/REVIEW-SUMMARY.md',
|
|
336
431
|
'workflow/FIX-PLAN.md'
|
|
@@ -365,7 +460,7 @@ function getFileManifest() {
|
|
|
365
460
|
manifest.push(`hooks/${hook}`);
|
|
366
461
|
}
|
|
367
462
|
|
|
368
|
-
// Templates are files (may include nested paths like workflow/
|
|
463
|
+
// Templates are files (may include nested paths like workflow/PLAN.md)
|
|
369
464
|
for (const template of managed.templates) {
|
|
370
465
|
manifest.push(`templates/${template}`);
|
|
371
466
|
}
|
|
@@ -377,6 +472,12 @@ function getFileManifest() {
|
|
|
377
472
|
}
|
|
378
473
|
}
|
|
379
474
|
|
|
475
|
+
if (managed.binHelpers) {
|
|
476
|
+
for (const binHelper of managed.binHelpers) {
|
|
477
|
+
manifest.push(`bin/${binHelper}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
380
481
|
return manifest;
|
|
381
482
|
}
|
|
382
483
|
|
|
@@ -406,9 +507,30 @@ function convertClaudeToCodexMarkdown(content) {
|
|
|
406
507
|
let converted = convertSlashCommandsToCodexMentions(content);
|
|
407
508
|
// Replace .claude/ path references with .codex/
|
|
408
509
|
converted = converted.replace(/\.claude\//g, '.codex/');
|
|
510
|
+
converted = convertClaudeSetupCommandsToCodexNotes(converted);
|
|
409
511
|
return converted;
|
|
410
512
|
}
|
|
411
513
|
|
|
514
|
+
function convertClaudeSetupCommandsToCodexNotes(content) {
|
|
515
|
+
return content
|
|
516
|
+
.replace(
|
|
517
|
+
/1\. "Install now \(recommended\)" — run `claude mcp add context7 -- npx -y @anthropic-ai\/claude-code-mcp-server-context7` via Bash/g,
|
|
518
|
+
'1. "Install now" — not supported automatically in Codex; install Context7 using your Codex MCP setup outside this workflow, then rerun configuration'
|
|
519
|
+
)
|
|
520
|
+
.replace(
|
|
521
|
+
/- If user selects "Install now": execute the install command/g,
|
|
522
|
+
'- If user selects "Install now": explain that Codex cannot run the Claude Code installer command; leave `tools.context7.available = false` unless Context7 is already detected'
|
|
523
|
+
)
|
|
524
|
+
.replace(
|
|
525
|
+
/1\. "Install now \(recommended\)" — run `claude plugin install skill-creator@claude-plugins-official` via Bash/g,
|
|
526
|
+
'1. "Install now" — not supported automatically in Codex; install an equivalent skill authoring workflow outside this command, then rerun configuration'
|
|
527
|
+
)
|
|
528
|
+
.replace(
|
|
529
|
+
/- If user selects "Install now": execute the install command, then set `tools\.skillCreator\.available = true` in the config/g,
|
|
530
|
+
'- If user selects "Install now": explain that Codex cannot run the Claude Code plugin installer; leave `tools.skillCreator.available = false` unless an equivalent tool is already detected'
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
|
|
412
534
|
// Generate the adapter header that teaches Codex how to map Claude Code concepts
|
|
413
535
|
function getCodexSkillAdapterHeader(skillName) {
|
|
414
536
|
const invocation = `$${skillName}`;
|
|
@@ -423,8 +545,10 @@ This skill was authored for Claude Code. Map these tool references:
|
|
|
423
545
|
| Claude Code | Codex Equivalent |
|
|
424
546
|
|-------------|------------------|
|
|
425
547
|
| \`AskUserQuestion\` | Ask the user directly in conversation |
|
|
426
|
-
| \`Agent(subagent_type="Explore")\` |
|
|
427
|
-
| \`Agent(prompt="...")\` | \`spawn_agent(message="...")\` |
|
|
548
|
+
| \`Agent(subagent_type="Explore")\` | \`spawn_agent(agent_type="explorer", model="gpt-5.4-mini", reasoning_effort="low", message="...")\` |
|
|
549
|
+
| \`Agent(prompt="...", model="haiku")\` | \`spawn_agent(model="gpt-5.4-mini", reasoning_effort="low", message="...")\` |
|
|
550
|
+
| \`Agent(prompt="...", model="sonnet")\` | \`spawn_agent(model="gpt-5.4", reasoning_effort="medium", message="...")\` |
|
|
551
|
+
| \`Agent(prompt="...")\` | \`spawn_agent(model="gpt-5.4-mini", reasoning_effort="low", message="...")\` unless the prompt explicitly requires complex reasoning |
|
|
428
552
|
| \`Read\` | \`read_file\` |
|
|
429
553
|
| \`Write\` | \`write_file\` |
|
|
430
554
|
| \`Edit\` | \`patch\` |
|
|
@@ -434,11 +558,24 @@ This skill was authored for Claude Code. Map these tool references:
|
|
|
434
558
|
| \`TaskCreate/TaskUpdate\` | Track progress internally |
|
|
435
559
|
| \`EnterPlanMode\` | Not available — use structured output instead |
|
|
436
560
|
|
|
561
|
+
## Codex Token Budget
|
|
562
|
+
- Default to \`gpt-5.4-mini\` with \`reasoning_effort: low\` for exploration, orchestration, simple implementation, and mechanical file edits.
|
|
563
|
+
- Use \`gpt-5.4\` with \`reasoning_effort: medium\` only for complex logic, cross-module behavior, security-sensitive changes, data migrations, final verification with meaningful logic review, or retries after failure.
|
|
564
|
+
- Use stronger models only when a previous cheaper attempt failed for reasoning reasons.
|
|
565
|
+
- Keep the parent skill context lean: delegate read-heavy exploration to explorer agents and pass only compact findings, target paths, pattern references, and command summaries between agents.
|
|
566
|
+
|
|
437
567
|
## Guard Rules (replaces plan-guard hook)
|
|
438
|
-
During planning
|
|
568
|
+
During the planning phase ($5-plan):
|
|
439
569
|
- Do NOT write to any file outside \`.5/\`
|
|
440
|
-
- Do NOT write source code — only
|
|
570
|
+
- Do NOT write source code — only the unified plan and scan cache
|
|
441
571
|
- Do NOT spawn implementation agents — only Explore/research agents
|
|
572
|
+
|
|
573
|
+
## Update & Migration Notices (replaces statusline hooks)
|
|
574
|
+
At the very start of this skill, before doing anything else, read these two files if they exist:
|
|
575
|
+
- \`.5/.update-cache.json\` — if \`latestAvailableVersion\` is set, compare it to \`packageVersion\` in \`.5/version.json\`. If a newer version is available, tell the user: "foifi update available: <version> — run \`npx foifi --codex --upgrade\` to update."
|
|
576
|
+
- \`.5/.migration-v2\` — if this file exists, tell the user: "You upgraded from foifi v1 to v2. Run \`$5-reconfigure\` to update your project configuration."
|
|
577
|
+
|
|
578
|
+
Show each applicable notice once at the top of your response, then continue with the skill normally. Do not abort the skill because of a notice.
|
|
442
579
|
</codex_skill_adapter>`;
|
|
443
580
|
}
|
|
444
581
|
|
|
@@ -447,7 +584,7 @@ function convertClaudeCommandToCodexSkill(content, skillName) {
|
|
|
447
584
|
const converted = convertClaudeToCodexMarkdown(content);
|
|
448
585
|
const { frontmatter, body } = extractFrontmatterAndBody(converted);
|
|
449
586
|
|
|
450
|
-
let description = `Run
|
|
587
|
+
let description = `Run dev-workflow: ${skillName}`;
|
|
451
588
|
if (frontmatter) {
|
|
452
589
|
const maybeDesc = extractFrontmatterField(frontmatter, 'description');
|
|
453
590
|
if (maybeDesc) description = maybeDesc;
|
|
@@ -495,6 +632,11 @@ function getCodexFileManifest() {
|
|
|
495
632
|
manifest.push(`skills/${skill}`);
|
|
496
633
|
}
|
|
497
634
|
|
|
635
|
+
// Agents are copied as-is for Codex skills that reference .codex/agents/
|
|
636
|
+
for (const agent of managed.agents) {
|
|
637
|
+
manifest.push(`agents/${agent}`);
|
|
638
|
+
}
|
|
639
|
+
|
|
498
640
|
// Templates are copied as-is
|
|
499
641
|
for (const template of managed.templates) {
|
|
500
642
|
manifest.push(`templates/${template}`);
|
|
@@ -507,6 +649,12 @@ function getCodexFileManifest() {
|
|
|
507
649
|
}
|
|
508
650
|
}
|
|
509
651
|
|
|
652
|
+
if (managed.binHelpers) {
|
|
653
|
+
for (const binHelper of managed.binHelpers) {
|
|
654
|
+
manifest.push(`bin/${binHelper}`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
510
658
|
// Instructions file
|
|
511
659
|
manifest.push('instructions.md');
|
|
512
660
|
|
|
@@ -590,7 +738,7 @@ function selectiveUpdate(targetPath, sourcePath) {
|
|
|
590
738
|
const src = path.join(templatesSrc, template);
|
|
591
739
|
const dest = path.join(templatesDest, template);
|
|
592
740
|
if (fs.existsSync(src)) {
|
|
593
|
-
// Ensure parent directory exists for nested paths (e.g., workflow/
|
|
741
|
+
// Ensure parent directory exists for nested paths (e.g., workflow/PLAN.md)
|
|
594
742
|
const destDir = path.dirname(dest);
|
|
595
743
|
if (!fs.existsSync(destDir)) {
|
|
596
744
|
fs.mkdirSync(destDir, { recursive: true });
|
|
@@ -616,6 +764,23 @@ function selectiveUpdate(targetPath, sourcePath) {
|
|
|
616
764
|
}
|
|
617
765
|
log.success('Updated references/ (workflow files only)');
|
|
618
766
|
}
|
|
767
|
+
|
|
768
|
+
// Update helper binaries used by runtime commands
|
|
769
|
+
if (managed.binHelpers && managed.binHelpers.length > 0) {
|
|
770
|
+
const binSrc = getPackageBinPath();
|
|
771
|
+
const binDest = path.join(targetPath, 'bin');
|
|
772
|
+
if (!fs.existsSync(binDest)) {
|
|
773
|
+
fs.mkdirSync(binDest, { recursive: true });
|
|
774
|
+
}
|
|
775
|
+
for (const binHelper of managed.binHelpers) {
|
|
776
|
+
const src = path.join(binSrc, binHelper);
|
|
777
|
+
const dest = path.join(binDest, binHelper);
|
|
778
|
+
if (fs.existsSync(src)) {
|
|
779
|
+
fs.copyFileSync(src, dest);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
log.success('Updated bin/ (workflow helper binaries)');
|
|
783
|
+
}
|
|
619
784
|
}
|
|
620
785
|
|
|
621
786
|
// Clean up files that were previously installed but are no longer managed
|
|
@@ -670,17 +835,20 @@ function cleanupOrphanedFiles(targetPath, dataDir) {
|
|
|
670
835
|
}
|
|
671
836
|
}
|
|
672
837
|
|
|
673
|
-
// Ensure .5/.gitignore exists and contains
|
|
838
|
+
// Ensure .5/.gitignore exists and contains transient runtime files
|
|
674
839
|
function ensureDotFiveGitignore(dataDir) {
|
|
675
840
|
const gitignorePath = path.join(dataDir, '.gitignore');
|
|
676
|
-
const
|
|
841
|
+
const entries = ['.update-cache.json', '.migration-v*', '.reconfig-reminder'];
|
|
677
842
|
if (fs.existsSync(gitignorePath)) {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
843
|
+
let content = fs.readFileSync(gitignorePath, 'utf8');
|
|
844
|
+
for (const entry of entries) {
|
|
845
|
+
if (!content.includes(entry)) {
|
|
846
|
+
content += '\n' + entry;
|
|
847
|
+
}
|
|
681
848
|
}
|
|
849
|
+
fs.writeFileSync(gitignorePath, content.trimEnd() + '\n');
|
|
682
850
|
} else {
|
|
683
|
-
fs.writeFileSync(gitignorePath,
|
|
851
|
+
fs.writeFileSync(gitignorePath, entries.join('\n') + '\n');
|
|
684
852
|
}
|
|
685
853
|
}
|
|
686
854
|
|
|
@@ -825,24 +993,25 @@ function mergeSettings(targetPath, sourcePath) {
|
|
|
825
993
|
// Check if installation exists
|
|
826
994
|
function checkExistingInstallation(targetPath) {
|
|
827
995
|
if (activeRuntime === 'codex') {
|
|
828
|
-
// Codex: commands are installed as skills/5-plan
|
|
829
|
-
const markerFile = path.join(targetPath, 'skills', '5-plan
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
996
|
+
// Codex: commands are installed as skills/5-plan/SKILL.md
|
|
997
|
+
const markerFile = path.join(targetPath, 'skills', '5-plan', 'SKILL.md');
|
|
998
|
+
const legacyMarkerFile = path.join(targetPath, 'skills', '5-plan-feature', 'SKILL.md');
|
|
999
|
+
return fs.existsSync(markerFile) || fs.existsSync(legacyMarkerFile);
|
|
1000
|
+
}
|
|
1001
|
+
const markerFile = path.join(targetPath, 'commands', '5', 'plan.md');
|
|
1002
|
+
const legacyMarkerFile = path.join(targetPath, 'commands', '5', 'plan-feature.md');
|
|
1003
|
+
return fs.existsSync(markerFile) || fs.existsSync(legacyMarkerFile);
|
|
834
1004
|
}
|
|
835
1005
|
|
|
836
1006
|
// Helper to show commands
|
|
837
1007
|
function showCommandsHelp(isGlobal) {
|
|
838
1008
|
if (activeRuntime === 'codex') {
|
|
839
1009
|
log.info('Available skills (invoke with $ prefix in Codex):');
|
|
840
|
-
log.info(' $5-plan-
|
|
841
|
-
log.info(' $5-
|
|
842
|
-
log.info(' $5-implement-
|
|
843
|
-
log.info(' $5-
|
|
844
|
-
log.info(' $5-review-
|
|
845
|
-
log.info(' $5-address-review-findings - Apply review findings & PR comments');
|
|
1010
|
+
log.info(' $5-plan - Create unified plan');
|
|
1011
|
+
log.info(' $5-split - Split plan into smaller plans');
|
|
1012
|
+
log.info(' $5-implement - Execute implementation + verification');
|
|
1013
|
+
log.info(' $5-review - Code review');
|
|
1014
|
+
log.info(' $5-address-review-findings - Decide review findings & PR comments');
|
|
846
1015
|
log.info(' $5-configure - Interactive project setup');
|
|
847
1016
|
log.info(' $5-reconfigure - Refresh docs/skills (no Q&A)');
|
|
848
1017
|
log.info(' $5-eject - Eject from update mechanism');
|
|
@@ -850,12 +1019,11 @@ function showCommandsHelp(isGlobal) {
|
|
|
850
1019
|
log.info(' $5-synchronize-agents - Sync user content between runtimes');
|
|
851
1020
|
} else {
|
|
852
1021
|
log.info('Available commands:');
|
|
853
|
-
log.info(' /5:plan-
|
|
854
|
-
log.info(' /5:
|
|
855
|
-
log.info(' /5:implement-
|
|
856
|
-
log.info(' /5:
|
|
857
|
-
log.info(' /5:review-
|
|
858
|
-
log.info(' /5:address-review-findings - Apply review findings & PR comments');
|
|
1022
|
+
log.info(' /5:plan - Create unified plan');
|
|
1023
|
+
log.info(' /5:split - Split plan into smaller plans');
|
|
1024
|
+
log.info(' /5:implement - Execute implementation + verification');
|
|
1025
|
+
log.info(' /5:review - Code review');
|
|
1026
|
+
log.info(' /5:address-review-findings - Decide review findings & PR comments');
|
|
859
1027
|
log.info(' /5:configure - Interactive project setup');
|
|
860
1028
|
log.info(' /5:reconfigure - Refresh docs/skills (no Q&A)');
|
|
861
1029
|
log.info(' /5:eject - Eject from update mechanism');
|
|
@@ -889,6 +1057,10 @@ function performFreshInstall(targetPath, sourcePath, isGlobal) {
|
|
|
889
1057
|
// Merge settings
|
|
890
1058
|
mergeSettings(targetPath, sourcePath);
|
|
891
1059
|
|
|
1060
|
+
// Install helper binaries used by runtime commands
|
|
1061
|
+
copyWorkflowHelperBins(targetPath);
|
|
1062
|
+
log.success('Installed bin/ (workflow helper binaries)');
|
|
1063
|
+
|
|
892
1064
|
// Initialize version tracking
|
|
893
1065
|
initializeVersionJson(isGlobal);
|
|
894
1066
|
|
|
@@ -908,6 +1080,52 @@ function performFreshInstall(targetPath, sourcePath, isGlobal) {
|
|
|
908
1080
|
showCommandsHelp(isGlobal);
|
|
909
1081
|
}
|
|
910
1082
|
|
|
1083
|
+
// Rename create-* generated skill directories to {pattern} (drop the create- prefix).
|
|
1084
|
+
// Earlier versions named them create-dto, create-service, etc. The new convention
|
|
1085
|
+
// uses the bare pattern name so skills work for both create and update.
|
|
1086
|
+
function renameCreateSkills(targetPath) {
|
|
1087
|
+
const skillsDir = path.join(targetPath, 'skills');
|
|
1088
|
+
if (!fs.existsSync(skillsDir)) return;
|
|
1089
|
+
try {
|
|
1090
|
+
for (const entry of fs.readdirSync(skillsDir)) {
|
|
1091
|
+
if (!entry.startsWith('create-')) continue;
|
|
1092
|
+
const oldPath = path.join(skillsDir, entry);
|
|
1093
|
+
const newName = entry.slice('create-'.length);
|
|
1094
|
+
const newPath = path.join(skillsDir, newName);
|
|
1095
|
+
if (fs.existsSync(newPath)) continue; // don't clobber a user-created skill
|
|
1096
|
+
const skillFile = path.join(oldPath, 'SKILL.md');
|
|
1097
|
+
if (!fs.existsSync(skillFile)) continue; // skip non-workflow dirs
|
|
1098
|
+
fs.renameSync(oldPath, newPath);
|
|
1099
|
+
// Update name: and description: in the frontmatter to match
|
|
1100
|
+
const content = fs.readFileSync(path.join(newPath, 'SKILL.md'), 'utf8');
|
|
1101
|
+
const updated = content
|
|
1102
|
+
.replace(/^name: create-/m, 'name: ')
|
|
1103
|
+
.replace(/^description: Creates a /m, 'description: Creates or updates a ');
|
|
1104
|
+
fs.writeFileSync(path.join(newPath, 'SKILL.md'), updated);
|
|
1105
|
+
log.info(`Renamed skill: ${entry} → ${newName}`);
|
|
1106
|
+
}
|
|
1107
|
+
} catch (e) {}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// Remove `context: fork` from any skill SKILL.md files under .claude/skills/.
|
|
1111
|
+
// These were generated by earlier versions of configure-skills and cause loops.
|
|
1112
|
+
function removeContextForkFromSkills(targetPath) {
|
|
1113
|
+
const skillsDir = path.join(targetPath, 'skills');
|
|
1114
|
+
if (!fs.existsSync(skillsDir)) return;
|
|
1115
|
+
try {
|
|
1116
|
+
for (const entry of fs.readdirSync(skillsDir)) {
|
|
1117
|
+
const skillFile = path.join(skillsDir, entry, 'SKILL.md');
|
|
1118
|
+
if (!fs.existsSync(skillFile)) continue;
|
|
1119
|
+
const original = fs.readFileSync(skillFile, 'utf8');
|
|
1120
|
+
const updated = original.replace(/^context: fork\s*\n/m, '');
|
|
1121
|
+
if (updated !== original) {
|
|
1122
|
+
fs.writeFileSync(skillFile, updated);
|
|
1123
|
+
log.info(`Removed context: fork from skills/${entry}/SKILL.md`);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
} catch (e) {}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
911
1129
|
// Perform update (preserves user-created files, updates .5/ data directory)
|
|
912
1130
|
function performUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
|
|
913
1131
|
log.header(`Updating from ${versionInfo.installed || 'legacy'} to ${versionInfo.available}`);
|
|
@@ -923,6 +1141,22 @@ function performUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
|
|
|
923
1141
|
// Merge settings (deep merge preserves user customizations)
|
|
924
1142
|
mergeSettings(targetPath, sourcePath);
|
|
925
1143
|
|
|
1144
|
+
// Rename create-* skill dirs to bare pattern names (create-dto → dto)
|
|
1145
|
+
renameCreateSkills(targetPath);
|
|
1146
|
+
|
|
1147
|
+
// Strip `context: fork` from generated skills — caused infinite loops
|
|
1148
|
+
removeContextForkFromSkills(targetPath);
|
|
1149
|
+
|
|
1150
|
+
// Flag v1 → v2 major upgrade so statusline can prompt for reconfigure
|
|
1151
|
+
const prevMajor = versionInfo.installed ? parseInt(versionInfo.installed.split('.')[0], 10) : 0;
|
|
1152
|
+
const newMajor = versionInfo.available ? parseInt(versionInfo.available.split('.')[0], 10) : 0;
|
|
1153
|
+
if (!isNaN(prevMajor) && !isNaN(newMajor) && prevMajor < newMajor) {
|
|
1154
|
+
try {
|
|
1155
|
+
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
|
1156
|
+
fs.writeFileSync(path.join(dataDir, '.migration-v' + newMajor), '1');
|
|
1157
|
+
} catch (e) {}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
926
1160
|
// Update version.json (per-runtime, preserving other runtime's state)
|
|
927
1161
|
writeVersionJson(dataDir, isGlobal, versionInfo.available);
|
|
928
1162
|
ensureDotFiveGitignore(dataDir);
|
|
@@ -944,40 +1178,41 @@ function performUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
|
|
|
944
1178
|
|
|
945
1179
|
// Generate instructions.md for Codex (replaces hooks + settings.json)
|
|
946
1180
|
function generateCodexInstructions(targetPath) {
|
|
947
|
-
const content = `#
|
|
1181
|
+
const content = `# dev-workflow — Codex Instructions
|
|
948
1182
|
|
|
949
|
-
This file is managed by the
|
|
1183
|
+
This file is managed by the dev-workflow installer. It provides Codex with
|
|
950
1184
|
the context it needs to run the workflow skills correctly.
|
|
951
1185
|
|
|
952
1186
|
## Workflow Overview
|
|
953
1187
|
|
|
954
|
-
The
|
|
1188
|
+
The workflow provides structured feature development:
|
|
955
1189
|
|
|
956
|
-
1. **Plan
|
|
957
|
-
2. **
|
|
958
|
-
3. **
|
|
959
|
-
4. **Verify Implementation** (\`$5-verify-implementation\`) — Build & test verification
|
|
960
|
-
5. **Review Code** (\`$5-review-code\`) — Code review
|
|
1190
|
+
1. **Plan** (\`$5-plan\`) — Requirements, discovery, and unified plan
|
|
1191
|
+
2. **Implement** (\`$5-implement\`) — Orchestrated implementation with inline verification
|
|
1192
|
+
3. **Review** (\`$5-review\`) — Code review
|
|
961
1193
|
|
|
962
1194
|
## Data Directory
|
|
963
1195
|
|
|
964
1196
|
All workflow state lives in \`.5/\` at the project root:
|
|
965
1197
|
- \`.5/config.json\` — Project configuration
|
|
966
|
-
- \`.5/features/{name}/
|
|
967
|
-
- \`.5/features/{name}/plan.md\` — Implementation plans
|
|
1198
|
+
- \`.5/features/{name}/plan.md\` — Unified plan
|
|
968
1199
|
- \`.5/features/{name}/state.json\` — Implementation state
|
|
969
1200
|
|
|
970
1201
|
## Guard Rules
|
|
971
1202
|
|
|
972
|
-
During planning
|
|
1203
|
+
During the planning phase ($5-plan):
|
|
973
1204
|
- Do NOT write files outside \`.5/\`
|
|
974
|
-
- Do NOT write source code — only
|
|
1205
|
+
- Do NOT write source code — only the unified plan and scan cache
|
|
975
1206
|
- The \`.5/.planning-active\` marker indicates planning is in progress
|
|
976
1207
|
|
|
977
1208
|
## Configuration
|
|
978
1209
|
|
|
979
1210
|
Run \`$5-configure\` after installation to set up your project.
|
|
980
1211
|
|
|
1212
|
+
## Codex Token Budget
|
|
1213
|
+
|
|
1214
|
+
Workflow skills use \`gpt-5.4-mini\` with low reasoning for exploration, orchestration, and simple executors by default. They escalate to \`gpt-5.4\` with medium reasoning only for complex logic, security-sensitive work, data migrations, public API changes, final verification that needs logic review, or retries after a cheaper attempt fails.
|
|
1215
|
+
|
|
981
1216
|
## Templates & References
|
|
982
1217
|
|
|
983
1218
|
Templates are in \`.codex/templates/\` and references in \`.codex/references/\`.
|
|
@@ -1038,6 +1273,28 @@ function installCodexSkills(targetPath, sourcePath) {
|
|
|
1038
1273
|
log.success('Installed utility skills');
|
|
1039
1274
|
}
|
|
1040
1275
|
|
|
1276
|
+
// Install workflow agents for Codex skills that reference .codex/agents/
|
|
1277
|
+
function installCodexAgents(targetPath, sourcePath) {
|
|
1278
|
+
const managed = getWorkflowManagedFiles();
|
|
1279
|
+
const agentsSrc = path.join(sourcePath, 'agents');
|
|
1280
|
+
const agentsDest = path.join(targetPath, 'agents');
|
|
1281
|
+
|
|
1282
|
+
if (!fs.existsSync(agentsSrc) || managed.agents.length === 0) return;
|
|
1283
|
+
|
|
1284
|
+
if (!fs.existsSync(agentsDest)) {
|
|
1285
|
+
fs.mkdirSync(agentsDest, { recursive: true });
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
for (const agent of managed.agents) {
|
|
1289
|
+
const src = path.join(agentsSrc, agent);
|
|
1290
|
+
const dest = path.join(agentsDest, agent);
|
|
1291
|
+
if (fs.existsSync(src)) {
|
|
1292
|
+
fs.copyFileSync(src, dest);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
log.success('Installed agents/');
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1041
1298
|
// Codex fresh installation
|
|
1042
1299
|
function performCodexFreshInstall(targetPath, sourcePath, isGlobal) {
|
|
1043
1300
|
if (!fs.existsSync(targetPath)) {
|
|
@@ -1047,6 +1304,7 @@ function performCodexFreshInstall(targetPath, sourcePath, isGlobal) {
|
|
|
1047
1304
|
|
|
1048
1305
|
// Install commands as Codex skills
|
|
1049
1306
|
installCodexSkills(targetPath, sourcePath);
|
|
1307
|
+
installCodexAgents(targetPath, sourcePath);
|
|
1050
1308
|
|
|
1051
1309
|
// Copy templates and references as-is (just path conversion)
|
|
1052
1310
|
for (const dir of ['templates', 'references']) {
|
|
@@ -1058,6 +1316,10 @@ function performCodexFreshInstall(targetPath, sourcePath, isGlobal) {
|
|
|
1058
1316
|
}
|
|
1059
1317
|
}
|
|
1060
1318
|
|
|
1319
|
+
// Install helper binaries used by runtime commands
|
|
1320
|
+
copyWorkflowHelperBins(targetPath);
|
|
1321
|
+
log.success('Installed bin/ (workflow helper binaries)');
|
|
1322
|
+
|
|
1061
1323
|
// Generate instructions.md (replaces hooks + settings.json)
|
|
1062
1324
|
generateCodexInstructions(targetPath);
|
|
1063
1325
|
|
|
@@ -1103,6 +1365,7 @@ function codexSelectiveUpdate(targetPath, sourcePath) {
|
|
|
1103
1365
|
|
|
1104
1366
|
// Re-install all skills
|
|
1105
1367
|
installCodexSkills(targetPath, sourcePath);
|
|
1368
|
+
installCodexAgents(targetPath, sourcePath);
|
|
1106
1369
|
|
|
1107
1370
|
// Update templates and references
|
|
1108
1371
|
for (const dir of ['templates', 'references']) {
|
|
@@ -1115,6 +1378,10 @@ function codexSelectiveUpdate(targetPath, sourcePath) {
|
|
|
1115
1378
|
}
|
|
1116
1379
|
log.success('Updated templates and references');
|
|
1117
1380
|
|
|
1381
|
+
// Update helper binaries used by runtime commands
|
|
1382
|
+
copyWorkflowHelperBins(targetPath);
|
|
1383
|
+
log.success('Updated bin/ helper binaries');
|
|
1384
|
+
|
|
1118
1385
|
// Regenerate instructions.md
|
|
1119
1386
|
generateCodexInstructions(targetPath);
|
|
1120
1387
|
}
|
|
@@ -1130,6 +1397,19 @@ function performCodexUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
|
|
|
1130
1397
|
const dataDir = getDataPath(isGlobal);
|
|
1131
1398
|
cleanupOrphanedFiles(targetPath, dataDir);
|
|
1132
1399
|
|
|
1400
|
+
// Rename create-* skill dirs to bare pattern names (create-dto → dto)
|
|
1401
|
+
renameCreateSkills(targetPath);
|
|
1402
|
+
|
|
1403
|
+
// Flag v1 → v2 major upgrade so skills can prompt for reconfigure
|
|
1404
|
+
const prevMajor = versionInfo.installed ? parseInt(versionInfo.installed.split('.')[0], 10) : 0;
|
|
1405
|
+
const newMajor = versionInfo.available ? parseInt(versionInfo.available.split('.')[0], 10) : 0;
|
|
1406
|
+
if (!isNaN(prevMajor) && !isNaN(newMajor) && prevMajor < newMajor) {
|
|
1407
|
+
try {
|
|
1408
|
+
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
|
1409
|
+
fs.writeFileSync(path.join(dataDir, '.migration-v' + newMajor), '1');
|
|
1410
|
+
} catch (e) {}
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1133
1413
|
// Update version.json (per-runtime, preserving other runtime's state)
|
|
1134
1414
|
writeVersionJson(dataDir, isGlobal, versionInfo.available);
|
|
1135
1415
|
ensureDotFiveGitignore(dataDir);
|
|
@@ -1148,7 +1428,7 @@ function performCodexUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
|
|
|
1148
1428
|
function codexUninstall() {
|
|
1149
1429
|
const targetPath = getTargetPath(false);
|
|
1150
1430
|
|
|
1151
|
-
log.header('
|
|
1431
|
+
log.header('dev-workflow Uninstallation (Codex)');
|
|
1152
1432
|
log.info(`Target: ${targetPath}`);
|
|
1153
1433
|
|
|
1154
1434
|
if (!checkExistingInstallation(targetPath)) {
|
|
@@ -1176,6 +1456,13 @@ function codexUninstall() {
|
|
|
1176
1456
|
}
|
|
1177
1457
|
log.success('Removed utility skills (preserved user-created skills)');
|
|
1178
1458
|
|
|
1459
|
+
// Remove agents
|
|
1460
|
+
for (const agent of managed.agents) {
|
|
1461
|
+
const agentPath = path.join(targetPath, 'agents', agent);
|
|
1462
|
+
if (fs.existsSync(agentPath)) fs.unlinkSync(agentPath);
|
|
1463
|
+
}
|
|
1464
|
+
log.success('Removed workflow agents');
|
|
1465
|
+
|
|
1179
1466
|
// Remove templates
|
|
1180
1467
|
for (const template of managed.templates) {
|
|
1181
1468
|
const templatePath = path.join(targetPath, 'templates', template);
|
|
@@ -1192,6 +1479,19 @@ function codexUninstall() {
|
|
|
1192
1479
|
log.success('Removed workflow references');
|
|
1193
1480
|
}
|
|
1194
1481
|
|
|
1482
|
+
// Remove helper binaries
|
|
1483
|
+
if (managed.binHelpers) {
|
|
1484
|
+
for (const binHelper of managed.binHelpers) {
|
|
1485
|
+
const binPath = path.join(targetPath, 'bin', binHelper);
|
|
1486
|
+
if (fs.existsSync(binPath)) fs.unlinkSync(binPath);
|
|
1487
|
+
}
|
|
1488
|
+
const binDir = path.join(targetPath, 'bin');
|
|
1489
|
+
if (fs.existsSync(binDir) && fs.readdirSync(binDir).length === 0) {
|
|
1490
|
+
fs.rmdirSync(binDir);
|
|
1491
|
+
}
|
|
1492
|
+
log.success('Removed workflow helper binaries');
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1195
1495
|
// Remove instructions.md
|
|
1196
1496
|
const instructionsPath = path.join(targetPath, 'instructions.md');
|
|
1197
1497
|
if (fs.existsSync(instructionsPath)) {
|
|
@@ -1217,7 +1517,7 @@ function install(isGlobal, forceUpgrade = false) {
|
|
|
1217
1517
|
const sourcePath = getSourcePath();
|
|
1218
1518
|
|
|
1219
1519
|
const runtimeLabel = activeRuntime === 'codex' ? 'Codex' : 'Claude Code';
|
|
1220
|
-
log.header(`
|
|
1520
|
+
log.header(`dev-workflow Installation (${runtimeLabel})`);
|
|
1221
1521
|
log.info(`Target: ${targetPath}`);
|
|
1222
1522
|
log.info(`Source: ${sourcePath}`);
|
|
1223
1523
|
|
|
@@ -1311,7 +1611,7 @@ function uninstall() {
|
|
|
1311
1611
|
|
|
1312
1612
|
const targetPath = getTargetPath(false); // Always local for uninstall
|
|
1313
1613
|
|
|
1314
|
-
log.header('
|
|
1614
|
+
log.header('dev-workflow Uninstallation');
|
|
1315
1615
|
log.info(`Target: ${targetPath}`);
|
|
1316
1616
|
|
|
1317
1617
|
if (!checkExistingInstallation(targetPath)) {
|
|
@@ -1389,6 +1689,21 @@ function uninstall() {
|
|
|
1389
1689
|
log.success('Removed workflow references (preserved user-created references)');
|
|
1390
1690
|
}
|
|
1391
1691
|
|
|
1692
|
+
// Remove only workflow-managed helper binaries
|
|
1693
|
+
if (managed.binHelpers) {
|
|
1694
|
+
for (const binHelper of managed.binHelpers) {
|
|
1695
|
+
const binPath = path.join(targetPath, 'bin', binHelper);
|
|
1696
|
+
if (fs.existsSync(binPath)) {
|
|
1697
|
+
fs.unlinkSync(binPath);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
const binDir = path.join(targetPath, 'bin');
|
|
1701
|
+
if (fs.existsSync(binDir) && fs.readdirSync(binDir).length === 0) {
|
|
1702
|
+
fs.rmdirSync(binDir);
|
|
1703
|
+
}
|
|
1704
|
+
log.success('Removed workflow helper binaries (preserved user-created binaries)');
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1392
1707
|
// Remove data directory (.5/)
|
|
1393
1708
|
const dataDir = getDataPath(false);
|
|
1394
1709
|
if (fs.existsSync(dataDir)) {
|
|
@@ -1457,7 +1772,7 @@ function main() {
|
|
|
1457
1772
|
}
|
|
1458
1773
|
|
|
1459
1774
|
if (anyUpdateAvailable) {
|
|
1460
|
-
log.info('Run: npx
|
|
1775
|
+
log.info('Run: npx foifi --upgrade');
|
|
1461
1776
|
}
|
|
1462
1777
|
|
|
1463
1778
|
activeRuntime = primaryRuntime;
|