devlyn-cli 4.2.18 → 4.2.19
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 +4 -3
- package/bin/devlyn.js +93 -55
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -120,8 +120,9 @@ or delete them; delete `~/.grok/skills`, since Grok now reads `~/.agents/skills`
|
|
|
120
120
|
Upgrading to 4.2.0: `/devlyn-resolve` and the pipeline helpers only it used are retired;
|
|
121
121
|
no measured replacement ships in their place. Run the installer again where devlyn is
|
|
122
122
|
installed. It removes the `devlyn-resolve` folders (and pre-4.0.0 `devlyn:resolve` ones)
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
only when they are unchanged devlyn-cli copies; others are left in place and named. It
|
|
124
|
+
removes the Stop hook it added to `.claude/settings.json` while keeping your own hooks,
|
|
125
|
+
and names the other settings it once added for the pipeline
|
|
125
126
|
(`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` and its `.devlyn` and git permissions) without
|
|
126
127
|
deleting them. `/devlyn-engines` keeps only the executor pin: `pair_judge_priority` and
|
|
127
128
|
`roles` in `.devlyn/engines.json` stay as written, select nothing, and `clear` removes them.
|
|
@@ -147,7 +148,7 @@ devlyn skill by its old name or path (for example Codex `[[skills.config]]` or o
|
|
|
147
148
|
`disabledExtensions`) needs the new name. A project that committed the old `devlyn:*`
|
|
148
149
|
folders checks out on Windows once that re-sync is committed.
|
|
149
150
|
|
|
150
|
-
Earlier versions of devlyn-cli shipped 16+ slash commands. The iter-0034 Phase 4 cutover (2026-05-04) and the 2026-05-14 follow-up consolidated them, 4.0.0 renamed every devlyn skill to the Agent Skills naming standard (`devlyn-<name>`: lowercase, digits and hyphens, the same as its folder), and after 4.1.0 resolve and design-ui were retired and the queue moved into ideate. Upgrades
|
|
151
|
+
Earlier versions of devlyn-cli shipped 16+ slash commands. The iter-0034 Phase 4 cutover (2026-05-04) and the 2026-05-14 follow-up consolidated them, 4.0.0 renamed every devlyn skill to the Agent Skills naming standard (`devlyn-<name>`: lowercase, digits and hyphens, the same as its folder), and after 4.1.0 resolve and design-ui were retired and the queue moved into ideate. Upgrades remove legacy skill directories only when they are unchanged devlyn-cli copies; others are left in place and named.
|
|
151
152
|
|
|
152
153
|
| Old command | Now use |
|
|
153
154
|
|---|---|
|
package/bin/devlyn.js
CHANGED
|
@@ -110,12 +110,13 @@ function hasDevlynInstall(root) {
|
|
|
110
110
|
const stat = (name) => fs.lstatSync(name, { throwIfNoEntry: false });
|
|
111
111
|
if (!stat(root)?.isDirectory()) return false;
|
|
112
112
|
const optional = new Set(OPTIONAL_ADDONS.map((addon) => addon.name));
|
|
113
|
-
const names = [...DEVLYN_CORE_SKILLS,
|
|
113
|
+
const names = [...DEVLYN_CORE_SKILLS,
|
|
114
114
|
...Object.keys(RENAMED_SKILLS)].filter((name) => name.startsWith('devlyn')
|
|
115
115
|
&& !optional.has(name.replace(/[:\uF03A]/g, '-')));
|
|
116
116
|
return !!stat(path.join(root, DEVLYN_INSTALL_LOCK))
|
|
117
117
|
|| names.some((name) => [...legacySkillPaths(root, name.replace(/^devlyn-/, 'devlyn:')),
|
|
118
118
|
path.join(root, name.replace(/:/g, '-'))].some((full) => stat(full)?.isDirectory()))
|
|
119
|
+
|| planDeprecatedSkills(root, {}).removals.size > 0
|
|
119
120
|
|| (!!stat(path.join(root, DEVLYN_INSTALL_MARKER)) && stat(path.join(root, '_shared'))?.isDirectory());
|
|
120
121
|
} catch (error) {
|
|
121
122
|
if (error.code === 'ENOTDIR') return false;
|
|
@@ -136,8 +137,15 @@ function hasDevlynClaude(global) {
|
|
|
136
137
|
const optional = new Set(OPTIONAL_ADDONS.map((addon) => addon.name));
|
|
137
138
|
const instructions = path.join(path.dirname(claudeDir), 'CLAUDE.md');
|
|
138
139
|
const claude = fs.lstatSync(instructions, { throwIfNoEntry: false })?.isFile() ? fs.readFileSync(instructions, 'utf8') : '';
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
const root = path.join(claudeDir, 'skills');
|
|
141
|
+
const deprecated = new Map([...deprecatedSkillPaths(root)].map((full) => [path.basename(full).toLowerCase(), full]));
|
|
142
|
+
const { removals } = planDeprecatedSkills(root, {});
|
|
143
|
+
return names(root).some((name) => {
|
|
144
|
+
const canonical = deprecated.get(name.toLowerCase());
|
|
145
|
+
if (canonical) return removals.has(canonical);
|
|
146
|
+
return name === DEVLYN_INSTALL_MARKER
|
|
147
|
+
|| (name.startsWith('devlyn') && !optional.has(name.replace(/[:\uF03A]/g, '-')));
|
|
148
|
+
})
|
|
141
149
|
|| names(path.join(claudeDir, 'commands')).some((name) => name.startsWith('devlyn.'))
|
|
142
150
|
|| holdsDevlynDefaults('CLAUDE.md', claude) || importsAgentsDefaults(claude);
|
|
143
151
|
}
|
|
@@ -368,24 +376,13 @@ function listContents() {
|
|
|
368
376
|
}
|
|
369
377
|
|
|
370
378
|
// Old and retired skills in one skill root; nothing outside it.
|
|
371
|
-
function cleanupDeprecated(skillsDir, written) {
|
|
379
|
+
function cleanupDeprecated(skillsDir, written, removals) {
|
|
372
380
|
let removed = 0;
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
for (const [relPath, fullPath] of deprecated) {
|
|
376
|
-
if (fs.existsSync(fullPath)) {
|
|
377
|
-
fs.rmSync(fullPath, { recursive: true });
|
|
378
|
-
written.push(fullPath);
|
|
379
|
-
log(` ✕ ${relPath}/ (removed)`, 'dim');
|
|
380
|
-
removed++;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
for (const [name, hashes] of Object.entries(RETIRED_SKILL_MD_SHA256)) {
|
|
384
|
-
const fullPath = path.join(skillsDir, name);
|
|
385
|
-
if (isShippedCopy(fullPath, hashes)) {
|
|
381
|
+
for (const fullPath of removals) {
|
|
382
|
+
if (path.dirname(fullPath) === skillsDir) {
|
|
386
383
|
fs.rmSync(fullPath, { recursive: true });
|
|
387
384
|
written.push(fullPath);
|
|
388
|
-
log(` ✕ skills/${
|
|
385
|
+
log(` ✕ skills/${path.basename(fullPath)}/ (removed)`, 'dim');
|
|
389
386
|
removed++;
|
|
390
387
|
}
|
|
391
388
|
}
|
|
@@ -462,24 +459,65 @@ function readInstallManifest(root) {
|
|
|
462
459
|
// touching any target. Optional addons must be selected before this preflight too.
|
|
463
460
|
function plannedSkills(root, optionalNames = []) {
|
|
464
461
|
const names = new Set([...DEVLYN_CORE_SKILLS, ...optionalNames]);
|
|
465
|
-
for (const relative of DEPRECATED_DIRS) {
|
|
466
|
-
for (const full of legacySkillPaths(root, path.basename(relative))) names.add(path.basename(full));
|
|
467
|
-
}
|
|
468
462
|
for (const [oldName, newName] of Object.entries(RENAMED_SKILLS)) {
|
|
469
463
|
const oldPaths = legacySkillPaths(root, oldName);
|
|
470
464
|
oldPaths.forEach((full) => names.add(path.basename(full)));
|
|
471
465
|
if (oldPaths.some((full) => fs.lstatSync(full, { throwIfNoEntry: false }))
|
|
472
466
|
|| isShippedCopy(path.join(root, newName), PRE_STANDARD_SKILL_MD_SHA256)) names.add(newName);
|
|
473
467
|
}
|
|
468
|
+
return names;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function verifySkill(dest, name, manifest, removalOnly = false) {
|
|
472
|
+
const actual = fingerprint(dest);
|
|
473
|
+
const logicalName = name.replace(/[:\uF03A]/g, '-');
|
|
474
|
+
const source = path.join(DEVLYN_CORE_SKILLS.includes(logicalName)
|
|
475
|
+
? path.join(CONFIG_SOURCE, 'skills') : OPTIONAL_SKILLS_SOURCE, logicalName);
|
|
476
|
+
const current = !removalOnly && fs.existsSync(source) && fingerprint(source) === actual;
|
|
477
|
+
const historical = SKILL_HISTORY.skills[logicalName]?.includes(actual);
|
|
478
|
+
const hashes = RETIRED_SKILL_MD_SHA256[name] || (removalOnly ? new Set() : PRE_STANDARD_SKILL_MD_SHA256);
|
|
479
|
+
const singleFile = isShippedCopy(dest, hashes);
|
|
480
|
+
if (!current && !historical && !singleFile && manifest[name] !== actual) {
|
|
481
|
+
throw new Error(manifest[name] ? 'modified contents (install manifest fingerprint mismatch)'
|
|
482
|
+
: fs.existsSync(source) || SKILL_HISTORY.skills[logicalName]?.length
|
|
483
|
+
? 'fingerprint mismatch with known shipped copies'
|
|
484
|
+
: 'unknown installer ownership (no fingerprint history for this copy)');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Removal-only paths never block an install: unverifiable copies stay with their owner.
|
|
489
|
+
function deprecatedSkillPaths(root) {
|
|
490
|
+
return new Set(DEPRECATED_DIRS.flatMap((relative) => legacySkillPaths(root, path.basename(relative))));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function planDeprecatedSkills(root, manifest) {
|
|
494
|
+
const removals = new Set();
|
|
495
|
+
const retained = new Set();
|
|
496
|
+
for (const dest of deprecatedSkillPaths(root)) {
|
|
497
|
+
try {
|
|
498
|
+
if (!fs.lstatSync(dest, { throwIfNoEntry: false })) continue;
|
|
499
|
+
verifySkill(dest, path.basename(dest), manifest, true);
|
|
500
|
+
removals.add(dest);
|
|
501
|
+
} catch (error) {
|
|
502
|
+
retained.add(dest);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return { removals, retained };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function planSkillRemovals(root, manifest) {
|
|
509
|
+
const plan = planDeprecatedSkills(root, manifest);
|
|
474
510
|
for (const [name, hashes] of Object.entries(RETIRED_SKILL_MD_SHA256)) {
|
|
475
|
-
|
|
511
|
+
const dest = path.join(root, name);
|
|
512
|
+
if (isShippedCopy(dest, hashes)) plan.removals.add(dest);
|
|
476
513
|
}
|
|
477
|
-
return
|
|
514
|
+
return plan;
|
|
478
515
|
}
|
|
479
516
|
|
|
480
517
|
// `commandDir`: the project .claude/ whose DEPRECATED_FILES this install deletes, or null.
|
|
481
518
|
function preflightSkills(roots, optionalNames = [], commandDir = null) {
|
|
482
519
|
const conflicts = [];
|
|
520
|
+
const plan = { removals: new Set(), retained: new Set() };
|
|
483
521
|
try {
|
|
484
522
|
// Do not follow a commands link into another installation either.
|
|
485
523
|
const commands = commandDir && fs.lstatSync(path.join(commandDir, 'commands'), { throwIfNoEntry: false });
|
|
@@ -511,6 +549,9 @@ function preflightSkills(roots, optionalNames = [], commandDir = null) {
|
|
|
511
549
|
conflicts.push(`${path.join(root, DEVLYN_INSTALL_MARKER)}: ${error.message}`);
|
|
512
550
|
continue;
|
|
513
551
|
}
|
|
552
|
+
const deprecated = planSkillRemovals(root, manifest);
|
|
553
|
+
for (const dest of deprecated.removals) plan.removals.add(dest);
|
|
554
|
+
for (const dest of deprecated.retained) plan.retained.add(dest);
|
|
514
555
|
for (const name of plannedSkills(root, optionalNames)) {
|
|
515
556
|
const dest = path.join(root, name);
|
|
516
557
|
const isTracked = tracked.some((file) => file.toLowerCase() === dest.toLowerCase()
|
|
@@ -520,19 +561,7 @@ function preflightSkills(roots, optionalNames = [], commandDir = null) {
|
|
|
520
561
|
if (isTracked) throw new Error('missing contents; cannot verify installer ownership');
|
|
521
562
|
continue;
|
|
522
563
|
}
|
|
523
|
-
|
|
524
|
-
const logicalName = name.replace(/[:\uF03A]/g, '-');
|
|
525
|
-
const source = path.join(DEVLYN_CORE_SKILLS.includes(logicalName)
|
|
526
|
-
? path.join(CONFIG_SOURCE, 'skills') : OPTIONAL_SKILLS_SOURCE, logicalName);
|
|
527
|
-
const current = fs.existsSync(source) && fingerprint(source) === actual;
|
|
528
|
-
const historical = SKILL_HISTORY.skills[logicalName]?.includes(actual);
|
|
529
|
-
const singleFile = isShippedCopy(dest, RETIRED_SKILL_MD_SHA256[name] || PRE_STANDARD_SKILL_MD_SHA256);
|
|
530
|
-
if (!current && !historical && !singleFile && manifest[name] !== actual) {
|
|
531
|
-
throw new Error(manifest[name] ? 'modified contents (install manifest fingerprint mismatch)'
|
|
532
|
-
: fs.existsSync(source) || SKILL_HISTORY.skills[logicalName]?.length
|
|
533
|
-
? 'fingerprint mismatch with known shipped copies'
|
|
534
|
-
: 'unknown installer ownership (no fingerprint history for this copy)');
|
|
535
|
-
}
|
|
564
|
+
verifySkill(dest, name, manifest);
|
|
536
565
|
} catch (error) {
|
|
537
566
|
conflicts.push(`${dest}: ${isTracked ? 'git-tracked; ' : ''}${error.message}`);
|
|
538
567
|
}
|
|
@@ -548,6 +577,7 @@ function preflightSkills(roots, optionalNames = [], commandDir = null) {
|
|
|
548
577
|
+ '\nFor tracked paths, also remove them from the Git index.'
|
|
549
578
|
+ '\nRerun the same command after resolving every conflict; -y does not bypass this check.');
|
|
550
579
|
}
|
|
580
|
+
return plan;
|
|
551
581
|
}
|
|
552
582
|
|
|
553
583
|
function assertCompleteSkillInstall(sourceSkillsDir, skillsDir, skillNames) {
|
|
@@ -721,13 +751,14 @@ function installAddon(addon) {
|
|
|
721
751
|
|
|
722
752
|
// The core skills into one skill root, replacing older copies and retired skills, then the
|
|
723
753
|
// marker that records a complete install.
|
|
724
|
-
function installCoreSkills(skillsDir, written = [], installedSkills = new Map()) {
|
|
754
|
+
function installCoreSkills(skillsDir, written = [], installedSkills = new Map(), plan) {
|
|
725
755
|
const sourceSkillsDir = path.join(CONFIG_SOURCE, 'skills');
|
|
726
756
|
log(`\n📁 Installing devlyn skills to ${skillsDir.replace(os.homedir(), '~')}`, 'green');
|
|
727
757
|
fs.mkdirSync(skillsDir, { recursive: true });
|
|
728
758
|
const previous = readInstallManifest(skillsDir);
|
|
759
|
+
plan ??= planSkillRemovals(skillsDir, previous);
|
|
729
760
|
clearInstallMarker(skillsDir);
|
|
730
|
-
const removed = cleanupDeprecated(skillsDir, written);
|
|
761
|
+
const removed = cleanupDeprecated(skillsDir, written, plan.removals);
|
|
731
762
|
if (removed > 0) {
|
|
732
763
|
log(`\n🧹 Cleaned up ${removed} deprecated file${removed > 1 ? 's' : ''}`, 'yellow');
|
|
733
764
|
}
|
|
@@ -817,7 +848,7 @@ function agentsMdIsClaudeMd() {
|
|
|
817
848
|
return target !== null && fs.existsSync(target) && fs.realpathSync.native(target) === fs.realpathSync.native(claude);
|
|
818
849
|
}
|
|
819
850
|
|
|
820
|
-
function installAgentsProject(withClaude, installedSkills = new Map()) {
|
|
851
|
+
function installAgentsProject(withClaude, installedSkills = new Map(), plan) {
|
|
821
852
|
const written = [];
|
|
822
853
|
if (!agentsMdIsClaudeMd()) {
|
|
823
854
|
if (updateInstructions('AGENTS.md')) written.push('AGENTS.md');
|
|
@@ -827,7 +858,7 @@ function installAgentsProject(withClaude, installedSkills = new Map()) {
|
|
|
827
858
|
throw new InstructionError('AGENTS.md is CLAUDE.md under another name here. Choose CLAUDE.md as well '
|
|
828
859
|
+ '(npx devlyn-cli -y --claude) so the devlyn block is written once, into CLAUDE.md.');
|
|
829
860
|
}
|
|
830
|
-
installCoreSkills(skillRoots('agents', false)[0], written, installedSkills);
|
|
861
|
+
installCoreSkills(skillRoots('agents', false)[0], written, installedSkills, plan);
|
|
831
862
|
if (updateProjectGitignore('.agents/skills/.devlyn-install.json')) written.push('.gitignore');
|
|
832
863
|
return written;
|
|
833
864
|
}
|
|
@@ -859,7 +890,7 @@ function installClaudeInstructions() {
|
|
|
859
890
|
}
|
|
860
891
|
|
|
861
892
|
// Project CLAUDE.md and .claude/: skills and settings.
|
|
862
|
-
function installClaudeCore(installedSkills = new Map()) {
|
|
893
|
+
function installClaudeCore(installedSkills = new Map(), plan) {
|
|
863
894
|
const skillsDir = skillRoots('claude', false)[0];
|
|
864
895
|
const targetDir = path.dirname(skillsDir);
|
|
865
896
|
// Read before any write, so settings this install cannot merge leave the Claude target as it was.
|
|
@@ -891,7 +922,7 @@ function installClaudeCore(installedSkills = new Map()) {
|
|
|
891
922
|
written.push(`.claude/${relPath}`);
|
|
892
923
|
}
|
|
893
924
|
}
|
|
894
|
-
installCoreSkills(skillsDir, written, installedSkills);
|
|
925
|
+
installCoreSkills(skillsDir, written, installedSkills, plan);
|
|
895
926
|
if (updateProjectGitignore('.claude/skills/.devlyn-install.json')) written.push('.gitignore');
|
|
896
927
|
|
|
897
928
|
// One-hour foreground Bash calls and one-hour prompt caching serve a drain host that waits in the foreground.
|
|
@@ -1007,7 +1038,7 @@ function withUserSkillLocks(roots, action) {
|
|
|
1007
1038
|
}
|
|
1008
1039
|
|
|
1009
1040
|
// Installs the targets in one scope; returns the skill roots written.
|
|
1010
|
-
function install(targets, global, optionalNames = []) {
|
|
1041
|
+
function install(targets, global, optionalNames = [], plan) {
|
|
1011
1042
|
// In the home folder CLAUDE.md, AGENTS.md (agents read a parent folder's too) and
|
|
1012
1043
|
// .claude/settings.json would apply to every project. Same folder by identity, so a link or
|
|
1013
1044
|
// another spelling of the path is caught too.
|
|
@@ -1022,23 +1053,32 @@ function install(targets, global, optionalNames = []) {
|
|
|
1022
1053
|
const written = [];
|
|
1023
1054
|
const installedSkills = new Map();
|
|
1024
1055
|
if (!global) readProjectGitignore();
|
|
1025
|
-
preflightSkills(roots, optionalNames,
|
|
1056
|
+
plan ??= preflightSkills(roots, optionalNames,
|
|
1026
1057
|
!global && targets.includes('claude') ? path.dirname(skillRoots('claude', false)[0]) : null);
|
|
1027
1058
|
// AGENTS.md first, whatever order the menu gave: a new CLAUDE.md holds the block only when AGENTS.md does not.
|
|
1028
1059
|
for (const target of ['agents', 'claude'].filter((name) => targets.includes(name))) {
|
|
1029
|
-
if (global) skillRoots(target, true).forEach((root) => installCoreSkills(root));
|
|
1030
|
-
else if (target === 'agents') written.push(...installAgentsProject(targets.includes('claude'), installedSkills));
|
|
1031
|
-
else written.push(...installClaudeCore(installedSkills));
|
|
1060
|
+
if (global) skillRoots(target, true).forEach((root) => installCoreSkills(root, [], installedSkills, plan));
|
|
1061
|
+
else if (target === 'agents') written.push(...installAgentsProject(targets.includes('claude'), installedSkills, plan));
|
|
1062
|
+
else written.push(...installClaudeCore(installedSkills, plan));
|
|
1032
1063
|
}
|
|
1033
1064
|
for (const name of optionalNames) installLocalSkill(name, roots, written, installedSkills);
|
|
1034
1065
|
log(`\n✅ devlyn ${PKG.version} installed`, 'green');
|
|
1035
1066
|
if (global) log('The principles block installs per project: run npx devlyn-cli in the project without --global.');
|
|
1036
1067
|
noticeGlobalDrift(roots);
|
|
1068
|
+
noticeRetainedSkills(plan.retained);
|
|
1037
1069
|
if (!global) noticeProjectChanges(written, installedSkills);
|
|
1038
1070
|
return roots;
|
|
1039
1071
|
});
|
|
1040
1072
|
}
|
|
1041
1073
|
|
|
1074
|
+
function noticeRetainedSkills(retained) {
|
|
1075
|
+
if (!retained.size) return;
|
|
1076
|
+
log('Left in place (not verified as unchanged devlyn-cli copies):\n'
|
|
1077
|
+
+ [...retained].map((dest) => ` ${dest}`).join('\n')
|
|
1078
|
+
+ '\nThese retired skills may still be visible to agents and may call removed helpers. '
|
|
1079
|
+
+ 'Move them out of the skills folder, or delete them, if you no longer use them.', 'yellow');
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1042
1082
|
// Advisory only: a successful install stays successful when Git is unavailable or fails.
|
|
1043
1083
|
function noticeProjectChanges(paths, installedSkills = new Map()) {
|
|
1044
1084
|
if (!paths.length && !installedSkills.size) return;
|
|
@@ -1095,7 +1135,7 @@ function noticeProjectChanges(paths, installedSkills = new Map()) {
|
|
|
1095
1135
|
}
|
|
1096
1136
|
if (!pending.length && !ignored.length) return;
|
|
1097
1137
|
const quote = (name) => /^[a-zA-Z0-9_./-]+$/.test(name) ? name : `'${name.replace(/'/g, "'\\''")}'`;
|
|
1098
|
-
log('
|
|
1138
|
+
log('Optional — commit (and push) these changes if teammates or /devlyn-ideate task worktrees should use them:', 'yellow');
|
|
1099
1139
|
if (ignored.length) log(`.gitignore hides ${ignored.length} installed files (${rule}).`, 'yellow');
|
|
1100
1140
|
const commands = [];
|
|
1101
1141
|
if (pending.length) commands.push(`git add -- ${pending.map(quote).join(' ')}`);
|
|
@@ -1167,11 +1207,8 @@ function update() {
|
|
|
1167
1207
|
}
|
|
1168
1208
|
|
|
1169
1209
|
return withUserSkillLocks(userRoots, () => {
|
|
1170
|
-
if (targets.length)
|
|
1171
|
-
|
|
1172
|
-
preflightSkills(projectRoots, [], targets.includes('claude') ? path.dirname(skillRoots('claude', false)[0]) : null);
|
|
1173
|
-
}
|
|
1174
|
-
preflightSkills(userRoots);
|
|
1210
|
+
if (targets.length) readProjectGitignore();
|
|
1211
|
+
const plan = preflightSkills(roots, [], targets.includes('claude') ? path.dirname(skillRoots('claude', false)[0]) : null);
|
|
1175
1212
|
// Ownership preflight has already refused unreadable or invalid markers.
|
|
1176
1213
|
const current = PKG.version.split('.').map(BigInt);
|
|
1177
1214
|
const newer = roots.flatMap((root) => {
|
|
@@ -1194,11 +1231,12 @@ function update() {
|
|
|
1194
1231
|
+ newer.join('\n') + '\nRun npx devlyn-cli@latest update.');
|
|
1195
1232
|
}
|
|
1196
1233
|
|
|
1197
|
-
userRoots.forEach((root) => installCoreSkills(root));
|
|
1198
|
-
if (targets.length) install(targets, false);
|
|
1234
|
+
userRoots.forEach((root) => installCoreSkills(root, [], new Map(), plan));
|
|
1235
|
+
if (targets.length) install(targets, false, [], plan);
|
|
1199
1236
|
else {
|
|
1200
1237
|
log(`\n✅ devlyn ${PKG.version} installed`, 'green');
|
|
1201
1238
|
noticeGlobalDrift(userRoots);
|
|
1239
|
+
noticeRetainedSkills(plan.retained);
|
|
1202
1240
|
}
|
|
1203
1241
|
});
|
|
1204
1242
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devlyn-cli",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.19",
|
|
4
4
|
"description": "Loop & harness engineering for AI coding agents (Claude Code, Codex, and any AGENTS.md-driven agent) — core engineering principles for CLAUDE.md/AGENTS.md and a loop designer that splits one intent into tasks agents drain hands-free",
|
|
5
5
|
"homepage": "https://github.com/fysoul17/devlyn-cli#readme",
|
|
6
6
|
"bin": {
|