devlyn-cli 2.5.7 → 2.6.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
CHANGED
|
@@ -27,7 +27,7 @@ If devlyn-cli saved you time, [give it a star](https://github.com/fysoul17/devly
|
|
|
27
27
|
npx devlyn-cli
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
That's it. The
|
|
30
|
+
That's it. The installer opens with a single **agent selector** — pick any combination of **Claude Code**, **Codex CLI**, **oh-my-pi (omp)**, **Pi**, Gemini, Cursor, Copilot, or Windsurf, and devlyn installs into every one you choose in a single pass. Claude Code and any agent already present on your machine are pre-checked, so the common case is just Enter. Skill-capable agents receive the `devlyn:resolve`, `devlyn:ideate`, and `devlyn:design-ui` skills in the directory each one loads from: Codex → `~/.codex/skills/`, while **omp and Pi share `~/.agents/skills/`** — the cross-agent standard both read — so the bundle is written there once, not duplicated per agent. In Codex / omp / Pi, invoke them as skills (`$devlyn:resolve`, `$devlyn:ideate`, `$devlyn:design-ui`); in Claude Code they're slash commands (`/devlyn:resolve`). Run it again anytime to update. (`npx devlyn-cli -y` installs the Claude core non-interactively; `npx devlyn-cli agents <cli>` adds one agent later.)
|
|
31
31
|
|
|
32
32
|
---
|
|
33
33
|
|
|
@@ -39,7 +39,7 @@ devlyn-cli turns Claude Code into a hands-free development pipeline. The pipelin
|
|
|
39
39
|
ideate (optional) → resolve → ship
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Codex
|
|
42
|
+
Non-Claude agents (Codex / omp / Pi): when one of these is selected, the workflows install as that agent's skills. Use `$devlyn:ideate`, `$devlyn:resolve`, or `$devlyn:design-ui`; the `/devlyn:*` slash-command form is for Claude Code.
|
|
43
43
|
|
|
44
44
|
### Step 1 (optional) — Plan with `/devlyn:ideate`
|
|
45
45
|
|
package/bin/devlyn.js
CHANGED
|
@@ -11,6 +11,19 @@ const AGENTS_SOURCE = path.join(__dirname, '..', 'agents-config');
|
|
|
11
11
|
const OPTIONAL_SKILLS_SOURCE = path.join(__dirname, '..', 'optional-skills');
|
|
12
12
|
const PKG = require('../package.json');
|
|
13
13
|
|
|
14
|
+
// The devlyn skill bundle installed into every skill-capable agent's loader
|
|
15
|
+
// directory. Single source of truth so codex/omp/pi stay in lockstep — adding a
|
|
16
|
+
// skill here installs it everywhere. Standards skills (code-*, root-cause-*,
|
|
17
|
+
// ui-*) stay Claude-core-only, matching the pre-existing Codex bundle.
|
|
18
|
+
const DEVLYN_CORE_SKILLS = ['devlyn:resolve', 'devlyn:ideate', 'devlyn:design-ui', '_shared'];
|
|
19
|
+
|
|
20
|
+
// Cross-agent shared skills directory read by BOTH oh-my-pi and Pi. Verified
|
|
21
|
+
// from the omp binary's skill-provider strings ("skills from .agents/skills —
|
|
22
|
+
// project walk-up + user home") and Pi's docs ("~/.agents/skills/"). Installing
|
|
23
|
+
// the bundle here once covers both agents — the de-dup driver writes it a single
|
|
24
|
+
// time even when both are selected.
|
|
25
|
+
const SHARED_AGENTS_SKILLS_DIR = path.join(os.homedir(), '.agents', 'skills');
|
|
26
|
+
|
|
14
27
|
// Cross-CLI agent installation targets
|
|
15
28
|
// Each entry maps a CLI tool to where its agent instructions should be placed
|
|
16
29
|
const CLI_TARGETS = {
|
|
@@ -22,9 +35,32 @@ const CLI_TARGETS = {
|
|
|
22
35
|
// Codex auto-loads skills from ~/.codex/skills/ (user-global). Same
|
|
23
36
|
// SKILL.md format as Claude Code; descriptions must stay ≤1024 chars.
|
|
24
37
|
skillsDir: path.join(os.homedir(), '.codex', 'skills'),
|
|
25
|
-
skillsToInstall:
|
|
38
|
+
skillsToInstall: DEVLYN_CORE_SKILLS,
|
|
26
39
|
detect: () => fs.existsSync(path.join(process.cwd(), 'AGENTS.md')) || fs.existsSync(path.join(process.cwd(), '.codex')),
|
|
27
40
|
},
|
|
41
|
+
omp: {
|
|
42
|
+
name: 'oh-my-pi (omp)',
|
|
43
|
+
instructionsFile: 'AGENTS.md',
|
|
44
|
+
baseInstructionsFile: 'AGENTS.md',
|
|
45
|
+
configDir: null, // omp discovers AGENTS.md at project root + user home
|
|
46
|
+
// omp loads skills from ~/.agents/skills (user home) — shared with Pi.
|
|
47
|
+
skillsDir: SHARED_AGENTS_SKILLS_DIR,
|
|
48
|
+
skillsToInstall: DEVLYN_CORE_SKILLS,
|
|
49
|
+
// Project-scoped only: machine-level ~/.omp must not auto-trigger a project
|
|
50
|
+
// AGENTS.md write via `npx devlyn-cli agents` in an unrelated repo.
|
|
51
|
+
detect: () => fs.existsSync(path.join(process.cwd(), '.omp')) || fs.existsSync(path.join(process.cwd(), '.agents')),
|
|
52
|
+
},
|
|
53
|
+
pi: {
|
|
54
|
+
name: 'Pi (earendil-works)',
|
|
55
|
+
instructionsFile: 'AGENTS.md',
|
|
56
|
+
baseInstructionsFile: 'AGENTS.md',
|
|
57
|
+
configDir: null, // Pi reads AGENTS.md / CLAUDE.md walking up from cwd
|
|
58
|
+
// Pi loads skills from ~/.agents/skills — shared with oh-my-pi.
|
|
59
|
+
skillsDir: SHARED_AGENTS_SKILLS_DIR,
|
|
60
|
+
skillsToInstall: DEVLYN_CORE_SKILLS,
|
|
61
|
+
// Project-scoped only (see omp): machine-level ~/.pi must not auto-trigger.
|
|
62
|
+
detect: () => fs.existsSync(path.join(process.cwd(), '.pi')) || fs.existsSync(path.join(process.cwd(), '.agents')),
|
|
63
|
+
},
|
|
28
64
|
gemini: {
|
|
29
65
|
name: 'Gemini CLI (Google)',
|
|
30
66
|
instructionsFile: 'GEMINI.md',
|
|
@@ -363,9 +399,9 @@ function cleanManagedSkillDirs(sourceSkillsDir, targetSkillsDir) {
|
|
|
363
399
|
return cleaned;
|
|
364
400
|
}
|
|
365
401
|
|
|
366
|
-
function multiSelect(items) {
|
|
402
|
+
function multiSelect(items, preselectedIndices = []) {
|
|
367
403
|
return new Promise((resolve) => {
|
|
368
|
-
const selected = new Set();
|
|
404
|
+
const selected = new Set(preselectedIndices.filter((i) => i >= 0 && i < items.length));
|
|
369
405
|
let cursor = 0;
|
|
370
406
|
let firstRender = true;
|
|
371
407
|
|
|
@@ -470,11 +506,15 @@ function installLocalSkill(skillName) {
|
|
|
470
506
|
copyRecursive(src, dest, targetDir);
|
|
471
507
|
|
|
472
508
|
// Mirror to every CLI skill-loader directory that already exists so optional
|
|
473
|
-
// skills are picked up by Codex (and any future CLI with a skillsDir)
|
|
474
|
-
// same way required skills are. Existing dir, not new dir — we don't
|
|
475
|
-
//
|
|
509
|
+
// skills are picked up by Codex/omp/Pi (and any future CLI with a skillsDir)
|
|
510
|
+
// the same way required skills are. Existing dir, not new dir — we don't
|
|
511
|
+
// create an agent install just because someone opted into a Claude-side skill.
|
|
512
|
+
// De-dup by directory: omp and Pi share ~/.agents/skills, so the mirror runs
|
|
513
|
+
// once per unique destination.
|
|
514
|
+
const mirrored = new Set();
|
|
476
515
|
for (const cli of Object.values(CLI_TARGETS)) {
|
|
477
|
-
if (!cli.skillsDir || !fs.existsSync(cli.skillsDir)) continue;
|
|
516
|
+
if (!cli.skillsDir || mirrored.has(cli.skillsDir) || !fs.existsSync(cli.skillsDir)) continue;
|
|
517
|
+
mirrored.add(cli.skillsDir);
|
|
478
518
|
const cliDest = path.join(cli.skillsDir, skillName);
|
|
479
519
|
if (fs.existsSync(cliDest)) fs.rmSync(cliDest, { recursive: true, force: true });
|
|
480
520
|
copyRecursive(src, cliDest, cli.skillsDir);
|
|
@@ -556,94 +596,136 @@ function installSkillsForCLI(cliKey) {
|
|
|
556
596
|
return copied;
|
|
557
597
|
}
|
|
558
598
|
|
|
559
|
-
|
|
599
|
+
// Strip devlyn's previously-appended "# Devlyn Agent Instructions" block from an
|
|
600
|
+
// instruction file (or the packaged base) before re-appending, so installs stay
|
|
601
|
+
// idempotent and never duplicate the block. Anchors on the LAST marker, not the
|
|
602
|
+
// first: the packaged AGENTS.md ships its own "# Devlyn Agent Instructions"
|
|
603
|
+
// section, so indexOf would truncate that real content — lastIndexOf targets the
|
|
604
|
+
// devlyn-appended block (always last) and preserves everything above it.
|
|
605
|
+
function stripManagedBlock(content) {
|
|
606
|
+
const marker = '# Devlyn Agent Instructions';
|
|
607
|
+
const markerIdx = content.lastIndexOf(marker);
|
|
608
|
+
if (markerIdx > 0) {
|
|
609
|
+
const sepIdx = content.lastIndexOf('\n---', markerIdx);
|
|
610
|
+
return content.slice(0, sepIdx > 0 ? sepIdx : markerIdx).trimEnd();
|
|
611
|
+
}
|
|
612
|
+
return content.trimEnd();
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// One-line description of exactly what selecting a CLI target installs, so the
|
|
616
|
+
// unified selector stays honest — "Gemini" (instructions only) must not look
|
|
617
|
+
// identical to "Codex" (instructions + skills).
|
|
618
|
+
function targetDesc(cli) {
|
|
619
|
+
if (cli.skillsDir) {
|
|
620
|
+
return `${cli.instructionsFile} + devlyn skills → ${cli.skillsDir.replace(os.homedir(), '~')}`;
|
|
621
|
+
}
|
|
622
|
+
if (cli.configDir) return `Agent instructions → ${cli.configDir}/`;
|
|
623
|
+
return `${cli.instructionsFile} instructions only`;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Install only the instruction file(s) for a CLI (no skills). Returns true when
|
|
627
|
+
// instruction content was written. Split from skills so the de-dup driver can
|
|
628
|
+
// write a shared destination (e.g. project AGENTS.md for codex/omp/pi) once.
|
|
629
|
+
function installInstructionsForCLI(cliKey) {
|
|
560
630
|
const cli = CLI_TARGETS[cliKey];
|
|
561
631
|
if (!cli) return false;
|
|
562
632
|
|
|
563
633
|
const agents = fs.existsSync(AGENTS_SOURCE)
|
|
564
634
|
? fs.readdirSync(AGENTS_SOURCE).filter((f) => f.endsWith('.md'))
|
|
565
635
|
: [];
|
|
636
|
+
if (agents.length === 0) return false;
|
|
566
637
|
|
|
567
|
-
|
|
568
|
-
log(`\n🤖 Installing agents for ${cli.name}...`, 'cyan');
|
|
638
|
+
log(`\n🤖 Installing agent instructions for ${cli.name}...`, 'cyan');
|
|
569
639
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
// Find the separator before the marker (---\n\n)
|
|
598
|
-
const sepIdx = existing.lastIndexOf('---', markerIdx);
|
|
599
|
-
existing = existing.slice(0, sepIdx > 0 ? sepIdx : markerIdx).trimEnd();
|
|
600
|
-
}
|
|
601
|
-
} else if (cli.baseInstructionsFile) {
|
|
602
|
-
const baseInstructionsSrc = path.join(__dirname, '..', cli.baseInstructionsFile);
|
|
603
|
-
if (fs.existsSync(baseInstructionsSrc)) {
|
|
604
|
-
existing = fs.readFileSync(baseInstructionsSrc, 'utf8').trimEnd();
|
|
605
|
-
}
|
|
640
|
+
if (cli.configDir) {
|
|
641
|
+
// CLI supports an agents directory — copy agent files there
|
|
642
|
+
const destDir = path.join(process.cwd(), cli.configDir);
|
|
643
|
+
if (!fs.existsSync(destDir)) {
|
|
644
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
645
|
+
}
|
|
646
|
+
for (const file of agents) {
|
|
647
|
+
const src = path.join(AGENTS_SOURCE, file);
|
|
648
|
+
const dest = path.join(destDir, file);
|
|
649
|
+
fs.copyFileSync(src, dest);
|
|
650
|
+
log(` → ${cli.configDir}/${file}`, 'dim');
|
|
651
|
+
}
|
|
652
|
+
} else {
|
|
653
|
+
// CLI uses a single instructions file — append agent content
|
|
654
|
+
const destFile = path.join(process.cwd(), cli.instructionsFile);
|
|
655
|
+
const separator = '\n\n---\n\n# Devlyn Agent Instructions\n\n';
|
|
656
|
+
const agentContent = agents.map((file) => {
|
|
657
|
+
return fs.readFileSync(path.join(AGENTS_SOURCE, file), 'utf8');
|
|
658
|
+
}).join('\n\n---\n\n');
|
|
659
|
+
|
|
660
|
+
let existing = '';
|
|
661
|
+
if (fs.existsSync(destFile)) {
|
|
662
|
+
existing = stripManagedBlock(fs.readFileSync(destFile, 'utf8'));
|
|
663
|
+
} else if (cli.baseInstructionsFile) {
|
|
664
|
+
const baseInstructionsSrc = path.join(__dirname, '..', cli.baseInstructionsFile);
|
|
665
|
+
if (fs.existsSync(baseInstructionsSrc)) {
|
|
666
|
+
existing = stripManagedBlock(fs.readFileSync(baseInstructionsSrc, 'utf8'));
|
|
606
667
|
}
|
|
607
|
-
|
|
608
|
-
fs.writeFileSync(destFile, existing + separator + agentContent + '\n');
|
|
609
|
-
log(` → ${cli.instructionsFile} (agent instructions appended)`, 'dim');
|
|
610
668
|
}
|
|
669
|
+
|
|
670
|
+
fs.writeFileSync(destFile, existing + separator + agentContent + '\n');
|
|
671
|
+
log(` → ${cli.instructionsFile} (agent instructions appended)`, 'dim');
|
|
611
672
|
}
|
|
673
|
+
return true;
|
|
674
|
+
}
|
|
612
675
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
676
|
+
// Install both instructions and skills for a single CLI. Used by the
|
|
677
|
+
// `agents <cli>` command; multi-target paths use installSelectedCLITargets.
|
|
678
|
+
function installAgentsForCLI(cliKey) {
|
|
679
|
+
const instr = installInstructionsForCLI(cliKey);
|
|
616
680
|
const skillsCopied = installSkillsForCLI(cliKey);
|
|
617
681
|
if (skillsCopied > 0) {
|
|
618
682
|
log(` → ${skillsCopied} skill${skillsCopied > 1 ? 's' : ''} installed (devlyn:resolve / devlyn:ideate / devlyn:design-ui / _shared)`, 'dim');
|
|
619
683
|
}
|
|
684
|
+
return instr || skillsCopied > 0;
|
|
685
|
+
}
|
|
620
686
|
|
|
621
|
-
|
|
687
|
+
// Install instructions + skills for a set of selected CLI targets, writing each
|
|
688
|
+
// unique destination exactly once. omp and Pi share project AGENTS.md and the
|
|
689
|
+
// ~/.agents/skills dir, so selecting both does not duplicate writes — this is
|
|
690
|
+
// the researched "group the common" model: targets are the user's selection,
|
|
691
|
+
// destinations are the unit of work.
|
|
692
|
+
function installSelectedCLITargets(cliKeys) {
|
|
693
|
+
const instrDone = new Set();
|
|
694
|
+
const skillsDone = new Set();
|
|
695
|
+
let count = 0;
|
|
696
|
+
for (const cliKey of cliKeys) {
|
|
697
|
+
const cli = CLI_TARGETS[cliKey];
|
|
698
|
+
if (!cli) continue;
|
|
699
|
+
const instrKey = cli.configDir ? `dir:${cli.configDir}` : `file:${cli.instructionsFile}`;
|
|
700
|
+
if (!instrDone.has(instrKey)) {
|
|
701
|
+
installInstructionsForCLI(cliKey);
|
|
702
|
+
instrDone.add(instrKey);
|
|
703
|
+
}
|
|
704
|
+
if (cli.skillsDir && !skillsDone.has(cli.skillsDir)) {
|
|
705
|
+
const copied = installSkillsForCLI(cliKey);
|
|
706
|
+
if (copied > 0) {
|
|
707
|
+
log(` → ${copied} skill${copied > 1 ? 's' : ''} → ${cli.skillsDir.replace(os.homedir(), '~')}`, 'dim');
|
|
708
|
+
}
|
|
709
|
+
skillsDone.add(cli.skillsDir);
|
|
710
|
+
}
|
|
711
|
+
count++;
|
|
712
|
+
}
|
|
713
|
+
return count;
|
|
622
714
|
}
|
|
623
715
|
|
|
624
716
|
function installAgentsForAllDetected() {
|
|
625
717
|
const detected = detectOtherCLIs();
|
|
626
718
|
if (detected.length === 0) return 0;
|
|
627
|
-
|
|
628
|
-
let installed = 0;
|
|
629
|
-
for (const cliKey of detected) {
|
|
630
|
-
if (installAgentsForCLI(cliKey)) installed++;
|
|
631
|
-
}
|
|
632
|
-
return installed;
|
|
719
|
+
return installSelectedCLITargets(detected);
|
|
633
720
|
}
|
|
634
721
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
log('❌ Config source not found', 'yellow');
|
|
641
|
-
process.exit(1);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// Install core config
|
|
722
|
+
// Install the Claude Code core config: project .claude/ (skills, templates,
|
|
723
|
+
// settings, CLAUDE.md, .gitignore) plus global ~/.claude/settings.json tweaks.
|
|
724
|
+
// Extracted so the unified target selector installs it only when "Claude Code"
|
|
725
|
+
// is chosen. Behavior is unchanged from the previous unconditional core install.
|
|
726
|
+
function installClaudeCore() {
|
|
645
727
|
const targetDir = getTargetDir();
|
|
646
|
-
log('\n📁 Installing
|
|
728
|
+
log('\n📁 Installing Claude Code config to .claude/', 'green');
|
|
647
729
|
const refreshed = cleanManagedSkillDirs(
|
|
648
730
|
path.join(CONFIG_SOURCE, 'skills'),
|
|
649
731
|
path.join(targetDir, 'skills'),
|
|
@@ -747,40 +829,55 @@ async function init(skipPrompts = false) {
|
|
|
747
829
|
log(' → ~/.claude/settings.json (disabled adaptive thinking, enabled 1h prompt caching)', 'dim');
|
|
748
830
|
}
|
|
749
831
|
|
|
750
|
-
log('\n✅
|
|
832
|
+
log('\n✅ Claude Code config installed!', 'green');
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
async function init(skipPrompts = false) {
|
|
836
|
+
showLogo();
|
|
837
|
+
log('─'.repeat(44), 'dim');
|
|
751
838
|
|
|
752
|
-
|
|
839
|
+
if (!fs.existsSync(CONFIG_SOURCE)) {
|
|
840
|
+
log('❌ Config source not found', 'yellow');
|
|
841
|
+
process.exit(1);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Non-interactive / -y: preserve historical behavior — install Claude core
|
|
845
|
+
// only. Multi-target selection needs a TTY; scripted runs add other agents
|
|
846
|
+
// via `npx devlyn-cli agents <cli>`.
|
|
753
847
|
if (skipPrompts || !process.stdin.isTTY) {
|
|
848
|
+
installClaudeCore();
|
|
754
849
|
log('\n💡 Add optional addons later: run `npx devlyn-cli` without -y', 'dim');
|
|
755
|
-
log(' Add Codex
|
|
850
|
+
log(' Add another agent (Codex / omp / Pi / …) later: `npx devlyn-cli agents <cli>`', 'dim');
|
|
756
851
|
log(`\n${COLORS.dim} Enjoying devlyn? Star it on GitHub — it helps others find it:${COLORS.reset}`);
|
|
757
852
|
log(` ${COLORS.purple}→ https://github.com/fysoul17/devlyn-cli${COLORS.reset}\n`);
|
|
758
853
|
return;
|
|
759
854
|
}
|
|
760
855
|
|
|
761
|
-
//
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
|
|
856
|
+
// Pick every agent to install devlyn into, up front. Claude Code is
|
|
857
|
+
// pre-checked (the happy path); every other agent is an explicit opt-in so a
|
|
858
|
+
// bare Enter never mutates a project's AGENTS.md or a shared skills dir for an
|
|
859
|
+
// agent the user didn't choose.
|
|
860
|
+
log('\n🎯 Select the agents to install devlyn into:\n', 'blue');
|
|
861
|
+
const targetOptions = [
|
|
862
|
+
{ key: 'claude', name: 'Claude Code', desc: '.claude/ config — skills, templates, settings, CLAUDE.md', type: 'cli' },
|
|
863
|
+
...Object.entries(CLI_TARGETS).map(([key, cli]) => ({ key, name: cli.name, desc: targetDesc(cli), type: 'cli' })),
|
|
864
|
+
];
|
|
865
|
+
const preselected = [targetOptions.findIndex((o) => o.key === 'claude')];
|
|
866
|
+
const selectedKeys = (await multiSelect(targetOptions, preselected)).map((t) => t.key);
|
|
867
|
+
|
|
868
|
+
if (selectedKeys.length === 0) {
|
|
869
|
+
log('\n💡 No agents selected — nothing installed.', 'yellow');
|
|
870
|
+
log(' Run `npx devlyn-cli` again and pick at least one agent.\n', 'dim');
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (selectedKeys.includes('claude')) {
|
|
875
|
+
installClaudeCore();
|
|
876
|
+
}
|
|
877
|
+
const cliKeys = selectedKeys.filter((k) => k !== 'claude');
|
|
878
|
+
if (cliKeys.length > 0) {
|
|
879
|
+
const installed = installSelectedCLITargets(cliKeys);
|
|
880
|
+
log(`\n ✅ Installed devlyn into ${installed} agent${installed !== 1 ? 's' : ''}`, 'green');
|
|
784
881
|
}
|
|
785
882
|
|
|
786
883
|
// Ask about optional addons (local skills + external packs)
|
|
@@ -806,9 +903,9 @@ async function init(skipPrompts = false) {
|
|
|
806
903
|
function showHelp() {
|
|
807
904
|
showLogo();
|
|
808
905
|
log('Usage:', 'green');
|
|
809
|
-
log(' npx devlyn-cli Install/update
|
|
906
|
+
log(' npx devlyn-cli Install/update devlyn (pick agents: Claude / Codex / omp / Pi / …)');
|
|
810
907
|
log(' npx devlyn-cli list List available skills & templates');
|
|
811
|
-
log(' npx devlyn-cli -y Install without prompts');
|
|
908
|
+
log(' npx devlyn-cli -y Install Claude core without prompts');
|
|
812
909
|
log(' npx devlyn-cli agents Install agents for detected CLIs');
|
|
813
910
|
log(' npx devlyn-cli agents all Install agents for all supported CLIs');
|
|
814
911
|
log(' npx devlyn-cli benchmark Run the resolve benchmark suite');
|
|
@@ -1065,12 +1162,10 @@ switch (command) {
|
|
|
1065
1162
|
log('─'.repeat(44), 'dim');
|
|
1066
1163
|
const subArg = args[1];
|
|
1067
1164
|
if (subArg === 'all') {
|
|
1068
|
-
// Install for all supported CLIs regardless of detection
|
|
1165
|
+
// Install for all supported CLIs regardless of detection. De-duped so the
|
|
1166
|
+
// shared ~/.agents/skills dir (omp + Pi) and shared AGENTS.md write once.
|
|
1069
1167
|
log('\n🤖 Installing agents for all supported CLIs...', 'blue');
|
|
1070
|
-
|
|
1071
|
-
for (const cliKey of Object.keys(CLI_TARGETS)) {
|
|
1072
|
-
if (installAgentsForCLI(cliKey)) count++;
|
|
1073
|
-
}
|
|
1168
|
+
const count = installSelectedCLITargets(Object.keys(CLI_TARGETS));
|
|
1074
1169
|
log(`\n✅ Agents installed for ${count} CLI${count !== 1 ? 's' : ''}`, 'green');
|
|
1075
1170
|
} else if (subArg && CLI_TARGETS[subArg]) {
|
|
1076
1171
|
// Install for a specific CLI
|
|
@@ -39,7 +39,7 @@ Each phase routes to an engine and prepends the per-engine adapter header from `
|
|
|
39
39
|
<modes>
|
|
40
40
|
Three input shapes:
|
|
41
41
|
|
|
42
|
-
1. **Free-form**: `/devlyn:resolve "fix the login bug"
|
|
42
|
+
1. **Free-form**: `/devlyn:resolve "fix the login bug"` (inline goal) or `/devlyn:resolve --goal-file <path>` (goal text read from a file — the devlynd `ResolveAdapter` launcher path, injection-safe). PHASE 0 runs the complexity classifier and either proceeds with an internal mini-spec (trivial), drafts focused questions for in-prompt resolution (medium), or escalates to `/devlyn:ideate` (large/ambiguous). No mid-pipeline prompts in any branch.
|
|
43
43
|
2. **Spec**: `/devlyn:resolve --spec docs/roadmap/phase-N/X.md`. Spec is read-only. Stage verification commands from sibling `spec.expected.json`; if absent, use the legacy `## Verification` JSON block.
|
|
44
44
|
3. **Verify-only**: `/devlyn:resolve --verify-only <diff-or-PR-ref> --spec <path>`. Skips PHASE 1-4. Runs PHASE 5 (VERIFY) on the supplied diff against the spec.
|
|
45
45
|
</modes>
|
|
@@ -58,6 +58,7 @@ Once `state.implement_passed_sha` is non-null (PHASE 2 returned and produced a d
|
|
|
58
58
|
- `--engine MODE` (default `claude`) — picks the adapter for IMPLEMENT, CLEANUP, and the primary VERIFY judge. It does not disable VERIFY pair-mode; when a VERIFY pair trigger fires, the second judge uses the OTHER engine.
|
|
59
59
|
- `--spec <path>` — switches to spec mode.
|
|
60
60
|
- `--verify-only <ref>` — switches to verify-only mode. Requires `--spec`.
|
|
61
|
+
- `--goal-file <path>` — free-form mode: read the goal text from `<path>` (relative to cwd) instead of an inline positional goal. Used by the devlynd `ResolveAdapter` launcher (goal written to `.devlyn/goal.txt`, referenced by path — never inlined as argv). The file content is opaque goal text fed to the complexity classifier, **never re-parsed as flags**. Mutually exclusive with `--spec`, `--verify-only`, and an inline positional goal.
|
|
61
62
|
- `--pair-verify` — force pair-mode JUDGE in PHASE 5 even when not auto-triggered.
|
|
62
63
|
- `--no-pair` — disable conditional VERIFY pair-JUDGE for this run. Record `pair_trigger.skipped_reason: "user_no_pair"` whenever a trigger would otherwise fire.
|
|
63
64
|
- `--risk-probes` — insert PHASE 1.5 cross-engine probe derivation. The OTHER engine converts visible `## Verification` bullets into bounded executable probes before IMPLEMENT; BUILD_GATE and VERIFY replay them mechanically.
|
|
@@ -67,12 +68,12 @@ Once `state.implement_passed_sha` is non-null (PHASE 2 returned and produced a d
|
|
|
67
68
|
|
|
68
69
|
2. Engine pre-flight: follow `_shared/engine-preflight.md`. If a required engine is unavailable, halt with a BLOCKED verdict and setup instructions instead of downgrading.
|
|
69
70
|
|
|
70
|
-
`--pair-verify` and `--no-pair` are mutually exclusive; if both are present, stop with `BLOCKED:invalid-flags`.
|
|
71
|
+
`--pair-verify` and `--no-pair` are mutually exclusive; if both are present, stop with `BLOCKED:invalid-flags`. Likewise `--goal-file` combined with `--spec`, `--verify-only`, or an inline positional goal — or a missing/repeated `--goal-file` value — stops with `BLOCKED:invalid-flags`. (These `BLOCKED:<reason>` strings are report-level only; the JSON `phases.*.verdict` carrier in `pipeline.state.json` always stays the bare enum `"BLOCKED"`.)
|
|
71
72
|
|
|
72
73
|
3. Initialize `.devlyn/pipeline.state.json` per `references/state-schema.md`. Set `state.run_id`, `started_at`, `engine`, `pair_verify: true` only when `--pair-verify` was passed and `false` otherwise, `base_ref.{branch, sha}`, `rounds.{max_rounds, global: 0}`, `bypasses`, empty `phases`, empty `criteria`, and `risk_profile: { high_risk: false, reasons: [], risk_probes_enabled: false, pair_default_enabled: true }`. `risk_profile` is strict typed state: keep it an object, keep the three flags as JSON booleans, and keep `reasons` as a string array; never serialize booleans as strings.
|
|
73
74
|
|
|
74
75
|
4. **Mode-specific init**:
|
|
75
|
-
- **Free-form**: read `references/free-form-mode.md
|
|
76
|
+
- **Free-form**: first resolve `goal_text` — the inline positional goal, OR (when `--goal-file <path>` was passed) the raw UTF-8 content of `<path>`, read as opaque goal text (never re-parsed as flags). Fail closed before classifying: empty/whitespace-only file → `BLOCKED:goal-file-empty`; missing/unreadable/not-a-regular-file/invalid-UTF-8 → `BLOCKED:goal-file-unreadable`; an absolute path, a `..` segment, or a path that canonicalizes outside cwd → `BLOCKED:goal-file-invalid-path`. (Like all PHASE-0 halts these are report-level `BLOCKED:<reason>` strings — no `phases.*.verdict` is written, so the bare-enum carrier rule is never violated.) Then read `references/free-form-mode.md` and run the complexity classifier deterministically on `goal_text` (rules over keyword density / file count / spec-shape signals, plus pair-evidence intent). Set `state.complexity ∈ {trivial, medium, large}`. Trivial: write internal mini-spec to `.devlyn/criteria.generated.md` and proceed. Medium: synthesize a minimal spec from the goal + add 1-2 context anchors from the codebase, write to `.devlyn/criteria.generated.md`, proceed. Every free-form branch that writes criteria must set `state.source.type = "generated"`, `state.source.criteria_path = ".devlyn/criteria.generated.md"`, and `state.source.criteria_sha256` from the raw file bytes. Large: log `recommend: /devlyn:ideate first` in the final report and either halt (default) or proceed with assumed defaults if `--continue-on-large` flag set, except pair-evidence intent without an actionable solo-headroom hypothesis must halt with `BLOCKED:solo-headroom-hypothesis-required`, and unmeasured pair-candidate intent without solo ceiling avoidance must halt with `BLOCKED:solo-ceiling-avoidance-required`.
|
|
76
77
|
- **Spec**: validate spec exists. If sibling `spec.expected.json` exists, run `--check-expected <expected-path>` to validate both the expected contract, sibling spec `complexity` frontmatter, and any present actionable solo-headroom hypothesis; if the spec has a solo-headroom hypothesis, its observable command must match `spec.expected.json.verification_commands[].cmd`. Then stage `.devlyn/spec-verify.json` from `verification_commands`. Otherwise run `--check <spec-path>` to validate the legacy inline carrier plus supported `complexity` frontmatter and any present actionable solo-headroom hypothesis; if the spec uses an inline `## Verification` JSON carrier, any solo-headroom hypothesis command must match that carrier's `verification_commands[].cmd`. Then stage from the legacy inline carrier. Compute `state.source.spec_sha256`.
|
|
77
78
|
- **Verify-only**: skip to PHASE 5 with `state.source.spec_path` set, the supplied diff captured at `.devlyn/external-diff.patch`.
|
|
78
79
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Free-form mode — complexity classifier
|
|
2
2
|
|
|
3
|
-
When `/devlyn:resolve` is invoked with a free-form goal (no `--spec`)
|
|
3
|
+
When `/devlyn:resolve` is invoked with a free-form goal (no `--spec`) — whether the goal is the inline positional text or the content of `--goal-file <path>` (PHASE 0 resolves `goal_text` from either source before classifying) — PHASE 0 runs this classifier to set `state.complexity ∈ {trivial, medium, large}` and either proceeds with an internal mini-spec, drafts focused questions for in-prompt resolution, or recommends `/devlyn:ideate` first.
|
|
4
4
|
|
|
5
5
|
The classifier is rules-based / deterministic — not an LLM judgment call. Decision rules below.
|
|
6
6
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devlyn-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"description": "AI development toolkit for Claude Code — ideate, resolve, and ship with context engineering and agent orchestration",
|
|
5
5
|
"homepage": "https://github.com/fysoul17/devlyn-cli#readme",
|
|
6
6
|
"bin": {
|
package/scripts/lint-skills.sh
CHANGED
|
@@ -242,10 +242,11 @@ if [ ! -e "optional-skills/devlyn:design-ui" ]; then
|
|
|
242
242
|
else
|
|
243
243
|
bad "devlyn:design-ui must not be installed as an optional addon"
|
|
244
244
|
fi
|
|
245
|
-
if grep -Fq "
|
|
246
|
-
|
|
245
|
+
if grep -Fq "const DEVLYN_CORE_SKILLS = ['devlyn:resolve', 'devlyn:ideate', 'devlyn:design-ui', '_shared'];" bin/devlyn.js \
|
|
246
|
+
&& grep -Fq "skillsToInstall: DEVLYN_CORE_SKILLS" bin/devlyn.js; then
|
|
247
|
+
ok "Codex install includes devlyn:design-ui (via shared DEVLYN_CORE_SKILLS bundle)"
|
|
247
248
|
else
|
|
248
|
-
bad "Codex skillsToInstall must include devlyn:design-ui"
|
|
249
|
+
bad "Codex skillsToInstall must include devlyn:design-ui (shared DEVLYN_CORE_SKILLS bundle)"
|
|
249
250
|
fi
|
|
250
251
|
if ! grep -F "name: 'devlyn:design-ui'" bin/devlyn.js >/dev/null 2>&1; then
|
|
251
252
|
ok "devlyn:design-ui is absent from OPTIONAL_ADDONS"
|