devlyn-cli 4.2.0 → 4.2.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 +4 -2
- package/bin/devlyn.js +170 -143
- package/bin/skill-history.json +140 -0
- package/bin/skill-ownership.js +67 -0
- package/node_modules/@inquirer/ansi/LICENSE +22 -0
- package/node_modules/@inquirer/ansi/README.md +121 -0
- package/node_modules/@inquirer/ansi/dist/index.d.ts +14 -0
- package/node_modules/@inquirer/ansi/dist/index.js +21 -0
- package/node_modules/@inquirer/ansi/package.json +79 -0
- package/node_modules/@inquirer/checkbox/LICENSE +22 -0
- package/node_modules/@inquirer/checkbox/README.md +265 -0
- package/node_modules/@inquirer/checkbox/dist/index.d.ts +67 -0
- package/node_modules/@inquirer/checkbox/dist/index.js +210 -0
- package/node_modules/@inquirer/checkbox/package.json +93 -0
- package/node_modules/@inquirer/core/LICENSE +22 -0
- package/node_modules/@inquirer/core/README.md +424 -0
- package/node_modules/@inquirer/core/dist/index.d.ts +13 -0
- package/node_modules/@inquirer/core/dist/index.js +12 -0
- package/node_modules/@inquirer/core/dist/lib/Separator.d.ts +10 -0
- package/node_modules/@inquirer/core/dist/lib/Separator.js +21 -0
- package/node_modules/@inquirer/core/dist/lib/__probe/p1.d.ts +9 -0
- package/node_modules/@inquirer/core/dist/lib/__probe/p1.js +4 -0
- package/node_modules/@inquirer/core/dist/lib/__probe/p2.d.ts +9 -0
- package/node_modules/@inquirer/core/dist/lib/__probe/p2.js +3 -0
- package/node_modules/@inquirer/core/dist/lib/__probe/p3.d.ts +9 -0
- package/node_modules/@inquirer/core/dist/lib/__probe/p3.js +5 -0
- package/node_modules/@inquirer/core/dist/lib/create-prompt.d.ts +11 -0
- package/node_modules/@inquirer/core/dist/lib/create-prompt.js +185 -0
- package/node_modules/@inquirer/core/dist/lib/errors.d.ts +20 -0
- package/node_modules/@inquirer/core/dist/lib/errors.js +21 -0
- package/node_modules/@inquirer/core/dist/lib/hook-engine.d.ts +17 -0
- package/node_modules/@inquirer/core/dist/lib/hook-engine.js +109 -0
- package/node_modules/@inquirer/core/dist/lib/key.d.ts +15 -0
- package/node_modules/@inquirer/core/dist/lib/key.js +34 -0
- package/node_modules/@inquirer/core/dist/lib/make-theme.d.ts +3 -0
- package/node_modules/@inquirer/core/dist/lib/make-theme.js +33 -0
- package/node_modules/@inquirer/core/dist/lib/pagination/use-pagination.d.ts +15 -0
- package/node_modules/@inquirer/core/dist/lib/pagination/use-pagination.js +121 -0
- package/node_modules/@inquirer/core/dist/lib/screen-manager.d.ts +14 -0
- package/node_modules/@inquirer/core/dist/lib/screen-manager.js +84 -0
- package/node_modules/@inquirer/core/dist/lib/theme.d.ts +166 -0
- package/node_modules/@inquirer/core/dist/lib/theme.js +29 -0
- package/node_modules/@inquirer/core/dist/lib/use-effect.d.ts +2 -0
- package/node_modules/@inquirer/core/dist/lib/use-effect.js +11 -0
- package/node_modules/@inquirer/core/dist/lib/use-keypress.d.ts +3 -0
- package/node_modules/@inquirer/core/dist/lib/use-keypress.js +20 -0
- package/node_modules/@inquirer/core/dist/lib/use-memo.d.ts +1 -0
- package/node_modules/@inquirer/core/dist/lib/use-memo.js +14 -0
- package/node_modules/@inquirer/core/dist/lib/use-prefix.d.ts +5 -0
- package/node_modules/@inquirer/core/dist/lib/use-prefix.js +35 -0
- package/node_modules/@inquirer/core/dist/lib/use-ref.d.ts +6 -0
- package/node_modules/@inquirer/core/dist/lib/use-ref.js +4 -0
- package/node_modules/@inquirer/core/dist/lib/use-state.d.ts +7 -0
- package/node_modules/@inquirer/core/dist/lib/use-state.js +28 -0
- package/node_modules/@inquirer/core/dist/lib/utils.d.ts +13 -0
- package/node_modules/@inquirer/core/dist/lib/utils.js +25 -0
- package/node_modules/@inquirer/core/package.json +98 -0
- package/node_modules/@inquirer/figures/LICENSE +22 -0
- package/node_modules/@inquirer/figures/dist/index.d.ts +275 -0
- package/node_modules/@inquirer/figures/dist/index.js +315 -0
- package/node_modules/@inquirer/figures/package.json +81 -0
- package/node_modules/@inquirer/select/LICENSE +22 -0
- package/node_modules/@inquirer/select/README.md +258 -0
- package/node_modules/@inquirer/select/dist/index.d.ts +42 -0
- package/node_modules/@inquirer/select/dist/index.js +191 -0
- package/node_modules/@inquirer/select/package.json +93 -0
- package/node_modules/@inquirer/type/LICENSE +22 -0
- package/node_modules/@inquirer/type/dist/index.d.ts +2 -0
- package/node_modules/@inquirer/type/dist/index.js +2 -0
- package/node_modules/@inquirer/type/dist/inquirer.d.ts +35 -0
- package/node_modules/@inquirer/type/dist/inquirer.js +1 -0
- package/node_modules/@inquirer/type/dist/utils.d.ts +45 -0
- package/node_modules/@inquirer/type/dist/utils.js +16 -0
- package/node_modules/@inquirer/type/package.json +88 -0
- package/node_modules/cli-width/LICENSE +13 -0
- package/node_modules/cli-width/README.md +71 -0
- package/node_modules/cli-width/index.d.ts +13 -0
- package/node_modules/cli-width/index.js +49 -0
- package/node_modules/cli-width/package.json +40 -0
- package/node_modules/fast-string-truncated-width/dist/index.d.ts +4 -0
- package/node_modules/fast-string-truncated-width/dist/index.js +111 -0
- package/node_modules/fast-string-truncated-width/dist/types.d.ts +19 -0
- package/node_modules/fast-string-truncated-width/dist/types.js +2 -0
- package/node_modules/fast-string-truncated-width/dist/utils.d.ts +4 -0
- package/node_modules/fast-string-truncated-width/dist/utils.js +20 -0
- package/node_modules/fast-string-truncated-width/license +21 -0
- package/node_modules/fast-string-truncated-width/package.json +35 -0
- package/node_modules/fast-string-truncated-width/readme.md +59 -0
- package/node_modules/fast-string-width/dist/index.d.ts +4 -0
- package/node_modules/fast-string-width/dist/index.js +14 -0
- package/node_modules/fast-string-width/license +21 -0
- package/node_modules/fast-string-width/package.json +34 -0
- package/node_modules/fast-string-width/readme.md +42 -0
- package/node_modules/fast-wrap-ansi/LICENSE +23 -0
- package/node_modules/fast-wrap-ansi/README.md +26 -0
- package/node_modules/fast-wrap-ansi/lib/main.d.ts +6 -0
- package/node_modules/fast-wrap-ansi/lib/main.js +218 -0
- package/node_modules/fast-wrap-ansi/package.json +51 -0
- package/node_modules/mute-stream/LICENSE +15 -0
- package/node_modules/mute-stream/README.md +68 -0
- package/node_modules/mute-stream/lib/index.js +142 -0
- package/node_modules/mute-stream/package.json +54 -0
- package/node_modules/signal-exit/LICENSE.txt +16 -0
- package/node_modules/signal-exit/README.md +74 -0
- package/node_modules/signal-exit/dist/cjs/browser.d.ts +12 -0
- package/node_modules/signal-exit/dist/cjs/browser.d.ts.map +1 -0
- package/node_modules/signal-exit/dist/cjs/browser.js +10 -0
- package/node_modules/signal-exit/dist/cjs/browser.js.map +1 -0
- package/node_modules/signal-exit/dist/cjs/index.d.ts +48 -0
- package/node_modules/signal-exit/dist/cjs/index.d.ts.map +1 -0
- package/node_modules/signal-exit/dist/cjs/index.js +279 -0
- package/node_modules/signal-exit/dist/cjs/index.js.map +1 -0
- package/node_modules/signal-exit/dist/cjs/package.json +3 -0
- package/node_modules/signal-exit/dist/cjs/signals.d.ts +29 -0
- package/node_modules/signal-exit/dist/cjs/signals.d.ts.map +1 -0
- package/node_modules/signal-exit/dist/cjs/signals.js +42 -0
- package/node_modules/signal-exit/dist/cjs/signals.js.map +1 -0
- package/node_modules/signal-exit/dist/mjs/browser.d.ts +12 -0
- package/node_modules/signal-exit/dist/mjs/browser.d.ts.map +1 -0
- package/node_modules/signal-exit/dist/mjs/browser.js +4 -0
- package/node_modules/signal-exit/dist/mjs/browser.js.map +1 -0
- package/node_modules/signal-exit/dist/mjs/index.d.ts +48 -0
- package/node_modules/signal-exit/dist/mjs/index.d.ts.map +1 -0
- package/node_modules/signal-exit/dist/mjs/index.js +275 -0
- package/node_modules/signal-exit/dist/mjs/index.js.map +1 -0
- package/node_modules/signal-exit/dist/mjs/package.json +3 -0
- package/node_modules/signal-exit/dist/mjs/signals.d.ts +29 -0
- package/node_modules/signal-exit/dist/mjs/signals.d.ts.map +1 -0
- package/node_modules/signal-exit/dist/mjs/signals.js +39 -0
- package/node_modules/signal-exit/dist/mjs/signals.js.map +1 -0
- package/node_modules/signal-exit/package.json +106 -0
- package/package.json +16 -2
package/README.md
CHANGED
|
@@ -215,10 +215,12 @@ Selected during install. Run `npx devlyn-cli` again to add more.
|
|
|
215
215
|
|
|
216
216
|
---
|
|
217
217
|
|
|
218
|
+
Installer updates preserve user work: before writing any selected target, devlyn refuses to replace or remove git-tracked skills, edited skills, or directories with extra files (including hidden files). `-y` does not bypass this check. Commit your work or move conflicting folders to a backup and migrate them manually; tracked folders must be moved out of the install path before rerunning. Unchanged installer copies and pristine untracked 4.1.0 installs upgrade automatically. Older copies without verifiable content history require manual migration.
|
|
219
|
+
|
|
218
220
|
## Requirements
|
|
219
221
|
|
|
220
|
-
- **Node.js
|
|
221
|
-
- **Python 3.11+** available as `python3
|
|
222
|
+
- **Node.js 20 (20.17+), 22 (22.13+) or 23.5+** and npm
|
|
223
|
+
- **Python 3.11+** available as `python3` for the harness, and **Git** for safe installation and the harness
|
|
222
224
|
- **An agent CLI** installed and configured: Codex, omp, Pi or Grok (AGENTS.md), or [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (CLAUDE.md)
|
|
223
225
|
- **For `auto`/`pr` delivery**, an `origin` remote naming one GitHub repository and an authenticated [`gh`](https://cli.github.com/)
|
|
224
226
|
|
package/bin/devlyn.js
CHANGED
|
@@ -4,13 +4,14 @@ const crypto = require('crypto');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const os = require('os');
|
|
7
|
-
const readline = require('readline');
|
|
8
7
|
const { execSync } = require('child_process');
|
|
9
8
|
|
|
10
9
|
const CONFIG_SOURCE = path.join(__dirname, '..', 'config');
|
|
11
10
|
const OPTIONAL_SKILLS_SOURCE = path.join(__dirname, '..', 'optional-skills');
|
|
12
11
|
const PKG = require('../package.json');
|
|
13
12
|
const { updateInstructions, InstructionError, holdsDevlynDefaults, importsAgentsMd } = require('./instructions');
|
|
13
|
+
const { contentHash, fingerprint, isOsMetadata, trackedPaths } = require('./skill-ownership');
|
|
14
|
+
const SKILL_HISTORY = require('./skill-history.json');
|
|
14
15
|
|
|
15
16
|
// The devlyn skill bundle installed into every skill-capable agent's loader
|
|
16
17
|
// directory. Single source of truth so codex/omp/pi stay in lockstep — adding a
|
|
@@ -82,13 +83,13 @@ const RETIRED_SKILL_MD_SHA256 = {
|
|
|
82
83
|
]),
|
|
83
84
|
};
|
|
84
85
|
|
|
85
|
-
// Whether `dir` is a real folder whose only file
|
|
86
|
+
// Whether `dir` is a real folder whose only file, besides OS metadata, is a SKILL.md
|
|
86
87
|
// with an LF-normalized SHA-256 in `hashes`: an unedited copy devlyn-cli shipped, not something
|
|
87
88
|
// the user added to or wrote.
|
|
88
89
|
function isShippedCopy(dir, hashes) {
|
|
89
90
|
const skill = path.join(dir, 'SKILL.md');
|
|
90
91
|
return fs.lstatSync(dir, { throwIfNoEntry: false })?.isDirectory() === true
|
|
91
|
-
&& fs.readdirSync(dir).filter((name) => !
|
|
92
|
+
&& fs.readdirSync(dir).filter((name) => !isOsMetadata(path.join(dir, name))).length === 1
|
|
92
93
|
&& fs.lstatSync(skill, { throwIfNoEntry: false })?.isFile() === true
|
|
93
94
|
&& hashes.has(crypto.createHash('sha256')
|
|
94
95
|
.update(fs.readFileSync(skill, 'utf8').replace(/\r\n/g, '\n')).digest('hex'));
|
|
@@ -121,7 +122,8 @@ function hasDevlynClaude(global) {
|
|
|
121
122
|
|| holdsDevlynDefaults('CLAUDE.md', claude) || importsAgentsDefaults(claude);
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
// Commands removed in previous versions; the project Claude install deletes them from .claude
|
|
125
|
+
// Commands removed in previous versions; the project Claude install deletes them from .claude/
|
|
126
|
+
// when they hold the bytes a version shipped (SKILL_HISTORY.commands).
|
|
125
127
|
const DEPRECATED_FILES = [
|
|
126
128
|
'commands/devlyn.handoff.md', // removed in v0.2.0
|
|
127
129
|
'commands/devlyn.clean.md', // migrated to skills in v0.6.0
|
|
@@ -146,7 +148,7 @@ const DEPRECATED_FILES = [
|
|
|
146
148
|
// Skill directories renamed from devlyn-* to devlyn:* in v0.7.x, plus
|
|
147
149
|
// iter-0034 Phase 4 cutover (2026-05-03): 15 user skills deleted and 3 moved
|
|
148
150
|
// to optional-skills/. Listed here so post-cutover `npx devlyn-cli` upgrades
|
|
149
|
-
//
|
|
151
|
+
// remove verified, unchanged legacy skill dirs from downstream `~/.claude/skills/`
|
|
150
152
|
// even though the source dirs no longer exist (the installer replaces only
|
|
151
153
|
// the skills it ships — without this list, deleted-from-source skills persist
|
|
152
154
|
// in user installs forever).
|
|
@@ -374,15 +376,22 @@ function cleanupDeprecated(skillsDir) {
|
|
|
374
376
|
// copy is refreshed in place. Any other folder under a 4.0 name is the user's own; `-y` leaves it.
|
|
375
377
|
function retireRenamedSkills(skillsDir) {
|
|
376
378
|
const optional = new Set(OPTIONAL_ADDONS.filter((addon) => addon.type === 'local').map((addon) => addon.name));
|
|
379
|
+
const refreshed = [];
|
|
377
380
|
for (const [oldName, newName] of Object.entries(RENAMED_SKILLS)) {
|
|
378
381
|
const found = legacySkillPaths(skillsDir, oldName).filter((fullPath) => fs.existsSync(fullPath));
|
|
379
|
-
if (found.length > 0 && optional.has(newName))
|
|
380
|
-
|
|
382
|
+
if (found.length > 0 && optional.has(newName)) {
|
|
383
|
+
installOptionalSkillInto(skillsDir, newName);
|
|
384
|
+
refreshed.push(newName);
|
|
385
|
+
} else if (isShippedCopy(path.join(skillsDir, newName), PRE_STANDARD_SKILL_MD_SHA256)) {
|
|
386
|
+
refreshPreStandardCopy(skillsDir, newName);
|
|
387
|
+
refreshed.push(newName);
|
|
388
|
+
}
|
|
381
389
|
for (const fullPath of found) {
|
|
382
390
|
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
383
391
|
log(` ✕ ${path.basename(fullPath)}/ (renamed to ${newName})`, 'dim');
|
|
384
392
|
}
|
|
385
393
|
}
|
|
394
|
+
return refreshed;
|
|
386
395
|
}
|
|
387
396
|
|
|
388
397
|
function copyRecursive(src, dest, baseDir) {
|
|
@@ -410,6 +419,101 @@ function clearInstallMarker(skillsDir) {
|
|
|
410
419
|
fs.rmSync(path.join(skillsDir, DEVLYN_INSTALL_MARKER), { force: true });
|
|
411
420
|
}
|
|
412
421
|
|
|
422
|
+
function readInstallManifest(root) {
|
|
423
|
+
const markerPath = path.join(root, DEVLYN_INSTALL_MARKER);
|
|
424
|
+
const stat = fs.lstatSync(markerPath, { throwIfNoEntry: false });
|
|
425
|
+
if (!stat) return {};
|
|
426
|
+
if (!stat.isFile()) throw new Error(`not a regular install marker: ${markerPath}`);
|
|
427
|
+
const marker = JSON.parse(fs.readFileSync(markerPath, 'utf8'));
|
|
428
|
+
return marker?.package === PKG.name && marker.schemaVersion === 1 ? marker.skills || {} : {};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Enumerate every directory this install may replace, migrate or remove, before
|
|
432
|
+
// touching any target. Optional addons must be selected before this preflight too.
|
|
433
|
+
function plannedSkills(root, optionalNames = []) {
|
|
434
|
+
const names = new Set([...DEVLYN_CORE_SKILLS, ...optionalNames]);
|
|
435
|
+
for (const relative of DEPRECATED_DIRS) {
|
|
436
|
+
for (const full of legacySkillPaths(root, path.basename(relative))) names.add(path.basename(full));
|
|
437
|
+
}
|
|
438
|
+
for (const [oldName, newName] of Object.entries(RENAMED_SKILLS)) {
|
|
439
|
+
const oldPaths = legacySkillPaths(root, oldName);
|
|
440
|
+
oldPaths.forEach((full) => names.add(path.basename(full)));
|
|
441
|
+
if (oldPaths.some((full) => fs.lstatSync(full, { throwIfNoEntry: false }))
|
|
442
|
+
|| isShippedCopy(path.join(root, newName), PRE_STANDARD_SKILL_MD_SHA256)) names.add(newName);
|
|
443
|
+
}
|
|
444
|
+
for (const [name, hashes] of Object.entries(RETIRED_SKILL_MD_SHA256)) {
|
|
445
|
+
if (isShippedCopy(path.join(root, name), hashes)) names.add(name);
|
|
446
|
+
}
|
|
447
|
+
return names;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// `commandDir`: the project .claude/ whose DEPRECATED_FILES this install deletes, or null.
|
|
451
|
+
function preflightSkills(roots, optionalNames = [], commandDir = null) {
|
|
452
|
+
const conflicts = [];
|
|
453
|
+
try {
|
|
454
|
+
// Do not follow a commands link into another installation either.
|
|
455
|
+
const commands = commandDir && fs.lstatSync(path.join(commandDir, 'commands'), { throwIfNoEntry: false });
|
|
456
|
+
if (commands && !commands.isDirectory()) throw new Error('not a real directory');
|
|
457
|
+
const tracked = commandDir ? trackedPaths(path.join(commandDir, 'commands')) : [];
|
|
458
|
+
for (const relPath of commandDir ? DEPRECATED_FILES : []) {
|
|
459
|
+
const fullPath = path.join(commandDir, relPath);
|
|
460
|
+
const stat = fs.lstatSync(fullPath, { throwIfNoEntry: false });
|
|
461
|
+
if (!stat || (stat.isFile() && SKILL_HISTORY.commands[relPath].includes(contentHash(fullPath)))) continue;
|
|
462
|
+
const isTracked = tracked.some((file) => file.toLowerCase() === fullPath.toLowerCase());
|
|
463
|
+
conflicts.push(`${fullPath}: ${isTracked ? 'git-tracked; ' : ''}`
|
|
464
|
+
+ (stat.isFile() ? 'fingerprint mismatch with known shipped copies' : 'link or special file'));
|
|
465
|
+
}
|
|
466
|
+
} catch (error) {
|
|
467
|
+
conflicts.push(`${path.join(commandDir, 'commands')}: ${error.message}`);
|
|
468
|
+
}
|
|
469
|
+
for (const root of roots) {
|
|
470
|
+
try {
|
|
471
|
+
// Do not follow an agent-directory or skill-root link into another installation.
|
|
472
|
+
for (const dir of [path.dirname(root), root]) {
|
|
473
|
+
const stat = fs.lstatSync(dir, { throwIfNoEntry: false });
|
|
474
|
+
if (stat && !stat.isDirectory()) throw new Error(`not a real directory: ${dir}`);
|
|
475
|
+
}
|
|
476
|
+
const tracked = trackedPaths(root);
|
|
477
|
+
const manifest = readInstallManifest(root);
|
|
478
|
+
for (const name of plannedSkills(root, optionalNames)) {
|
|
479
|
+
const dest = path.join(root, name);
|
|
480
|
+
const isTracked = tracked.some((file) => file.toLowerCase() === dest.toLowerCase()
|
|
481
|
+
|| file.toLowerCase().startsWith(dest.toLowerCase() + path.sep));
|
|
482
|
+
try {
|
|
483
|
+
if (!fs.lstatSync(dest, { throwIfNoEntry: false })) {
|
|
484
|
+
if (isTracked) throw new Error('missing contents; cannot verify installer ownership');
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
const actual = fingerprint(dest);
|
|
488
|
+
const logicalName = name.replace(/[:\uF03A]/g, '-');
|
|
489
|
+
const source = path.join(DEVLYN_CORE_SKILLS.includes(logicalName)
|
|
490
|
+
? path.join(CONFIG_SOURCE, 'skills') : OPTIONAL_SKILLS_SOURCE, logicalName);
|
|
491
|
+
const current = fs.existsSync(source) && fingerprint(source) === actual;
|
|
492
|
+
const historical = SKILL_HISTORY.skills[logicalName]?.includes(actual);
|
|
493
|
+
const singleFile = isShippedCopy(dest, RETIRED_SKILL_MD_SHA256[name] || PRE_STANDARD_SKILL_MD_SHA256);
|
|
494
|
+
if (!current && !historical && !singleFile && manifest[name] !== actual) {
|
|
495
|
+
throw new Error(manifest[name] ? 'modified contents (install manifest fingerprint mismatch)'
|
|
496
|
+
: fs.existsSync(source) || SKILL_HISTORY.skills[logicalName]?.length
|
|
497
|
+
? 'fingerprint mismatch with known shipped copies'
|
|
498
|
+
: 'unknown installer ownership (no fingerprint history for this copy)');
|
|
499
|
+
}
|
|
500
|
+
} catch (error) {
|
|
501
|
+
conflicts.push(`${dest}: ${isTracked ? 'git-tracked; ' : ''}${error.message}`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
} catch (error) {
|
|
505
|
+
conflicts.push(`${root}: ${error.message}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (conflicts.length) {
|
|
509
|
+
throw new InstructionError('Installation refused; nothing was changed:\n'
|
|
510
|
+
+ conflicts.map((conflict) => ` - ${conflict}`).join('\n')
|
|
511
|
+
+ '\nMove these out of the install path, or back them up and remove them, then rerun.'
|
|
512
|
+
+ '\nFor tracked paths, also remove them from the Git index.'
|
|
513
|
+
+ '\nRerun the same command after resolving every conflict; -y does not bypass this check.');
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
413
517
|
function assertCompleteSkillInstall(sourceSkillsDir, skillsDir, skillNames) {
|
|
414
518
|
function complete(src, dest) {
|
|
415
519
|
if (!fs.existsSync(src) || !fs.existsSync(dest)) return false;
|
|
@@ -429,13 +533,14 @@ function assertCompleteSkillInstall(sourceSkillsDir, skillsDir, skillNames) {
|
|
|
429
533
|
}
|
|
430
534
|
}
|
|
431
535
|
|
|
432
|
-
function writeInstallMarker(skillsDir) {
|
|
536
|
+
function writeInstallMarker(skillsDir, names, previous) {
|
|
433
537
|
const markerPath = path.join(skillsDir, DEVLYN_INSTALL_MARKER);
|
|
434
538
|
const tempPath = `${markerPath}.${process.pid}.tmp`;
|
|
435
539
|
const marker = {
|
|
436
540
|
schemaVersion: 1,
|
|
437
541
|
package: PKG.name,
|
|
438
542
|
version: PKG.version,
|
|
543
|
+
skills: { ...previous, ...Object.fromEntries(names.map((name) => [name, fingerprint(path.join(skillsDir, name))])) },
|
|
439
544
|
};
|
|
440
545
|
try {
|
|
441
546
|
fs.writeFileSync(tempPath, JSON.stringify(marker, null, 2) + '\n', {
|
|
@@ -449,141 +554,65 @@ function writeInstallMarker(skillsDir) {
|
|
|
449
554
|
}
|
|
450
555
|
}
|
|
451
556
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
if (key === '\u0003') {
|
|
489
|
-
process.stdin.setRawMode(false);
|
|
490
|
-
process.stdin.removeListener('data', onKeypress);
|
|
491
|
-
process.exit();
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// Enter
|
|
495
|
-
if (key === '\r' || key === '\n') {
|
|
496
|
-
process.stdin.setRawMode(false);
|
|
497
|
-
process.stdin.removeListener('data', onKeypress);
|
|
498
|
-
process.stdin.pause();
|
|
499
|
-
console.log('');
|
|
500
|
-
resolve([...selected].map((i) => items[i]));
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// Space - toggle selection
|
|
505
|
-
if (key === ' ') {
|
|
506
|
-
if (selected.has(cursor)) {
|
|
507
|
-
selected.delete(cursor);
|
|
508
|
-
} else {
|
|
509
|
-
selected.add(cursor);
|
|
510
|
-
}
|
|
511
|
-
render();
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// Arrow up or k
|
|
516
|
-
if (key === '\x1b[A' || key === 'k') {
|
|
517
|
-
cursor = cursor > 0 ? cursor - 1 : items.length - 1;
|
|
518
|
-
render();
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Arrow down or j
|
|
523
|
-
if (key === '\x1b[B' || key === 'j') {
|
|
524
|
-
cursor = cursor < items.length - 1 ? cursor + 1 : 0;
|
|
525
|
-
render();
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// 'a' - select all
|
|
530
|
-
if (key === 'a') {
|
|
531
|
-
if (selected.size === items.length) {
|
|
532
|
-
selected.clear();
|
|
533
|
-
} else {
|
|
534
|
-
items.forEach((_, i) => selected.add(i));
|
|
535
|
-
}
|
|
536
|
-
render();
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
process.stdin.on('data', onKeypress);
|
|
557
|
+
// One Inquirer menu, imported only when shown (Inquirer is ESM). The page gets the terminal
|
|
558
|
+
// rows left after everything else the menu draws, so a redraw never scrolls it into
|
|
559
|
+
// duplicates. Ctrl+C cancels the install.
|
|
560
|
+
async function ask(module, help, config) {
|
|
561
|
+
const { default: prompt } = await import(module);
|
|
562
|
+
const { stdin: input, stdout: output } = process;
|
|
563
|
+
const columns = output.columns || 80;
|
|
564
|
+
// Most rows a text wraps to: an ASCII character takes one cell, any other at most two.
|
|
565
|
+
const rows = (text = '') => Math.ceil([...text].reduce((cells, c) => cells + (c.codePointAt(0) < 0x80 ? 1 : 2), 0) / columns);
|
|
566
|
+
// Message, blank line, the active choice's description, key help, and the line Inquirer
|
|
567
|
+
// adds when the help exactly fills the width.
|
|
568
|
+
const reserved = rows(`? ${config.message}`) + 1 + Math.max(...config.choices.map((choice) => rows(choice.description))) + rows(help) + 1;
|
|
569
|
+
try {
|
|
570
|
+
return await prompt({
|
|
571
|
+
...config,
|
|
572
|
+
pageSize: Math.max(1, (output.rows || 24) - reserved),
|
|
573
|
+
theme: { keybindings: ['vim'], style: { keysHelpTip: () => help } },
|
|
574
|
+
}, { input, output });
|
|
575
|
+
} catch (error) {
|
|
576
|
+
if (error.name !== 'ExitPromptError') throw error;
|
|
577
|
+
console.log('Installation cancelled.');
|
|
578
|
+
process.exit(0);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Checkbox menu (space toggles, a toggles all); resolves to the chosen items.
|
|
583
|
+
function multiSelect(message, items, preselectedIndices = []) {
|
|
584
|
+
return ask('@inquirer/checkbox', '↑↓ navigate · space select · a all · enter confirm', {
|
|
585
|
+
message,
|
|
586
|
+
choices: items.map((item, index) => ({
|
|
587
|
+
name: item.type ? `${item.name} [${item.type === 'mcp' ? 'mcp' : item.type === 'local' ? 'skill' : 'pack'}]` : item.name,
|
|
588
|
+
short: item.name,
|
|
589
|
+
description: item.desc,
|
|
590
|
+
value: item,
|
|
591
|
+
checked: preselectedIndices.includes(index),
|
|
592
|
+
})),
|
|
542
593
|
});
|
|
543
594
|
}
|
|
544
595
|
|
|
545
|
-
// One of `items
|
|
546
|
-
function singleSelect(items, initial) {
|
|
547
|
-
return
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if (drawn) process.stdout.write(`\x1b[${items.length + 2}A\x1b[0J`);
|
|
552
|
-
drawn = true;
|
|
553
|
-
console.log(`${COLORS.dim}(↑↓ navigate, enter confirm)${COLORS.reset}\n`);
|
|
554
|
-
items.forEach((item, i) => console.log(i === cursor ? `${COLORS.cyan}❯ ${item}${COLORS.reset}` : ` ${item}`));
|
|
555
|
-
};
|
|
556
|
-
render();
|
|
557
|
-
process.stdin.setRawMode(true);
|
|
558
|
-
process.stdin.resume();
|
|
559
|
-
process.stdin.setEncoding('utf8');
|
|
560
|
-
const onKeypress = (key) => {
|
|
561
|
-
if (key === '\u0003') {
|
|
562
|
-
process.stdin.setRawMode(false);
|
|
563
|
-
process.exit();
|
|
564
|
-
}
|
|
565
|
-
if (key === '\r' || key === '\n') {
|
|
566
|
-
process.stdin.setRawMode(false);
|
|
567
|
-
process.stdin.removeListener('data', onKeypress);
|
|
568
|
-
process.stdin.pause();
|
|
569
|
-
console.log('');
|
|
570
|
-
resolve(cursor);
|
|
571
|
-
} else if (['\x1b[A', 'k', '\x1b[B', 'j'].includes(key)) {
|
|
572
|
-
cursor = (cursor + (key === '\x1b[A' || key === 'k' ? items.length - 1 : 1)) % items.length;
|
|
573
|
-
render();
|
|
574
|
-
}
|
|
575
|
-
};
|
|
576
|
-
process.stdin.on('data', onKeypress);
|
|
596
|
+
// One of `items`; resolves to the chosen index.
|
|
597
|
+
function singleSelect(message, items, initial) {
|
|
598
|
+
return ask('@inquirer/select', '↑↓ navigate · enter confirm', {
|
|
599
|
+
message,
|
|
600
|
+
choices: items.map((name, index) => ({ name, value: index })),
|
|
601
|
+
default: initial,
|
|
577
602
|
});
|
|
578
603
|
}
|
|
579
604
|
|
|
605
|
+
// After install(), whose preflight already cleared every selected addon before any change.
|
|
580
606
|
function installLocalSkill(skillName, roots) {
|
|
581
607
|
if (!fs.existsSync(path.join(OPTIONAL_SKILLS_SOURCE, skillName))) {
|
|
582
608
|
log(` ⚠️ Skill "${skillName}" not found`, 'yellow');
|
|
583
609
|
return false;
|
|
584
610
|
}
|
|
585
611
|
log(`\n🛠️ Installing ${skillName}...`, 'cyan');
|
|
586
|
-
for (const root of roots)
|
|
612
|
+
for (const root of roots) {
|
|
613
|
+
installOptionalSkillInto(root, skillName);
|
|
614
|
+
writeInstallMarker(root, [skillName], readInstallManifest(root));
|
|
615
|
+
}
|
|
587
616
|
return true;
|
|
588
617
|
}
|
|
589
618
|
|
|
@@ -653,6 +682,7 @@ function installCoreSkills(skillsDir) {
|
|
|
653
682
|
const sourceSkillsDir = path.join(CONFIG_SOURCE, 'skills');
|
|
654
683
|
log(`\n📁 Installing devlyn skills to ${skillsDir.replace(os.homedir(), '~')}`, 'green');
|
|
655
684
|
fs.mkdirSync(skillsDir, { recursive: true });
|
|
685
|
+
const previous = readInstallManifest(skillsDir);
|
|
656
686
|
clearInstallMarker(skillsDir);
|
|
657
687
|
const removed = cleanupDeprecated(skillsDir);
|
|
658
688
|
if (removed > 0) {
|
|
@@ -667,8 +697,8 @@ function installCoreSkills(skillsDir) {
|
|
|
667
697
|
copyRecursive(src, dest, skillsDir);
|
|
668
698
|
}
|
|
669
699
|
assertCompleteSkillInstall(sourceSkillsDir, skillsDir, DEVLYN_CORE_SKILLS);
|
|
670
|
-
retireRenamedSkills(skillsDir);
|
|
671
|
-
writeInstallMarker(skillsDir);
|
|
700
|
+
const refreshed = retireRenamedSkills(skillsDir);
|
|
701
|
+
writeInstallMarker(skillsDir, [...DEVLYN_CORE_SKILLS, ...refreshed], previous);
|
|
672
702
|
}
|
|
673
703
|
|
|
674
704
|
// Keep installer-managed pipeline state and install metadata out of git.
|
|
@@ -820,7 +850,7 @@ function installClaudeCore() {
|
|
|
820
850
|
}
|
|
821
851
|
|
|
822
852
|
// Installs the targets in one scope; returns the skill roots written.
|
|
823
|
-
function install(targets, global) {
|
|
853
|
+
function install(targets, global, optionalNames = []) {
|
|
824
854
|
// In the home folder CLAUDE.md, AGENTS.md (agents read a parent folder's too) and
|
|
825
855
|
// .claude/settings.json would apply to every project. Same folder by identity, so a link or
|
|
826
856
|
// another spelling of the path is caught too.
|
|
@@ -831,6 +861,8 @@ function install(targets, global) {
|
|
|
831
861
|
+ 'would apply to every project. Run from a project folder, or use --global for skills only.');
|
|
832
862
|
}
|
|
833
863
|
const roots = targets.flatMap((target) => skillRoots(target, global));
|
|
864
|
+
preflightSkills(roots, optionalNames,
|
|
865
|
+
!global && targets.includes('claude') ? path.dirname(skillRoots('claude', false)[0]) : null);
|
|
834
866
|
// AGENTS.md first, whatever order the menu gave: a new CLAUDE.md holds the block only when AGENTS.md does not.
|
|
835
867
|
for (const target of ['agents', 'claude'].filter((name) => targets.includes(name))) {
|
|
836
868
|
if (global) skillRoots(target, true).forEach((root) => installCoreSkills(root));
|
|
@@ -885,13 +917,12 @@ async function init({ yes, claude, global }) {
|
|
|
885
917
|
return;
|
|
886
918
|
}
|
|
887
919
|
|
|
888
|
-
log('\n🎯 What to install:\n', 'blue');
|
|
889
920
|
const targetOptions = [
|
|
890
921
|
{ key: 'agents', name: 'AGENTS.md — Codex · omp · Pi · Grok', desc: 'AGENTS.md + .agents/skills' },
|
|
891
922
|
{ key: 'claude', name: 'CLAUDE.md — Claude Code', desc: 'CLAUDE.md + .claude/ (skills, settings)' },
|
|
892
923
|
];
|
|
893
924
|
const preselected = claude || hasDevlynClaude(false) || (global && hasDevlynClaude(true)) ? [0, 1] : [0];
|
|
894
|
-
const targets = (await multiSelect(targetOptions, preselected)).map((option) => option.key);
|
|
925
|
+
const targets = (await multiSelect('What to install', targetOptions, preselected)).map((option) => option.key);
|
|
895
926
|
|
|
896
927
|
if (targets.length === 0) {
|
|
897
928
|
log('\n💡 Nothing selected — nothing installed.', 'yellow');
|
|
@@ -899,14 +930,10 @@ async function init({ yes, claude, global }) {
|
|
|
899
930
|
return;
|
|
900
931
|
}
|
|
901
932
|
|
|
902
|
-
|
|
903
|
-
const scope = await singleSelect(['This project', 'Global — every project on this machine'], global ? 1 : 0);
|
|
904
|
-
const roots = install(targets, scope === 1);
|
|
905
|
-
|
|
933
|
+
const scope = await singleSelect('Where', ['This project', 'Global — every project on this machine'], global ? 1 : 0);
|
|
906
934
|
// Ask about optional addons (local skills + external packs; MCP servers belong to Claude Code)
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
const selectedAddons = await multiSelect(OPTIONAL_ADDONS.filter((addon) => addon.type !== 'mcp' || targets.includes('claude')));
|
|
935
|
+
const selectedAddons = await multiSelect('Optional skills & packs', OPTIONAL_ADDONS.filter((addon) => addon.type !== 'mcp' || targets.includes('claude')));
|
|
936
|
+
const roots = install(targets, scope === 1, selectedAddons.filter((addon) => addon.type === 'local').map((addon) => addon.name));
|
|
910
937
|
|
|
911
938
|
if (selectedAddons.length > 0) {
|
|
912
939
|
for (const addon of selectedAddons) {
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": "devlyn-cli@4.1.0 (npm tarball sha1 398ff07d3d689b56cc22e18ac50260bc6e4e9e85); devlyn-cli@4.2.0 (npm tarball sha1 c93c2fb1cf5630b18d2eba0085182c14fddbe2d5); whole-tree fingerprints from bin/skill-ownership.js; deprecated commands: commands/ and optional-commands/*/ files of devlyn-cli 0.0.1-0.5.8 (every published version that shipped them), LF-normalized SHA-256",
|
|
3
|
+
"skills": {
|
|
4
|
+
"_shared": [
|
|
5
|
+
"eb3327d5e6940bd5e0459fa28c9ab323d9c58ccfdeb9426c19cd027cc4e5c4ed",
|
|
6
|
+
"8c80926ab03b2477e52264495b35c2116f65fc51b02e8c56e441b5705872392a"
|
|
7
|
+
],
|
|
8
|
+
"devlyn-design-ui": [
|
|
9
|
+
"91080266232fb62b6f1d45932184f065589e12eb66f02fb6442527c6dfcd81b6"
|
|
10
|
+
],
|
|
11
|
+
"devlyn-engines": [
|
|
12
|
+
"3451d37f47e6e707cff92baa12d04b43aee94301456b8e280c09332e598a1c2c",
|
|
13
|
+
"89f0efaa2efdfddc887b494138247980d97856f6cabe6a8dcc585b3a28e5fa88"
|
|
14
|
+
],
|
|
15
|
+
"devlyn-ideate": [
|
|
16
|
+
"8ff5148cc65b4608c35b29afd6fef20d42d8da93a094144d98cfc63e74089b6e",
|
|
17
|
+
"5824f07e455fe1c3942bf595a12e935258d4c0a851ca1bf741db915603d1c4e0"
|
|
18
|
+
],
|
|
19
|
+
"devlyn-queue": [
|
|
20
|
+
"4c7d988dcd154c7bf658d2d24cd7868955df9d854ed87eb784742d6d4bb229e0"
|
|
21
|
+
],
|
|
22
|
+
"devlyn-resolve": [
|
|
23
|
+
"2abd303851f085da07f6b55ee1724842ef7f8e8ad6b68c6339df5f1eaa8c69de"
|
|
24
|
+
],
|
|
25
|
+
"asset-creator": [
|
|
26
|
+
"e295f57eca534cbd4e27e236999f4f5412ebdc6944ac864761e922dea213f904"
|
|
27
|
+
],
|
|
28
|
+
"better-auth-setup": [
|
|
29
|
+
"e97c39c5d669ffc1f49d57fd3a016e852727e57c3abe2f9be1ebffab9fcb19ec"
|
|
30
|
+
],
|
|
31
|
+
"cloudflare-nextjs-setup": [
|
|
32
|
+
"f147a28b9f6f62feb21df89bdf08869820f4871ede7a8ba4a242246509ee6e35"
|
|
33
|
+
],
|
|
34
|
+
"code-health-standards": [
|
|
35
|
+
"2056617b324bb7e1e3b85a6536bfe33151e31ef421656cade74258fb4e922c1b"
|
|
36
|
+
],
|
|
37
|
+
"code-review-standards": [
|
|
38
|
+
"3fe0742825aff05fa6fda49d3e3e0fbf5457307af944eb90f2771cbf611edd69"
|
|
39
|
+
],
|
|
40
|
+
"devlyn-pencil-pull": [
|
|
41
|
+
"65af00f88b0837ffd377ebf363da73c766ae0ae8e20716fe18f80edfa91bd6da"
|
|
42
|
+
],
|
|
43
|
+
"devlyn-pencil-push": [
|
|
44
|
+
"76955458ced72f4b0bdaf71482be6097c4c86876934b6e6a2ac7d19f850662ad"
|
|
45
|
+
],
|
|
46
|
+
"devlyn-reap": [
|
|
47
|
+
"77e029ab06c37fb7135a9bd4395ff84b84ab2e44cf79c5827798777e39472351"
|
|
48
|
+
],
|
|
49
|
+
"dokkit": [
|
|
50
|
+
"42045db4e2f70f68d0ed90a9c7c5c2660a6d5183ea641dfa9f2f1f0d0938e3fb"
|
|
51
|
+
],
|
|
52
|
+
"generate-skill": [
|
|
53
|
+
"16a78b867539c0bc11a44f3ecfa1762ba7fcba8841fca8558abe818fc961ff68"
|
|
54
|
+
],
|
|
55
|
+
"polar-billing-setup": [
|
|
56
|
+
"8b6dc842903e7343ac7b1e3a1c55911a9987552196ffb27a11a86093537fe976"
|
|
57
|
+
],
|
|
58
|
+
"prompt-engineering": [
|
|
59
|
+
"d7a0718ab0fd7e0a3c0d8ef8bc4e0349edfc843648343f08f9776e1ea047573c"
|
|
60
|
+
],
|
|
61
|
+
"pyx-scan": [
|
|
62
|
+
"60f02fa049616e94fff4318c96dc9f417b3def7ec75a3bd0846f2bcbd99b0b69"
|
|
63
|
+
],
|
|
64
|
+
"root-cause-analysis": [
|
|
65
|
+
"eeb20fe09edd37b07360c3440b68bdb488abee4917d4e808e64e3889c6addf5a"
|
|
66
|
+
],
|
|
67
|
+
"ui-implementation-standards": [
|
|
68
|
+
"0fbaad2b7d77462916d9364b97b921354ffbf945a68e9e0d422b3a88d36d2569"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
"commands": {
|
|
72
|
+
"commands/devlyn.handoff.md": [
|
|
73
|
+
"91d143c6305e95e4af6992eda0d127e94a394c2e91a2d409b4d41ca03c23f470"
|
|
74
|
+
],
|
|
75
|
+
"commands/devlyn.clean.md": [
|
|
76
|
+
"918c6ae867c57a4d9748237adae0df8c78a1447c7c028a0af988b59a5fbb0d9a"
|
|
77
|
+
],
|
|
78
|
+
"commands/devlyn.design-system.md": [
|
|
79
|
+
"f81166b5ba08d9ee1f5cff33c911ea387e5cb50141a9712a96fc4434c60a673f"
|
|
80
|
+
],
|
|
81
|
+
"commands/devlyn.design-ui.md": [
|
|
82
|
+
"0496a6b2dd8c6bb0474b71ef9a319a6b95bb76972d0f07a01c33f8860fe83dfb",
|
|
83
|
+
"4e2aafcc0de6ce7f3022a353aaa9fb4330e76a50a91ab970b9b9a0f192f9f1f2"
|
|
84
|
+
],
|
|
85
|
+
"commands/devlyn.discover-product.md": [
|
|
86
|
+
"a609401d29cd2bdad7a48c7c8f096b442fe03a17c6b2c3151936c47b4a16e6c6",
|
|
87
|
+
"1e66aa35bf300ec6167e1b888f43a92ebd9e60d501397ff0334c9a7809d433f5"
|
|
88
|
+
],
|
|
89
|
+
"commands/devlyn.evaluate.md": [
|
|
90
|
+
"8f17ccc639d54adab24c49dcf471376bab97511675d00554056a4c1f67a14115"
|
|
91
|
+
],
|
|
92
|
+
"commands/devlyn.feature-spec.md": [
|
|
93
|
+
"d442ed7ae8d14a255c37f1b120b758f69794be88e2970a8a0c0e51519344b6c7"
|
|
94
|
+
],
|
|
95
|
+
"commands/devlyn.implement-ui.md": [
|
|
96
|
+
"a8d0813781b2f7a3df5a4751c92755bf7c84c53428cf63cea34c965939b8c478"
|
|
97
|
+
],
|
|
98
|
+
"commands/devlyn.product-spec.md": [
|
|
99
|
+
"7a92d2df2586b44e2c4d9182eeb3d61f7adc2a13bed4d2cd26abcfe086585895"
|
|
100
|
+
],
|
|
101
|
+
"commands/devlyn.recommend-features.md": [
|
|
102
|
+
"2b92523017e1da10c299dafb19d84736de5084b5b46693de7fd2d6df51dc56b4"
|
|
103
|
+
],
|
|
104
|
+
"commands/devlyn.resolve.md": [
|
|
105
|
+
"48c0c3ee553f87f1f0733190effc7220c01c82837a5300ef68c9ffdf30c5d3a1",
|
|
106
|
+
"24595766e1a81a1fcacb78261b03c3665bc686bcac8b1805d1c4db379584879f",
|
|
107
|
+
"eaad5354cfc20ed378ffd2a2e1ca6bade1310bd2a47ab93e5942e9599c3ba9a8"
|
|
108
|
+
],
|
|
109
|
+
"commands/devlyn.review.md": [
|
|
110
|
+
"55888e08bf31ead1d359f218277cfa7efc3c6cb0f96fadc665d5a2406ff115d3",
|
|
111
|
+
"22f532f587f6d2fbf2662e38d5073b85a030141573f86d92ad40b9d292836fb7",
|
|
112
|
+
"7f9ce1db4ffd3cdaf9fd949e4b07de04e0931c91cd846d4ab220ff6114a45b17",
|
|
113
|
+
"3a511fc678e599ff4b4c37fd9191399a599e1ba9ea9c40fc4afcbe29cb188d2b"
|
|
114
|
+
],
|
|
115
|
+
"commands/devlyn.team-design-ui.md": [
|
|
116
|
+
"02485911588e9f52afb5a5082fd7804ee7a41bad44323960a2afe1a16a49d628"
|
|
117
|
+
],
|
|
118
|
+
"commands/devlyn.team-resolve.md": [
|
|
119
|
+
"8f11fa0c3203c0dd5bf09b5243dcff5968a36275682c54b3a012262fc545859a",
|
|
120
|
+
"09fc163d34dfe8906dd8d22501a028bfbbad639499acab01e5e60f06fc520382",
|
|
121
|
+
"ccfc5083813ee50d407109616ccbc2509aa7526316cebbf7a271537abdfff3b7",
|
|
122
|
+
"72f66be3c4c27a46e76d56d13a0d381880c054a03243188f98da2faf6dc77295",
|
|
123
|
+
"54c52c3f27998998ed55cc648d071bbb10444761b819c59ed118b795596a4aee"
|
|
124
|
+
],
|
|
125
|
+
"commands/devlyn.team-review.md": [
|
|
126
|
+
"58b36cfe2749968d697d486a104a1b139e4312730b0ac2933550634f6ece48db",
|
|
127
|
+
"7e5c2d93a23d51af7939dae29c70095dbd8a4a681cc06d4d578f2de29df346a3",
|
|
128
|
+
"c8e145955a303141af4082dae174e3b3f1681e4c9698e25ae337eadd31fb883b"
|
|
129
|
+
],
|
|
130
|
+
"commands/devlyn.update-docs.md": [
|
|
131
|
+
"86945afccf24762a88d26eef3703fbf6c5a90a3343fd3de72a9cb26b6f62d3b5"
|
|
132
|
+
],
|
|
133
|
+
"commands/devlyn.pencil-pull.md": [
|
|
134
|
+
"dfdd3d19ca676558bfa2bb3ce398181b0a081ab88d1feaf69c54c706a9671631"
|
|
135
|
+
],
|
|
136
|
+
"commands/devlyn.pencil-push.md": [
|
|
137
|
+
"5f2e8b29609cbb2af72941579823bec598265ebf29cd3a375d6aa47d95b74354"
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
}
|