devlyn-cli 4.3.7 → 4.3.9

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.
@@ -89,14 +89,15 @@ function effectiveConfig(context) {
89
89
  const keys = {
90
90
  claude: ['appendSystemPrompt', 'pluginDirs'], codex: ['developer_instructions', 'skills', 'marketplaces', 'plugins'],
91
91
  grok: ['rules'], omp: ['appendSystemPrompt', 'skills'], antigravity: [], cursor: [],
92
- opencode: ['inlineConfig'], kimi: ['ownerAgent'], copilot: ['skillsDirs'], qwen: ['appendSystemPrompt'],
92
+ opencode: ['inlineConfig', 'skillsPaths'], kimi: ['ownerAgent'], copilot: ['skillsDirs', 'instructionsDirs'], qwen: ['appendSystemPrompt'],
93
93
  }[context.engine];
94
94
  if (!object(config) || Object.keys(config).some((key) => !keys.includes(key))) return null;
95
- for (const field of ['appendSystemPrompt', 'developer_instructions', 'rules', 'inlineConfig', 'skillsDirs']) {
95
+ for (const field of ['appendSystemPrompt', 'developer_instructions', 'rules', 'inlineConfig', 'skillsDirs', 'instructionsDirs']) {
96
96
  if (config[field] !== undefined && config[field] !== null && !textValue(config[field])) return null;
97
97
  }
98
98
  if (context.engine === 'kimi' && typeof config.ownerAgent !== 'boolean') return null;
99
99
  if (config.pluginDirs !== undefined && (!strings(config.pluginDirs) || !config.pluginDirs.every(path.isAbsolute))) return null;
100
+ if (config.skillsPaths != null && !strings(config.skillsPaths)) return null;
100
101
  if (context.engine === 'codex') {
101
102
  for (const key of ['marketplaces', 'plugins', 'skills']) if (config[key] != null && !object(config[key])) return null;
102
103
  if (config.skills?.config !== undefined && (!Array.isArray(config.skills.config)
@@ -144,7 +145,10 @@ function discoveryRoots(context, env, config) {
144
145
  for (const dir of config?.skills?.customDirectories || []) add(context.cwd, dir);
145
146
  }
146
147
  if (engine === 'codex' && process.platform !== 'win32') add('/etc/codex', 'skills');
147
- if (engine === 'opencode') add(env.OPENCODE_CONFIG_DIR, 'skills');
148
+ if (engine === 'opencode') {
149
+ add(env.OPENCODE_CONFIG_DIR, 'skills');
150
+ for (const dir of config?.skillsPaths || []) add(context.cwd, dir);
151
+ }
148
152
  if (engine === 'copilot') for (const dir of (config?.skillsDirs ?? env.COPILOT_SKILLS_DIRS ?? '').split(',').filter(Boolean)) add(context.cwd, dir);
149
153
  for (let dir = path.resolve(context.cwd); ; dir = path.dirname(dir)) {
150
154
  if (native[1]) add(dir, native[1], 'skills');
@@ -156,9 +160,38 @@ function discoveryRoots(context, env, config) {
156
160
  return [...new Set(roots)];
157
161
  }
158
162
 
159
- function inspectSkills(context, release, env, config) {
163
+ function kimiSkillRoots(context, env) {
164
+ const home = env.HOME || env.USERPROFILE || os.homedir();
165
+ let project = path.resolve(context.cwd);
166
+ const roots = new Set(), errors = [];
167
+ for (let dir = project; ; dir = path.dirname(dir)) {
168
+ const git = path.join(dir, '.git');
169
+ try {
170
+ if (fs.statSync(git, { throwIfNoEntry: false })) { project = dir; break; }
171
+ } catch (error) {
172
+ errors.push(reason('config-unreadable', { path: git, detail: error.message }));
173
+ break;
174
+ }
175
+ if (dir === path.dirname(dir)) break;
176
+ }
177
+ // --skills-dir replaces auto discovery and makes every root user-priority:
178
+ // first-name wins, so preserve workspace precedence by putting it first.
179
+ for (const root of [path.join(project, '.kimi-code/skills'), path.join(project, '.agents/skills'),
180
+ path.resolve(env.KIMI_CODE_HOME || path.join(home, '.kimi-code'), 'skills'), path.resolve(home, '.agents/skills')]) {
181
+ try {
182
+ if (fs.statSync(root, { throwIfNoEntry: false })?.isDirectory()) roots.add(fs.realpathSync(root));
183
+ } catch (error) {
184
+ errors.push(reason('config-unreadable', { path: root, detail: error.message }));
185
+ }
186
+ }
187
+ return { roots: [...roots], errors };
188
+ }
189
+
190
+ function inspectSkills(context, release, env, config, active) {
160
191
  const found = [], errors = [], hashes = new Map();
161
192
  for (const root of discoveryRoots(context, env, config)) {
193
+ // Reusing the selected, verified release is not a competing OpenCode source.
194
+ if (active && context.engine === 'opencode' && config?.skillsPaths && root === path.join(release.root, 'config/skills')) continue;
162
195
  for (const name of SKILLS) {
163
196
  const dir = path.join(root, name);
164
197
  try {
@@ -227,7 +260,7 @@ function planActivation(releaseRoot, input, env = process.env) {
227
260
  fail('release-version-conflict', { version: context.recordedRelease.version }, 3);
228
261
  }
229
262
  const config = effectiveConfig(context);
230
- const { found, errors } = inspectSkills(context, release, env, config);
263
+ const { found, errors } = inspectSkills(context, release, env, config, !off);
231
264
  if (errors.length) status.hide = channel('partial', null, errors);
232
265
  status.inheritedDevlyn = found.filter((entry) => entry.owned).map((entry) => entry.path);
233
266
  const supported = supportedVersion(context.engine, context.version);
@@ -368,19 +401,35 @@ function planActivation(releaseRoot, input, env = process.env) {
368
401
  let inline;
369
402
  try { inline = config.inlineConfig == null ? {} : JSON.parse(config.inlineConfig); }
370
403
  catch (error) { if (!(error instanceof SyntaxError)) throw error; inline = null; }
371
- if (!object(inline) || (inline.instructions !== undefined && !strings(inline.instructions))) {
372
- status.principles = channel('partial', null, [reason('config-unreadable', { field: 'inlineConfig.instructions' })]);
373
- status.skills = channel('partial', null, [reason('config-unreadable', { field: 'inlineConfig.instructions' })]);
404
+ const invalidField = !object(inline) || (inline.instructions !== undefined && !strings(inline.instructions))
405
+ ? 'inlineConfig.instructions'
406
+ : config.skillsPaths != null && inline.skills !== undefined && !object(inline.skills) ? 'inlineConfig.skills' : null;
407
+ if (invalidField) {
408
+ status.principles = channel('partial', null, [reason('config-unreadable', { field: invalidField })]);
409
+ status.skills = channel('partial', null, [reason('config-unreadable', { field: invalidField })]);
374
410
  break;
375
411
  }
412
+ if (Array.isArray(config.skillsPaths) && !collisions.length && !errors.length) {
413
+ inline.skills = { ...inline.skills, paths: [...new Set([...config.skillsPaths, path.join(release.root, 'config/skills')])] };
414
+ native('skills.paths', 'read-file', `Read ${path.join(release.root, 'config/skills/devlyn-ideate/SKILL.md')}`);
415
+ } else fallback(config.skillsPaths == null ? [] : [...collisions, ...errors]);
376
416
  plan.envPatch.OPENCODE_CONFIG_CONTENT = JSON.stringify({ ...inline,
377
417
  instructions: [...(inline.instructions || []), file('devlyn-instructions.md', instructions)] });
378
- principles('OPENCODE_CONFIG_CONTENT.instructions'); fallback();
418
+ principles('OPENCODE_CONFIG_CONTENT.instructions');
379
419
  break;
380
420
  }
381
421
  case 'copilot': {
382
- status.principles = context.committedBlockApplies ? channel('configured', 'native-project')
383
- : channel('unavailable', null, [reason('principles-unavailable')]);
422
+ if (!context.committedBlockApplies) {
423
+ const directory = path.join(context.runDir, 'copilot');
424
+ if (directory.includes(',')) {
425
+ status.principles = channel('unavailable', null, [reason('principles-unavailable', { detail: 'comma-in-path' })]);
426
+ } else {
427
+ file('copilot/devlyn.instructions.md', '---\napplyTo: "**"\n---\n' + instructions);
428
+ const dirs = config.instructionsDirs ?? env.COPILOT_CUSTOM_INSTRUCTIONS_DIRS ?? '';
429
+ plan.envPatch.COPILOT_CUSTOM_INSTRUCTIONS_DIRS = dirs.split(',').includes(directory) ? dirs : (dirs ? `${dirs},${directory}` : directory);
430
+ principles('COPILOT_CUSTOM_INSTRUCTIONS_DIRS');
431
+ }
432
+ }
384
433
  const root = path.join(release.root, 'config/skills');
385
434
  if (root.includes(',') || collisions.length || errors.length) {
386
435
  status.skills = channel('partial', null, root.includes(',') ? [reason('skills-unavailable', { detail: 'comma-in-path' })] : [...collisions, ...errors]);
@@ -398,7 +447,12 @@ function planActivation(releaseRoot, input, env = process.env) {
398
447
  status.skills = channel('partial', null, [reason('skills-unavailable', { launchMode: context.launchMode, ownerAgent: config.ownerAgent })]);
399
448
  } else {
400
449
  plan.engineArgs.push('--agent-file', file('devlyn-kimi.md', joinText('${base_prompt}', instructions)));
401
- principles('agent-file'); fallback();
450
+ principles('agent-file');
451
+ const kimi = kimiSkillRoots(context, env);
452
+ if (!collisions.length && !errors.length && !kimi.errors.length) {
453
+ for (const root of [...kimi.roots, path.join(release.root, 'config/skills')]) plan.engineArgs.push('--skills-dir', root);
454
+ native('--skills-dir', 'read-file', `Read ${path.join(release.root, 'config/skills/devlyn-ideate/SKILL.md')}`);
455
+ } else fallback([...collisions, ...errors, ...kimi.errors]);
402
456
  }
403
457
  break;
404
458
  case 'antigravity':
@@ -768,6 +768,8 @@
768
768
  "4.3.4",
769
769
  "4.3.5",
770
770
  "4.3.6",
771
- "4.3.7"
771
+ "4.3.7",
772
+ "4.3.8",
773
+ "4.3.9"
772
774
  ]
773
775
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devlyn",
3
- "version": "4.3.7",
3
+ "version": "4.3.9",
4
4
  "description": "Devlyn harness skills",
5
5
  "skills": "./skills/"
6
6
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "devlyn-harness-v4-3-7",
2
+ "name": "devlyn-harness-v4-3-9",
3
3
  "plugins": [
4
4
  {
5
5
  "name": "devlyn",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devlyn",
3
- "version": "4.3.7",
3
+ "version": "4.3.9",
4
4
  "description": "Devlyn harness skills",
5
5
  "skills": "./skills/"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devlyn-cli",
3
- "version": "4.3.7",
3
+ "version": "4.3.9",
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": {
package/release.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "package": "devlyn-cli",
4
- "version": "4.3.7",
4
+ "version": "4.3.9",
5
5
  "principles": {
6
6
  "defaults": "config/skills/_shared/runtime-principles.md",
7
7
  "memory": {
@@ -139,7 +139,7 @@
139
139
  },
140
140
  {
141
141
  "path": "bin/harness-activation.js",
142
- "sha256": "94adca93876fd0d8a39b5dddd1e017296b9ff7d1cf6037871df3d32250ea83ba",
142
+ "sha256": "fecb47b04e7f6e72735a9ea50b6aa524eca611d42c6db1b03c1260ddf2259bbc",
143
143
  "executable": false
144
144
  },
145
145
  {
@@ -164,7 +164,7 @@
164
164
  },
165
165
  {
166
166
  "path": "bin/skill-history.json",
167
- "sha256": "8272d3975ef06e1108c16c0d56b2178a4de73e2020f82f1f10a50c2a3d5ddb46",
167
+ "sha256": "947ec7e9dead4b4e9ebb234219c7aaea76b0561fde997c95d53c67f6babbdb3a",
168
168
  "executable": false
169
169
  },
170
170
  {
@@ -309,7 +309,7 @@
309
309
  },
310
310
  {
311
311
  "path": "native/claude/.claude-plugin/plugin.json",
312
- "sha256": "c582e887aaad4322678e85ba02997ff51ef8f30d2fa50458024787884422117b",
312
+ "sha256": "5aa927b9eb784e0a246571e8c747607390c7dc89207a66b9bc8759281429b259",
313
313
  "executable": false
314
314
  },
315
315
  {
@@ -439,12 +439,12 @@
439
439
  },
440
440
  {
441
441
  "path": "native/codex/.agents/plugins/marketplace.json",
442
- "sha256": "74699fb150844bc39ebd1cf11696c993d22c7f9add43b21e8656efd8af3f70de",
442
+ "sha256": "39a9e1bcfd87ffca63ce3e6a7b38d280b8bf4db725a28683c2f1f60cdb792730",
443
443
  "executable": false
444
444
  },
445
445
  {
446
446
  "path": "native/codex/plugins/devlyn/.codex-plugin/plugin.json",
447
- "sha256": "c582e887aaad4322678e85ba02997ff51ef8f30d2fa50458024787884422117b",
447
+ "sha256": "5aa927b9eb784e0a246571e8c747607390c7dc89207a66b9bc8759281429b259",
448
448
  "executable": false
449
449
  },
450
450
  {
@@ -1619,12 +1619,12 @@
1619
1619
  },
1620
1620
  {
1621
1621
  "path": "package.json",
1622
- "sha256": "0450b10c13cf6b932b441138368a46060b2a2278bfde57474bb73b4ceb160de9",
1622
+ "sha256": "9c67dfff676ecd8d7597146398ab5304c6ecac12d870b1ff1924478060614acc",
1623
1623
  "executable": false
1624
1624
  }
1625
1625
  ],
1626
1626
  "digest": {
1627
1627
  "algorithm": "sha256",
1628
- "value": "05823285e84ce3fc21e7003776b7216cbdbb78b7f4cc0f5029d2976736326f59"
1628
+ "value": "de2c9dba7a92d6c9844f3434fbdb21c8236e58b5e64e5f475fcca0dfb4b11c3f"
1629
1629
  }
1630
1630
  }