devlyn-cli 4.3.7 → 4.3.8

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,11 @@ function discoveryRoots(context, env, config) {
156
160
  return [...new Set(roots)];
157
161
  }
158
162
 
159
- function inspectSkills(context, release, env, config) {
163
+ function inspectSkills(context, release, env, config, active) {
160
164
  const found = [], errors = [], hashes = new Map();
161
165
  for (const root of discoveryRoots(context, env, config)) {
166
+ // Reusing the selected, verified release is not a competing OpenCode source.
167
+ if (active && context.engine === 'opencode' && config?.skillsPaths && root === path.join(release.root, 'config/skills')) continue;
162
168
  for (const name of SKILLS) {
163
169
  const dir = path.join(root, name);
164
170
  try {
@@ -227,7 +233,7 @@ function planActivation(releaseRoot, input, env = process.env) {
227
233
  fail('release-version-conflict', { version: context.recordedRelease.version }, 3);
228
234
  }
229
235
  const config = effectiveConfig(context);
230
- const { found, errors } = inspectSkills(context, release, env, config);
236
+ const { found, errors } = inspectSkills(context, release, env, config, !off);
231
237
  if (errors.length) status.hide = channel('partial', null, errors);
232
238
  status.inheritedDevlyn = found.filter((entry) => entry.owned).map((entry) => entry.path);
233
239
  const supported = supportedVersion(context.engine, context.version);
@@ -368,19 +374,35 @@ function planActivation(releaseRoot, input, env = process.env) {
368
374
  let inline;
369
375
  try { inline = config.inlineConfig == null ? {} : JSON.parse(config.inlineConfig); }
370
376
  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' })]);
377
+ const invalidField = !object(inline) || (inline.instructions !== undefined && !strings(inline.instructions))
378
+ ? 'inlineConfig.instructions'
379
+ : config.skillsPaths != null && inline.skills !== undefined && !object(inline.skills) ? 'inlineConfig.skills' : null;
380
+ if (invalidField) {
381
+ status.principles = channel('partial', null, [reason('config-unreadable', { field: invalidField })]);
382
+ status.skills = channel('partial', null, [reason('config-unreadable', { field: invalidField })]);
374
383
  break;
375
384
  }
385
+ if (Array.isArray(config.skillsPaths) && !collisions.length && !errors.length) {
386
+ inline.skills = { ...inline.skills, paths: [...new Set([...config.skillsPaths, path.join(release.root, 'config/skills')])] };
387
+ native('skills.paths', 'read-file', `Read ${path.join(release.root, 'config/skills/devlyn-ideate/SKILL.md')}`);
388
+ } else fallback(config.skillsPaths == null ? [] : [...collisions, ...errors]);
376
389
  plan.envPatch.OPENCODE_CONFIG_CONTENT = JSON.stringify({ ...inline,
377
390
  instructions: [...(inline.instructions || []), file('devlyn-instructions.md', instructions)] });
378
- principles('OPENCODE_CONFIG_CONTENT.instructions'); fallback();
391
+ principles('OPENCODE_CONFIG_CONTENT.instructions');
379
392
  break;
380
393
  }
381
394
  case 'copilot': {
382
- status.principles = context.committedBlockApplies ? channel('configured', 'native-project')
383
- : channel('unavailable', null, [reason('principles-unavailable')]);
395
+ if (!context.committedBlockApplies) {
396
+ const directory = path.join(context.runDir, 'copilot');
397
+ if (directory.includes(',')) {
398
+ status.principles = channel('unavailable', null, [reason('principles-unavailable', { detail: 'comma-in-path' })]);
399
+ } else {
400
+ file('copilot/devlyn.instructions.md', '---\napplyTo: "**"\n---\n' + instructions);
401
+ const dirs = config.instructionsDirs ?? env.COPILOT_CUSTOM_INSTRUCTIONS_DIRS ?? '';
402
+ plan.envPatch.COPILOT_CUSTOM_INSTRUCTIONS_DIRS = dirs.split(',').includes(directory) ? dirs : (dirs ? `${dirs},${directory}` : directory);
403
+ principles('COPILOT_CUSTOM_INSTRUCTIONS_DIRS');
404
+ }
405
+ }
384
406
  const root = path.join(release.root, 'config/skills');
385
407
  if (root.includes(',') || collisions.length || errors.length) {
386
408
  status.skills = channel('partial', null, root.includes(',') ? [reason('skills-unavailable', { detail: 'comma-in-path' })] : [...collisions, ...errors]);
@@ -768,6 +768,7 @@
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"
772
773
  ]
773
774
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devlyn",
3
- "version": "4.3.7",
3
+ "version": "4.3.8",
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-8",
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.8",
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.8",
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.8",
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": "980f251957d9093b47ba2c5229b77145233a7253938220b69069468f7b4f8979",
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": "9012124a107faba4ee1a0719462920590ff04bcdf82d5bc40d178ef8f182ea82",
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": "54f89d39a32c9d822321ae2a2805c8fe071c2b78c6b84f1c29ac85e4ae59667f",
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": "6c507f8668ee2df5322e6bc0a7f1f7303410d2566b24709eaf4a9a581a25755c",
443
443
  "executable": false
444
444
  },
445
445
  {
446
446
  "path": "native/codex/plugins/devlyn/.codex-plugin/plugin.json",
447
- "sha256": "c582e887aaad4322678e85ba02997ff51ef8f30d2fa50458024787884422117b",
447
+ "sha256": "54f89d39a32c9d822321ae2a2805c8fe071c2b78c6b84f1c29ac85e4ae59667f",
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": "42de8c1332266cae7209808646f51e3b97f3c8e2dc2e4775e0e0ab9daa52bfd1",
1623
1623
  "executable": false
1624
1624
  }
1625
1625
  ],
1626
1626
  "digest": {
1627
1627
  "algorithm": "sha256",
1628
- "value": "05823285e84ce3fc21e7003776b7216cbdbb78b7f4cc0f5029d2976736326f59"
1628
+ "value": "cac9f8a9d305a77ecff9139fe06bad8a9932a47dc1f800c20520c5a79b969070"
1629
1629
  }
1630
1630
  }