devlyn-cli 4.3.28 → 4.3.30
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/AGENTS.md +1 -1
- package/CLAUDE.md +1 -1
- package/README.md +10 -10
- package/bin/devlyn.js +92 -58
- package/bin/engines.js +10 -10
- package/bin/harness-activation.js +369 -23
- package/bin/harness-context.js +7 -7
- package/bin/instruction-templates.json +2 -0
- package/bin/skill-history.json +12 -0
- package/config/skills/_shared/engine-doctor.sh +2 -2
- package/config/skills/_shared/engine-preflight.md +2 -4
- package/config/skills/_shared/managed-storage.md +80 -0
- package/config/skills/_shared/platform-support.py +67 -28
- package/config/skills/_shared/role-config.py +16 -7
- package/config/skills/_shared/runtime-principles.md +1 -1
- package/config/skills/_shared/storage-client.py +31 -7
- package/config/skills/_shared/task-complete.py +1 -1
- package/config/skills/_shared/task-completion.md +5 -91
- package/config/skills/devlyn-engines/SKILL.md +1 -1
- package/config/skills/devlyn-ideate/SKILL.md +2 -2
- package/config/skills/devlyn-ideate/references/executor.md +62 -0
- package/config/skills/devlyn-ideate/references/loop.md +4 -63
- package/config/skills/devlyn-ideate/references/package-format.md +1 -2
- package/config/skills/devlyn-ideate/scripts/acceptance.py +442 -21
- package/config/skills/devlyn-ideate/scripts/queue.py +58 -10
- package/harness/AGENTS.md +2 -2
- package/harness/CLAUDE.md +2 -2
- package/native/claude/.claude-plugin/plugin.json +1 -1
- package/native/claude/skills/_shared/engine-doctor.sh +2 -2
- package/native/claude/skills/_shared/engine-preflight.md +2 -4
- package/native/claude/skills/_shared/managed-storage.md +80 -0
- package/native/claude/skills/_shared/platform-support.py +67 -28
- package/native/claude/skills/_shared/role-config.py +16 -7
- package/native/claude/skills/_shared/runtime-principles.md +1 -1
- package/native/claude/skills/_shared/storage-client.py +31 -7
- package/native/claude/skills/_shared/task-complete.py +1 -1
- package/native/claude/skills/_shared/task-completion.md +5 -91
- package/native/claude/skills/devlyn-engines/SKILL.md +1 -1
- package/native/claude/skills/devlyn-ideate/SKILL.md +2 -2
- package/native/claude/skills/devlyn-ideate/references/executor.md +62 -0
- package/native/claude/skills/devlyn-ideate/references/loop.md +4 -63
- package/native/claude/skills/devlyn-ideate/references/package-format.md +1 -2
- package/native/claude/skills/devlyn-ideate/scripts/acceptance.py +442 -21
- package/native/claude/skills/devlyn-ideate/scripts/queue.py +58 -10
- package/native/codex/.agents/plugins/marketplace.json +1 -1
- package/native/codex/plugins/devlyn/.codex-plugin/plugin.json +1 -1
- package/native/codex/plugins/devlyn/skills/_shared/engine-doctor.sh +2 -2
- package/native/codex/plugins/devlyn/skills/_shared/engine-preflight.md +2 -4
- package/native/codex/plugins/devlyn/skills/_shared/managed-storage.md +80 -0
- package/native/codex/plugins/devlyn/skills/_shared/platform-support.py +67 -28
- package/native/codex/plugins/devlyn/skills/_shared/role-config.py +16 -7
- package/native/codex/plugins/devlyn/skills/_shared/runtime-principles.md +1 -1
- package/native/codex/plugins/devlyn/skills/_shared/storage-client.py +31 -7
- package/native/codex/plugins/devlyn/skills/_shared/task-complete.py +1 -1
- package/native/codex/plugins/devlyn/skills/_shared/task-completion.md +5 -91
- package/native/codex/plugins/devlyn/skills/devlyn-engines/SKILL.md +1 -1
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/SKILL.md +2 -2
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/executor.md +62 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/loop.md +4 -63
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/package-format.md +1 -2
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/scripts/acceptance.py +442 -21
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/scripts/queue.py +58 -10
- package/package.json +1 -11
- package/release.json +99 -69
- /package/config/skills/_shared/adapters/{claude.md → claude.adapter.md} +0 -0
- /package/config/skills/_shared/adapters/{codex.md → codex.adapter.md} +0 -0
- /package/config/skills/_shared/adapters/{omp.md → omp.adapter.md} +0 -0
- /package/native/claude/skills/_shared/adapters/{claude.md → claude.adapter.md} +0 -0
- /package/native/claude/skills/_shared/adapters/{codex.md → codex.adapter.md} +0 -0
- /package/native/claude/skills/_shared/adapters/{omp.md → omp.adapter.md} +0 -0
- /package/native/codex/plugins/devlyn/skills/_shared/adapters/{claude.md → claude.adapter.md} +0 -0
- /package/native/codex/plugins/devlyn/skills/_shared/adapters/{codex.md → codex.adapter.md} +0 -0
- /package/native/codex/plugins/devlyn/skills/_shared/adapters/{omp.md → omp.adapter.md} +0 -0
|
@@ -8,9 +8,14 @@ const history = require('./skill-history.json');
|
|
|
8
8
|
const { MARKER, marketplaceName, projectionPath, inspectProjection } = require('./codex-projection');
|
|
9
9
|
|
|
10
10
|
const SKILLS = ['devlyn-ideate', 'devlyn-engines'];
|
|
11
|
+
// The version an engine's run-folder channel was examined on; bin/engines.js keeps the engine's floor. An
|
|
12
|
+
// older version, and Windows (channelGate), keep the plan they had before the channel: only a first task
|
|
13
|
+
// carries the principles.
|
|
14
|
+
const CHANNEL_MINIMUM = { antigravity: '1.5.4', cursor: '2026.11.05-86047df' };
|
|
11
15
|
const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
12
16
|
const textValue = (value) => typeof value === 'string' && !value.includes('\0');
|
|
13
17
|
const strings = (value) => Array.isArray(value) && value.every(textValue);
|
|
18
|
+
const isFile = (file) => fs.statSync(file, { throwIfNoEntry: false })?.isFile() === true;
|
|
14
19
|
const reason = (code, params = {}) => ({ code, params });
|
|
15
20
|
const channel = (state = 'disabled', mechanism = null, reasons = []) => ({ state, mechanism, reasons });
|
|
16
21
|
const joinText = (...parts) => parts.filter((part) => part !== null && part !== undefined && part !== '').join('\n\n');
|
|
@@ -90,6 +95,7 @@ function effectiveConfig(context) {
|
|
|
90
95
|
claude: ['appendSystemPrompt', 'pluginDirs'], codex: ['developer_instructions', 'skills', 'marketplaces', 'plugins'],
|
|
91
96
|
grok: ['rules'], omp: ['appendSystemPrompt', 'skills'], antigravity: [], cursor: [],
|
|
92
97
|
opencode: ['inlineConfig', 'skillsPaths'], kimi: ['ownerAgent'], copilot: ['skillsDirs', 'instructionsDirs'], qwen: ['appendSystemPrompt'],
|
|
98
|
+
pi: ['appendSystemPrompt'],
|
|
93
99
|
}[context.engine];
|
|
94
100
|
if (!object(config) || Object.keys(config).some((key) => !keys.includes(key))) return null;
|
|
95
101
|
for (const field of ['appendSystemPrompt', 'developer_instructions', 'rules', 'inlineConfig', 'skillsDirs', 'instructionsDirs']) {
|
|
@@ -120,11 +126,27 @@ function effectiveConfig(context) {
|
|
|
120
126
|
return config;
|
|
121
127
|
}
|
|
122
128
|
|
|
129
|
+
// pi's agent dir is PI_CODING_AGENT_DIR, else ~/.pi/agent. That ~, and a leading ~ pi expands in the
|
|
130
|
+
// variable, is os.homedir(): USERPROFILE on Windows, HOME elsewhere (config.js getAgentDir). The home of
|
|
131
|
+
// its user .agents/skills takes HOME first on every platform (package-manager.js getHomeDir).
|
|
132
|
+
function piFolders(cwd, env, platform = process.platform) {
|
|
133
|
+
const paths = platform === 'win32' ? path.win32 : path.posix;
|
|
134
|
+
const home = (platform === 'win32' ? env.USERPROFILE : env.HOME) || os.homedir();
|
|
135
|
+
const dir = env.PI_CODING_AGENT_DIR;
|
|
136
|
+
const agentDir = dir ? dir.replace(platform === 'win32' ? /^~(?=$|[\\/])/ : /^~(?=$|\/)/, () => home) : paths.join(home, '.pi', 'agent');
|
|
137
|
+
return { agentDir: paths.resolve(cwd, agentDir), agentsHome: env.HOME || home };
|
|
138
|
+
}
|
|
139
|
+
|
|
123
140
|
function discoveryRoots(context, env, config) {
|
|
124
141
|
const home = env.HOME || env.USERPROFILE || os.homedir();
|
|
142
|
+
const pi = piFolders(context.cwd, env);
|
|
125
143
|
const roots = [...(context.skillRoots || [])];
|
|
126
144
|
const add = (base, ...parts) => { if (base) roots.push(path.resolve(base, ...parts)); };
|
|
127
145
|
const engine = context.engine;
|
|
146
|
+
// Antigravity and Cursor search the folders their examined version reads only where they get their run-folder
|
|
147
|
+
// channel; elsewhere they keep the folders searched before it.
|
|
148
|
+
const channelled = ['antigravity', 'cursor'].includes(engine) && !channelGate(context);
|
|
149
|
+
const antigravity = channelled && engine === 'antigravity';
|
|
128
150
|
const native = {
|
|
129
151
|
claude: [env.CLAUDE_CONFIG_DIR || path.join(home, '.claude'), '.claude'],
|
|
130
152
|
codex: [context.accountHome || env.CODEX_HOME || path.join(home, '.codex'), '.agents'],
|
|
@@ -135,10 +157,13 @@ function discoveryRoots(context, env, config) {
|
|
|
135
157
|
kimi: [env.KIMI_CODE_HOME || path.join(home, '.kimi-code'), '.kimi-code'],
|
|
136
158
|
copilot: [env.COPILOT_HOME || path.join(home, '.copilot'), '.github'],
|
|
137
159
|
qwen: [env.QWEN_HOME || path.join(home, '.qwen'), '.qwen'],
|
|
160
|
+
pi: [pi.agentDir, null],
|
|
138
161
|
}[engine];
|
|
139
162
|
if (engine !== 'codex') add(native[0], 'skills');
|
|
140
163
|
const copilotDefaultHome = !env.COPILOT_HOME || path.resolve(env.COPILOT_HOME) === path.join(home, '.copilot');
|
|
141
|
-
if (!['claude', 'cursor'].includes(engine) && (engine !== 'copilot' || copilotDefaultHome)) add(home, '.agents/skills');
|
|
164
|
+
if (!['claude', 'cursor'].includes(engine) && !antigravity && (engine !== 'copilot' || copilotDefaultHome)) add(engine === 'pi' ? pi.agentsHome : home, '.agents/skills');
|
|
165
|
+
// The examined Antigravity reads ~/.gemini/skills and its personal folder ~/.gemini/config instead.
|
|
166
|
+
if (antigravity) { add(home, '.gemini/skills'); add(home, '.gemini/config/skills'); }
|
|
142
167
|
if (['omp', 'opencode', 'grok'].includes(engine)) add(home, '.claude/skills');
|
|
143
168
|
if (engine === 'omp') {
|
|
144
169
|
add(home, '.codex/skills'); add(home, '.agent/skills');
|
|
@@ -150,12 +175,25 @@ function discoveryRoots(context, env, config) {
|
|
|
150
175
|
for (const dir of config?.skillsPaths || []) add(context.cwd, dir);
|
|
151
176
|
}
|
|
152
177
|
if (engine === 'copilot') for (const dir of (config?.skillsDirs ?? env.COPILOT_SKILLS_DIRS ?? '').split(',').filter(Boolean)) add(context.cwd, dir);
|
|
178
|
+
if (engine === 'pi') add(context.cwd, '.pi/skills');
|
|
179
|
+
const walk = [];
|
|
153
180
|
for (let dir = path.resolve(context.cwd); ; dir = path.dirname(dir)) {
|
|
181
|
+
walk.push(dir);
|
|
182
|
+
if (fs.existsSync(path.join(dir, '.git'))) break;
|
|
183
|
+
// Outside any repository the examined Antigravity reads the working directory alone.
|
|
184
|
+
if (dir === path.dirname(dir)) { if (antigravity) walk.splice(1); break; }
|
|
185
|
+
}
|
|
186
|
+
// The examined Cursor also searches nested folders below the working directory, which the planner does not.
|
|
187
|
+
const cursorFolders = channelled && engine === 'cursor' ? ['.cursor', '.agents', '.claude', '.codex', '.grok'] : [];
|
|
188
|
+
for (const folder of cursorFolders) add(home, folder, 'skills');
|
|
189
|
+
if (cursorFolders.length) add(home, '.cursor/skills-cursor');
|
|
190
|
+
for (const dir of walk) {
|
|
191
|
+
for (const folder of cursorFolders) add(dir, folder, 'skills');
|
|
154
192
|
if (native[1]) add(dir, native[1], 'skills');
|
|
155
193
|
if (!['claude', 'cursor'].includes(engine)) add(dir, '.agents/skills');
|
|
156
194
|
if (['grok', 'omp', 'opencode', 'copilot'].includes(engine)) add(dir, '.claude/skills');
|
|
157
195
|
if (['omp', 'antigravity'].includes(engine)) add(dir, '.agent/skills');
|
|
158
|
-
if (
|
|
196
|
+
if (antigravity) { add(dir, '_agents/skills'); add(dir, '_agent/skills'); }
|
|
159
197
|
}
|
|
160
198
|
return [...new Set(roots)];
|
|
161
199
|
}
|
|
@@ -187,6 +225,153 @@ function kimiSkillRoots(context, env) {
|
|
|
187
225
|
return { roots: [...roots], errors };
|
|
188
226
|
}
|
|
189
227
|
|
|
228
|
+
// omp and pi read their own APPEND_SYSTEM.md only when no append text is passed, and the plan passes
|
|
229
|
+
// some, so the owner's text leads it. Text the caller supplies wins, an explicit empty string included.
|
|
230
|
+
// Only when it supplies none (the key absent, or null for unset) is the first existing file `lookup()`
|
|
231
|
+
// names read, as `devlyn run` reads omp's (harness-context.js ompOwnerAppend). Returns {text}, or
|
|
232
|
+
// {reason} when the owner's text cannot be known.
|
|
233
|
+
function ownerAppend(config, lookup) {
|
|
234
|
+
if (config.appendSystemPrompt != null) return { text: config.appendSystemPrompt };
|
|
235
|
+
try {
|
|
236
|
+
const found = lookup();
|
|
237
|
+
if (found.reason) return found;
|
|
238
|
+
const file = found.files.find(isFile);
|
|
239
|
+
return { text: file === undefined ? null : fs.readFileSync(file, 'utf8') };
|
|
240
|
+
} catch (error) {
|
|
241
|
+
return { reason: reason('config-unreadable', { path: error.path, detail: error.code || error.message }) };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// pi's user and project settings.json (settings-manager.js). Entries they name resolve through pi's own
|
|
246
|
+
// package manager, which the planner does not repeat. A file that cannot be read is null and reported.
|
|
247
|
+
function piSettings(cwd, agentDir) {
|
|
248
|
+
const settings = { errors: [] };
|
|
249
|
+
for (const [scope, file] of [['user', path.join(agentDir, 'settings.json')], ['project', path.join(cwd, '.pi/settings.json')]]) {
|
|
250
|
+
try {
|
|
251
|
+
settings[scope] = fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '') || '{}') : {};
|
|
252
|
+
} catch (error) {
|
|
253
|
+
settings[scope] = null;
|
|
254
|
+
settings.errors.push(reason('config-unreadable', { path: file, detail: error.code || error.message }));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return settings;
|
|
258
|
+
}
|
|
259
|
+
const listed = (settings, ...keys) => keys.some((key) => settings?.[key] != null && settings[key].length !== 0);
|
|
260
|
+
|
|
261
|
+
// Whether the agent dir's extensions folder holds anything pi could load; pi skips dot entries and
|
|
262
|
+
// node_modules (package-manager.js collectAutoExtensionEntries).
|
|
263
|
+
function piExtensionEntries(agentDir) {
|
|
264
|
+
try {
|
|
265
|
+
return fs.readdirSync(path.join(agentDir, 'extensions')).some((name) => !name.startsWith('.') && name !== 'node_modules');
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (error.code === 'ENOENT') return false;
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// pi reads a project's .pi/APPEND_SYSTEM.md only when it trusts the project, else its agent dir's
|
|
273
|
+
// (resource-loader discoverAppendSystemPromptFile). pi first asks the extensions it loads before trust,
|
|
274
|
+
// and the first yes or no wins (project-trust.js resolveProjectTrusted). With project settings held
|
|
275
|
+
// untrusted, those come only from the agent dir's extensions folder and the extensions or packages its
|
|
276
|
+
// user settings name (resource-loader loadProjectTrustExtensions, package-manager resolve): the app
|
|
277
|
+
// passes no -e, and pi's built-in extensions answer no trust question. With none, trust is pi's saved
|
|
278
|
+
// decision for the folder or its nearest decided parent (trust.json). Otherwise pi settles trust at
|
|
279
|
+
// launch, so which file it reads cannot be known here. The planner never writes trust.
|
|
280
|
+
function piAppendFiles(cwd, agentDir, userSettings) {
|
|
281
|
+
const project = path.join(cwd, '.pi/APPEND_SYSTEM.md');
|
|
282
|
+
const user = path.join(agentDir, 'APPEND_SYSTEM.md');
|
|
283
|
+
if (!isFile(project)) return { files: [user] };
|
|
284
|
+
const undecided = { reason: reason('principles-unavailable', { detail: 'project-trust-undecided', path: project }) };
|
|
285
|
+
if (userSettings === null || listed(userSettings, 'extensions', 'packages') || piExtensionEntries(agentDir)) return undecided;
|
|
286
|
+
const store = path.join(agentDir, 'trust.json');
|
|
287
|
+
let trusted = null;
|
|
288
|
+
try {
|
|
289
|
+
const saved = fs.existsSync(store) ? JSON.parse(fs.readFileSync(store, 'utf8').replace(/^\uFEFF/, '')) : {};
|
|
290
|
+
if (!object(saved) || Object.values(saved).some((value) => ![true, false, null].includes(value))) throw new Error('not a trust record');
|
|
291
|
+
for (let dir = fs.realpathSync(cwd); trusted === null; dir = path.dirname(dir)) {
|
|
292
|
+
if (typeof saved[dir] === 'boolean') trusted = saved[dir];
|
|
293
|
+
else if (dir === path.dirname(dir)) break;
|
|
294
|
+
}
|
|
295
|
+
} catch (error) {
|
|
296
|
+
return { reason: reason('config-unreadable', { path: store, detail: error.code || error.message }) };
|
|
297
|
+
}
|
|
298
|
+
if (trusted === null) return undecided;
|
|
299
|
+
return { files: [trusted ? project : user] };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// The files pi may load as skills below a root (package-manager.js collectSkillEntries). pi stops at a
|
|
303
|
+
// folder's SKILL.md unless an ignore file hides it, and takes other .md files at the root of its own
|
|
304
|
+
// folders and below it in .agents/skills. Ignore files are not read here, so every .md file is taken and
|
|
305
|
+
// every folder searched: at least what pi finds is found. Dot entries and node_modules are skipped as pi
|
|
306
|
+
// skips them.
|
|
307
|
+
function piSkillFiles(root) {
|
|
308
|
+
const files = [], seen = new Set();
|
|
309
|
+
const visit = (dir) => {
|
|
310
|
+
let entries;
|
|
311
|
+
try {
|
|
312
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
313
|
+
} catch (error) {
|
|
314
|
+
if (dir === root && error.code === 'ENOENT') return;
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
seen.add(fs.realpathSync(dir));
|
|
318
|
+
for (const entry of entries) {
|
|
319
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
320
|
+
const full = path.join(dir, entry.name);
|
|
321
|
+
let kind = entry;
|
|
322
|
+
// pi follows links and skips one it cannot follow.
|
|
323
|
+
if (entry.isSymbolicLink()) { try { kind = fs.statSync(full); } catch { continue; } }
|
|
324
|
+
if (kind.isDirectory()) { if (!seen.has(fs.realpathSync(full))) visit(full); }
|
|
325
|
+
else if (kind.isFile() && entry.name.endsWith('.md')) files.push(full);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
visit(root);
|
|
329
|
+
return files;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// The names pi may give a skill file (skills.js loadSkillFromFile): its frontmatter's string `name`, else,
|
|
333
|
+
// also for an empty one, its folder's name. A plain scalar may be no string (null, a number), so the
|
|
334
|
+
// folder counts then too.
|
|
335
|
+
// null when the name is not one simple line, which counts as a collision.
|
|
336
|
+
function piSkillNames(file) {
|
|
337
|
+
const text = fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '').replace(/\r\n?/g, '\n');
|
|
338
|
+
const folder = path.basename(path.dirname(file));
|
|
339
|
+
const end = text.startsWith('---') ? text.indexOf('\n---', 3) : -1;
|
|
340
|
+
const lines = end < 0 ? [] : text.slice(4, end).split('\n');
|
|
341
|
+
if (/^\s/.test(lines.find((line) => line.trim() && !line.trim().startsWith('#')) ?? '')) return null;
|
|
342
|
+
// Lines that are or may be a top-level name key: quoted or complex keys, merges, anchors, flow mappings.
|
|
343
|
+
const keys = lines.flatMap((line, index) => (/^(?:name[ \t]*:|["'?{[&*!]|<<)/.test(line) ? [index] : []));
|
|
344
|
+
if (!keys.length) return [folder];
|
|
345
|
+
const match = keys.length === 1 && /^name:(.*)$/.exec(lines[keys[0]]);
|
|
346
|
+
if (!match) return null;
|
|
347
|
+
const value = match[1].replace(/\s#.*$/, '').trim();
|
|
348
|
+
const quoted = /^"([^"\\]*)"$|^'([^']*)'$/.exec(value);
|
|
349
|
+
if (quoted) return [(quoted[1] ?? quoted[2]) || folder];
|
|
350
|
+
const next = lines.slice(keys[0] + 1).find((line) => line.trim()) ?? '';
|
|
351
|
+
if (!value || /^[-?:,[\]{}#&*!|>'"%@`]/.test(value) || /^\s/.test(next)) return null;
|
|
352
|
+
return [value, folder];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// pi names a skill by its frontmatter wherever it sits below a root, and a found name wins over --skill.
|
|
356
|
+
// Files `found` already reports are skipped.
|
|
357
|
+
function piNameCollisions(roots, found) {
|
|
358
|
+
const collisions = [], errors = [], seen = new Set(found.map((entry) => entry.path));
|
|
359
|
+
for (const root of roots) {
|
|
360
|
+
try {
|
|
361
|
+
for (const file of piSkillFiles(root)) {
|
|
362
|
+
if (seen.has(file)) continue;
|
|
363
|
+
seen.add(file);
|
|
364
|
+
const names = piSkillNames(file);
|
|
365
|
+
if (names === null) collisions.push(reason('name-collision', { path: file }));
|
|
366
|
+
else for (const name of new Set(names)) if (SKILLS.includes(name)) collisions.push(reason('name-collision', { name, path: file }));
|
|
367
|
+
}
|
|
368
|
+
} catch (error) {
|
|
369
|
+
errors.push(reason('config-unreadable', { path: error.path || root, detail: error.code || error.message }));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return { collisions, errors };
|
|
373
|
+
}
|
|
374
|
+
|
|
190
375
|
function inspectSkills(context, release, env, config, active) {
|
|
191
376
|
const found = [], errors = [], hashes = new Map();
|
|
192
377
|
for (const root of discoveryRoots(context, env, config)) {
|
|
@@ -214,10 +399,73 @@ function skillCatalog(release, paths = path) {
|
|
|
214
399
|
return ['Devlyn session skill catalog (read the absolute file to invoke):',
|
|
215
400
|
...SKILLS.map((name) => `${name}: ${paths.join(root, name, 'SKILL.md')}`),
|
|
216
401
|
`This session's _shared folder is ${paths.join(root, '_shared')}.`,
|
|
217
|
-
'Use that absolute folder wherever the Devlyn defaults name _shared
|
|
402
|
+
'Use that absolute folder wherever the Devlyn defaults name _shared/.',
|
|
218
403
|
].join('\n');
|
|
219
404
|
}
|
|
220
405
|
|
|
406
|
+
// A skill entry of Antigravity's run folder: the release skill's own name and description lines as
|
|
407
|
+
// written, then the release SKILL.md to follow. A copy cannot be planned (plan files are text and the
|
|
408
|
+
// skills carry scripts), and since 1.5.2 Antigravity skips a link or a manifest entry that leads outside
|
|
409
|
+
// the added folder. Each line must be one complete top-level entry, or the plan fails.
|
|
410
|
+
function antigravitySkill(release, name) {
|
|
411
|
+
const dir = path.join(release.root, 'config/skills', name);
|
|
412
|
+
const file = path.join(dir, 'SKILL.md');
|
|
413
|
+
const text = fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n');
|
|
414
|
+
const end = text.startsWith('---\n') ? text.indexOf('\n---\n', 3) : -1;
|
|
415
|
+
const lines = end < 0 ? [] : text.slice(4, end).split('\n');
|
|
416
|
+
const header = ['name', 'description'].map((field) => {
|
|
417
|
+
const at = lines.flatMap((line, index) => (line.startsWith(`${field}:`) ? [index] : []));
|
|
418
|
+
if (at.length !== 1 || !lines[at[0]].slice(field.length + 1).trim() || /^\s/.test(lines[at[0] + 1] ?? '')) {
|
|
419
|
+
fail('config-unreadable', { path: file, field }, 1);
|
|
420
|
+
}
|
|
421
|
+
return lines[at[0]];
|
|
422
|
+
});
|
|
423
|
+
return `---\n${header.join('\n')}\n---\nRead ${file} and follow it as this skill. `
|
|
424
|
+
+ `Its folder, ${dir}, is the skill's folder (DEVLYN_SKILL_DIR), not the folder of this file.\n`;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// The path with the links of its existing part resolved; parts that do not exist yet are kept as given.
|
|
428
|
+
function physicalPath(target) {
|
|
429
|
+
let parent = path.resolve(target);
|
|
430
|
+
const missing = [];
|
|
431
|
+
while (!fs.existsSync(parent)) { missing.unshift(path.basename(parent)); parent = path.dirname(parent); }
|
|
432
|
+
return path.join(fs.realpathSync(parent), ...missing);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// The reasons for the principles and the skills when neither reaches the session.
|
|
436
|
+
const withheld = (params) => [reason('principles-unavailable', params), reason('skills-unavailable', params)];
|
|
437
|
+
|
|
438
|
+
// Why an engine keeps the plan it had before its run-folder channel, as the reasons for the principles and the
|
|
439
|
+
// skills when no first task carries them, or null when it gets the channel. The channels were examined on macOS
|
|
440
|
+
// and are not planned on Windows, whatever the version: it cannot show who owns a folder, and an argument that
|
|
441
|
+
// misbehaves there would break a pane. Below the version a channel was examined on, updating the engine gets it.
|
|
442
|
+
function channelGate(context) {
|
|
443
|
+
if (process.platform === 'win32') return withheld({ detail: 'platform-unexamined' });
|
|
444
|
+
const minimum = CHANNEL_MINIMUM[context.engine];
|
|
445
|
+
if (supportedVersion(context.engine, context.version, minimum)) return null;
|
|
446
|
+
const outdated = reason('engine-version-unsupported', { minimum });
|
|
447
|
+
return [outdated, outdated];
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// The first directory that makes a run-folder channel's `folder`, inside a resolved run directory (physicalPath),
|
|
451
|
+
// unsafe to hand an engine that loads what it finds there, or null. Every existing level from the folder up, the
|
|
452
|
+
// folder included, must be a real directory (a link is not one) of this user or root that no one else can write:
|
|
453
|
+
// another user could put a .git or a folder of their own there later. With `git`, no directory from the folder up
|
|
454
|
+
// may hold a .git entry of any kind: Antigravity 1.5.4 loads the agents and hooks of a Git root above an added
|
|
455
|
+
// folder with no trust check. A level that does not exist yet is skipped; a level lstat cannot read cannot be
|
|
456
|
+
// shown safe. Access-control lists are not read.
|
|
457
|
+
function unsafeRunDir(folder, git) {
|
|
458
|
+
const entry = (file) => {
|
|
459
|
+
try { return fs.lstatSync(file, { throwIfNoEntry: false }) ?? null; } catch { return false; }
|
|
460
|
+
};
|
|
461
|
+
for (let dir = folder; ; dir = path.dirname(dir)) {
|
|
462
|
+
const stats = entry(dir);
|
|
463
|
+
if (stats === false || (stats && (!stats.isDirectory() || ![process.getuid(), 0].includes(stats.uid) || stats.mode & 0o022))) return dir;
|
|
464
|
+
if (git && entry(path.join(dir, '.git')) !== null) return dir;
|
|
465
|
+
if (dir === path.dirname(dir)) return null;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
221
469
|
function codexRegistrations(config, accountHome) {
|
|
222
470
|
const owned = [], errors = [];
|
|
223
471
|
for (const [key, plugin] of Object.entries(config.plugins || {})) {
|
|
@@ -315,11 +563,7 @@ function planActivation(releaseRoot, input, env = process.env) {
|
|
|
315
563
|
const catalog = skillCatalog(release);
|
|
316
564
|
const instructions = joinText(defaults, catalog);
|
|
317
565
|
const file = (relativePath, utf8) => {
|
|
318
|
-
|
|
319
|
-
const missing = [];
|
|
320
|
-
while (!fs.existsSync(parent)) { missing.unshift(path.basename(parent)); parent = path.dirname(parent); }
|
|
321
|
-
const physical = path.join(fs.realpathSync(parent), ...missing);
|
|
322
|
-
const relative = path.relative(release.root, physical);
|
|
566
|
+
const relative = path.relative(release.root, physicalPath(context.runDir));
|
|
323
567
|
if (!relative || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative))) {
|
|
324
568
|
fail('invalid-arguments', { argument: 'runDir must be outside the release' }, 2);
|
|
325
569
|
}
|
|
@@ -327,11 +571,26 @@ function planActivation(releaseRoot, input, env = process.env) {
|
|
|
327
571
|
return path.join(context.runDir, relativePath);
|
|
328
572
|
};
|
|
329
573
|
const principles = (mechanism) => { status.principles = channel('configured', mechanism); };
|
|
330
|
-
|
|
331
|
-
|
|
574
|
+
// The catalog travels in the principles text: without that text only the absolute invocation is left.
|
|
575
|
+
const fallback = (reasons = [], withCatalog = true) => {
|
|
576
|
+
status.skills = withCatalog ? channel('partial', 'absolute-catalog', [...reasons, reason('skills-catalog')])
|
|
577
|
+
: channel('partial', null, reasons);
|
|
332
578
|
status.invocation = { kind: 'read-file', text: `Read ${path.join(release.root, 'config/skills/devlyn-ideate/SKILL.md')}` };
|
|
333
579
|
};
|
|
334
580
|
const native = (mechanism, kind, text) => { status.skills = channel('configured', mechanism); status.invocation = { kind, text }; };
|
|
581
|
+
// Where no run-folder channel is planned, only a first task carries the principles and the catalog, ahead
|
|
582
|
+
// of its text; without one nothing reaches the session and each channel gives its reason.
|
|
583
|
+
const firstTask = ([principlesReason, skillsReason]) => {
|
|
584
|
+
if (context.initialPrompt) {
|
|
585
|
+
status.principles = channel('partial', 'initial-task', [reason('principles-task-only')]);
|
|
586
|
+
plan.prompt = { ...context.initialPrompt, text: joinText(instructions, context.initialPrompt.text) };
|
|
587
|
+
fallback();
|
|
588
|
+
} else {
|
|
589
|
+
status.principles = channel('unavailable', null, [principlesReason]);
|
|
590
|
+
status.skills = channel('unavailable', null, [skillsReason]);
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
// A skill folder that cannot be inspected is also found, so every inspection error comes with a collision.
|
|
335
594
|
const collisions = found.map((entry) => reason('name-collision', { name: entry.name, path: entry.path }));
|
|
336
595
|
switch (context.engine) {
|
|
337
596
|
case 'claude': {
|
|
@@ -381,11 +640,39 @@ function planActivation(releaseRoot, input, env = process.env) {
|
|
|
381
640
|
case 'omp': {
|
|
382
641
|
const skills = { ...config.skills,
|
|
383
642
|
customDirectories: [...new Set([...(config.skills?.customDirectories || []), path.join(release.root, 'config/skills')])] };
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
643
|
+
// The files in the order `devlyn run` reads them (harness-context.js ompOwnerAppend).
|
|
644
|
+
const owner = ownerAppend(config, () => ({ files: [path.join(context.cwd, '.omp/APPEND_SYSTEM.md'),
|
|
645
|
+
path.join(env.HOME || env.USERPROFILE || os.homedir(), '.omp/agent/APPEND_SYSTEM.md')] }));
|
|
646
|
+
plan.engineArgs.push('--config', file('devlyn-omp.json', JSON.stringify({ skills }, null, 2) + '\n'));
|
|
647
|
+
// Owner text that cannot be known leaves the flag off, so the engine's own lookup stays intact.
|
|
648
|
+
if (owner.reason) status.principles = channel('partial', null, [owner.reason]);
|
|
649
|
+
else {
|
|
650
|
+
plan.engineArgs.push('--append-system-prompt', joinText(owner.text, instructions));
|
|
651
|
+
principles('append-system-prompt');
|
|
652
|
+
}
|
|
653
|
+
if (collisions.length || errors.length || skills.enableSkillCommands === false || skills.ignoredSkills?.length) {
|
|
654
|
+
fallback([...collisions, ...errors], !owner.reason);
|
|
655
|
+
} else native('skills.customDirectories', 'slash', '/skill:devlyn-ideate');
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
658
|
+
case 'pi': {
|
|
659
|
+
const { agentDir } = piFolders(context.cwd, env);
|
|
660
|
+
const settings = piSettings(context.cwd, agentDir);
|
|
661
|
+
const owner = ownerAppend(config, () => piAppendFiles(context.cwd, agentDir, settings.user));
|
|
662
|
+
if (owner.reason) status.principles = channel('partial', null, [owner.reason]);
|
|
663
|
+
else {
|
|
664
|
+
plan.engineArgs.push('--append-system-prompt', file('devlyn-instructions.md', joinText(owner.text, instructions)));
|
|
665
|
+
principles('append-system-prompt');
|
|
666
|
+
}
|
|
667
|
+
// A discovered or settings-named skill wins over a --skill of the same name.
|
|
668
|
+
const named = piNameCollisions(discoveryRoots(context, env, config), found);
|
|
669
|
+
const reasons = [...collisions, ...named.collisions, ...errors, ...named.errors, ...settings.errors];
|
|
670
|
+
if (reasons.length || [settings.user, settings.project].some((scope) => listed(scope, 'skills', 'packages'))) {
|
|
671
|
+
fallback(reasons, !owner.reason);
|
|
672
|
+
} else {
|
|
673
|
+
for (const name of SKILLS) plan.engineArgs.push('--skill', path.join(release.root, 'config/skills', name));
|
|
674
|
+
native('--skill', 'slash', '/skill:devlyn-ideate');
|
|
675
|
+
}
|
|
389
676
|
break;
|
|
390
677
|
}
|
|
391
678
|
case 'grok':
|
|
@@ -452,14 +739,73 @@ function planActivation(releaseRoot, input, env = process.env) {
|
|
|
452
739
|
} else fallback([...collisions, ...errors, ...kimi.errors]);
|
|
453
740
|
}
|
|
454
741
|
break;
|
|
455
|
-
case 'antigravity':
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
742
|
+
case 'antigravity': {
|
|
743
|
+
// Antigravity reads AGENTS.md (always on) and .agents/skills in every workspace folder, and a folder given
|
|
744
|
+
// by --add-dir is one. A conversation keeps its added folders and reads them from disk again on every
|
|
745
|
+
// launch, so the plan is the same in every launch mode and a caller hands a resumed conversation the same
|
|
746
|
+
// runDir: with another, both folders apply while the old one exists. A folder removed later only stops
|
|
747
|
+
// applying. It truncates a rule file over 24,000 bytes.
|
|
748
|
+
const gate = channelGate(context);
|
|
749
|
+
if (gate) {
|
|
750
|
+
firstTask(gate);
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
// The engine is given the resolved folder, the one the checks ran on.
|
|
754
|
+
const folder = path.join(physicalPath(context.runDir), 'antigravity');
|
|
755
|
+
const unsafe = unsafeRunDir(folder, true);
|
|
756
|
+
if (unsafe) {
|
|
757
|
+
firstTask(withheld({ detail: 'run-dir-unsafe', path: unsafe }));
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
const bytes = Buffer.byteLength(instructions), maximum = 24000;
|
|
761
|
+
if (bytes > maximum) {
|
|
762
|
+
status.principles = channel('unavailable', null, [reason('principles-unavailable', { detail: 'too-large', bytes, maximum })]);
|
|
763
|
+
} else {
|
|
764
|
+
file('antigravity/AGENTS.md', instructions);
|
|
765
|
+
principles('add-dir');
|
|
766
|
+
}
|
|
767
|
+
if (collisions.length) fallback([...collisions, ...errors], bytes <= maximum);
|
|
768
|
+
else {
|
|
769
|
+
for (const name of SKILLS) file(`antigravity/.agents/skills/${name}/SKILL.md`, antigravitySkill(release, name));
|
|
770
|
+
native('add-dir', 'slash', '/devlyn-ideate');
|
|
771
|
+
}
|
|
772
|
+
if (plan.files.length) plan.engineArgs.push('--add-dir', folder);
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
case 'cursor': {
|
|
776
|
+
const gate = channelGate(context);
|
|
777
|
+
if (gate) {
|
|
778
|
+
firstTask(gate);
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
// A --plugin-dir folder's sessionStart command hook runs when a session is created, and Cursor puts the
|
|
782
|
+
// additional_context it prints before the model as instructions, kept with the conversation (10,200
|
|
783
|
+
// characters were delivered whole). A resumed chat runs no hook and keeps what its first launch had
|
|
784
|
+
// (measured); a fork is treated the same way, because what it gets is not established. The hook is a
|
|
785
|
+
// command Cursor runs, so it is planned only in a safe run directory, by its resolved absolute path, quoted
|
|
786
|
+
// for the shell: no lookup through PATH or the working folder. The skills come from the release, whatever
|
|
787
|
+
// the run directory.
|
|
788
|
+
const hook = path.join(physicalPath(context.runDir), 'cursor');
|
|
789
|
+
const unsafe = unsafeRunDir(hook);
|
|
790
|
+
if (unsafe || !isFile('/bin/cat')) {
|
|
791
|
+
firstTask(withheld(unsafe ? { detail: 'run-dir-unsafe', path: unsafe } : { detail: 'platform-unexamined' }));
|
|
792
|
+
} else {
|
|
793
|
+
const command = `/bin/cat '${path.join(hook, 'context.json').replace(/'/g, "'\\''")}'`;
|
|
794
|
+
file('cursor/hooks/hooks.json', JSON.stringify({ version: 1, hooks: { sessionStart: [{ command }] } }) + '\n');
|
|
795
|
+
// Every non-ASCII character escaped, so no shell or code page can change the text.
|
|
796
|
+
file('cursor/context.json', JSON.stringify({ additional_context: instructions })
|
|
797
|
+
.replace(/[\u0080-\uffff]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`) + '\n');
|
|
798
|
+
plan.engineArgs.push('--plugin-dir', hook);
|
|
799
|
+
status.principles = continuation ? channel('partial', 'session-start-hook', [reason('principles-session-start-only')])
|
|
800
|
+
: channel('configured', 'session-start-hook');
|
|
801
|
+
}
|
|
802
|
+
if (collisions.length) fallback([...collisions, ...errors], plan.files.length > 0 || plan.prompt !== null);
|
|
803
|
+
else {
|
|
804
|
+
plan.engineArgs.push('--plugin-dir', path.join(release.root, 'config'));
|
|
805
|
+
native('plugin-dir', 'slash', '/devlyn-ideate');
|
|
806
|
+
}
|
|
462
807
|
break;
|
|
808
|
+
}
|
|
463
809
|
}
|
|
464
810
|
if (plan.engineArgs.length || Object.keys(plan.envPatch).length || plan.prompt) plan.envPatch.DEVLYN_HARNESS_ROOT = release.root;
|
|
465
811
|
return finish(plan);
|
|
@@ -473,4 +819,4 @@ function activationCommand(args) {
|
|
|
473
819
|
return planActivation(opts['release-root'], readContext(opts['context-file']));
|
|
474
820
|
}
|
|
475
821
|
|
|
476
|
-
module.exports = { planActivation, activationCommand, readContext, toml, skillCatalog };
|
|
822
|
+
module.exports = { planActivation, activationCommand, readContext, toml, skillCatalog, piFolders };
|
package/bin/harness-context.js
CHANGED
|
@@ -8,7 +8,7 @@ const path = require('path');
|
|
|
8
8
|
const crypto = require('crypto');
|
|
9
9
|
const readline = require('readline');
|
|
10
10
|
const { spawn, spawnSync } = require('child_process');
|
|
11
|
-
const {
|
|
11
|
+
const { physicalRoot, materializeRelease, fail } = require('./release');
|
|
12
12
|
|
|
13
13
|
const ENGINES = Object.freeze(['claude', 'codex', 'omp']);
|
|
14
14
|
const READ_MS = 6000;
|
|
@@ -209,13 +209,13 @@ function appState(home, common) {
|
|
|
209
209
|
return { defaultRelease: state.defaultRelease || null, choice };
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
// A release chosen by a record must
|
|
212
|
+
// A release chosen by a record must be the one recorded; its own planner verifies its files.
|
|
213
213
|
function recorded(root, version, digest, source) {
|
|
214
|
-
let
|
|
215
|
-
try {
|
|
216
|
-
catch (error) { fail('release-unavailable', { source, version, detail: error.reason?.code || error.
|
|
217
|
-
if (
|
|
218
|
-
return { release, source };
|
|
214
|
+
let manifest;
|
|
215
|
+
try { root = physicalRoot(root); manifest = JSON.parse(fs.readFileSync(path.join(root, 'release.json'), 'utf8')); }
|
|
216
|
+
catch (error) { fail('release-unavailable', { source, version, detail: error.reason?.code || error.code || 'unreadable' }, 3); }
|
|
217
|
+
if (manifest?.version !== version || manifest?.digest?.value !== digest) fail('release-version-conflict', { source, version }, 3);
|
|
218
|
+
return { release: { package: 'devlyn-cli', version, digest, root }, source };
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
// The release a terminal launch plans with: an unfinished queue's bound release in this repository,
|
|
@@ -494,6 +494,7 @@
|
|
|
494
494
|
"cc8bf51efb9ef8e5fa4134743f2d25434f4fd51595128edcfd2331dbf29d7dd5",
|
|
495
495
|
"ccacf091bab4102d7156631c6227120a661bb4ec0df514e14a59db44b7bd5dfd",
|
|
496
496
|
"ce1b0d9d0621fd057e684dc18b43156d30f589c72ed20189ea1232ea4f7ce6c6",
|
|
497
|
+
"ce8c1ea5c851fed75b898759fcf8359bd95ec04b30e547ed06bdf4101a2dddd8",
|
|
497
498
|
"cebacda3b11db7aa161d96b12edd6f3f790ee2850404cd42ecdf2912ed53de08",
|
|
498
499
|
"cefad7acb7dcd92777f888081aa6585b1418aa581366736fd44db68b1bd66dd0",
|
|
499
500
|
"cf43eb0460beebdfe1efbfb1a35a39e5fe450137252ea2c83e51d978faa1f17e",
|
|
@@ -1024,6 +1025,7 @@
|
|
|
1024
1025
|
"ce351bfa301b000f0786cac24959108a3db6cdf1cad3733cb07b394c62d35227",
|
|
1025
1026
|
"ce3cceeb74119953afde95da84dbe1bd6e3da3dbde5f85dd123a0fe8afec5d36",
|
|
1026
1027
|
"ce4b307083addc337a833547cb6998cb34a38d82e572587e9a57dc2cf46d275a",
|
|
1028
|
+
"ce8c1ea5c851fed75b898759fcf8359bd95ec04b30e547ed06bdf4101a2dddd8",
|
|
1027
1029
|
"ce9c73a3305e4ec9cd559cec6fdd10b8f4a9f3124b42ea4598340dc16a575505",
|
|
1028
1030
|
"ceddbf09792093c7417e72c7e2cf08e4780e9798dcd107292cdd095b023c2a8c",
|
|
1029
1031
|
"cf04e7c73e16d8e0e561e89d4a6519a1c47b9ffa08f88d1e5bda75ad2983f4df",
|
package/bin/skill-history.json
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"_shared": [
|
|
5
5
|
"015eea12cc07cfb9eb318c30855f6deacb5bf43bd508a511dffd2e48c9d82964",
|
|
6
6
|
"0544f4e90951233c6cbe99755fb5c95728d7429b8b4ce5dd6e2829dd0816524c",
|
|
7
|
+
"0575d3635d8ec6f72120309a93af17e1801c636d3cf2b426d2feec064230c500",
|
|
7
8
|
"05935881380d65ba34a964592e4587534fcea034b43b13673eff4108ed9389fd",
|
|
8
9
|
"06ab3a4b9e1c97d9b3dcd238148d775f1165f3fc08898af2f3b76149162b78d3",
|
|
9
10
|
"082124278a538e4ea8b6622d767f996e3d818ec85d6e0d8ddddafe9bb03118b2",
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
"135f09d5382b4d375204bba3ff6fb3f6fdc34a1ec89fe88542da02d12dda0f0d",
|
|
18
19
|
"1504c7a41e5f4a3206288d05e66fb48847222864f7825f05633883e2c562ec77",
|
|
19
20
|
"161cb4f1b27a9f81ae1e96af6ff596399c8936578a11d601108a89cbdeea241d",
|
|
21
|
+
"16811f6cf716d3931562be0da4dae13b6aa5a9a126029a8ccddb7c797d0cb652",
|
|
20
22
|
"196afa14871099a9afc4e484b00e8d8ccd928dba79db95a6a8776f58c8e78c39",
|
|
21
23
|
"1c1e5e44ff704b1b8791fabcad79a4158d8cd2f024569c1685ec3b185a98fbcc",
|
|
22
24
|
"20ac702b463f3eaa07fd6686d5209aed7993773504e35896fc732f7e653ec6cd",
|
|
@@ -72,6 +74,7 @@
|
|
|
72
74
|
"59f0ecc2c1fd9620e4cac06be370e0bf9965a35c20616047445dbc42da6a1677",
|
|
73
75
|
"5b8541a98c06de50443c2c3905c1b92ba143a972dfe618c4b296b799e0e1673c",
|
|
74
76
|
"5be2e643f96b6ce503ae8d631c67729f0c24b6c5d2b431c087161b520ccbe417",
|
|
77
|
+
"5d3d614f8a75ae7cf1b5e29036e963ce76cf817fe9bd86457a4f8ff026b0036f",
|
|
75
78
|
"60cdd1a8b220e629bcba3a24652323d16e3e6a15ba5b07c69ef2785a9c2eca70",
|
|
76
79
|
"61ec4c0435a2f0b307df8e88d81d9862c3abc1bf758648f2c18039581596659b",
|
|
77
80
|
"628a40e873d156e9179fd5a6bb3da9f910b6dde0886055804a506cdf8a631aaa",
|
|
@@ -121,6 +124,7 @@
|
|
|
121
124
|
"97e7ad89c74c609b15dfbf6f9f7ac61df8b50e97e4bc318f72922e058ecffba4",
|
|
122
125
|
"9815b6e6963e068872a6dbeae24068143bf92042bb035f67bafd1a224d65bcb0",
|
|
123
126
|
"998343c148618b937556b58b83749c05d5aa3283280d751c118da6a542fb87f2",
|
|
127
|
+
"9ca5956a73070555a93f19ecb1f92a8f57cec6fd1170a2809eeacc49cc0841be",
|
|
124
128
|
"9dd3ec225818efe0ad3726192db94ff129154e995617a9d3c150fc58ad0f9f2c",
|
|
125
129
|
"9e4d06fd466fa896040109bac0018fcfb2f1604d55abb4180ff1f1a0b665bbc8",
|
|
126
130
|
"9f14ca629e41730b1a9fb5d0ad1a39cff558fe34758d6ceccbb9de498f9f384e",
|
|
@@ -175,6 +179,7 @@
|
|
|
175
179
|
"e0a305cf3bf47c8bc357195d4d50b0f6177069e870c33040c258c93edae68646",
|
|
176
180
|
"e115205a744fef918a59d1b3a601df09a8b6df1d3e60613b9e8b87240929f8d8",
|
|
177
181
|
"e19204055bb16fff4263f95fac092ee8f971ee9055b7a969497e5714782a454c",
|
|
182
|
+
"e2a8157e0467cbe0c7cff583e34629bbd69c906830a1530a0e69a4cf4c448f2e",
|
|
178
183
|
"e41013abf5e17445ef83a86974ac1de77beaa61e484e1916577ecc64bb34c220",
|
|
179
184
|
"e725ec4eca42f737ee4bdaceb10c11db289414a1d4bb8be8fe103f89c89f07d5",
|
|
180
185
|
"e812d02ebdbdb8d82eb85842d676e21bedddb0d00154eed99b9d29b43cb8fc02",
|
|
@@ -193,6 +198,7 @@
|
|
|
193
198
|
"f9617e1034543f31ae2a3820301fbac6e4674e5d6fbc0d4b76751fe87b8e013e",
|
|
194
199
|
"fd68c7a74272929a14e685e447a51058ffcdd4c1d6bef83cbba18045c07aee55",
|
|
195
200
|
"fdb608b34a8600b4ce209d7f645ec5ad4475948be6ad95c941cc7ef02b5959df",
|
|
201
|
+
"fe357defe8ac6efd6bbd15b3bf35f79d79761c0f56d507488d9428536d4fb5f7",
|
|
196
202
|
"fe386122e502410394ab60643e79aa736ddb5030257c863f4a198e23b56b4cef",
|
|
197
203
|
"feaa255b2b6a7b0dc79877c4eb620c0f3cac88b50d2d1407a0a8a02ebc08ed49"
|
|
198
204
|
],
|
|
@@ -321,6 +327,7 @@
|
|
|
321
327
|
"89f0efaa2efdfddc887b494138247980d97856f6cabe6a8dcc585b3a28e5fa88",
|
|
322
328
|
"9bc8113592de34a7d559d8a5db6253e472357e947010ea2304701f1f7de3643d",
|
|
323
329
|
"ee723ff072721cae3f5e130d72515d44414337cffadb9615902b0d578912bda2",
|
|
330
|
+
"fc2710a0ed3eb0aeb7d095378bcbd1c77a5c46d0973bda7a49f6ec0a173adb2c",
|
|
324
331
|
"fed622aa1bcf9a3b437d42c39f6b35403c8cde657be0685b93bbe864e82ca8c4"
|
|
325
332
|
],
|
|
326
333
|
"devlyn-evaluate": [
|
|
@@ -348,6 +355,7 @@
|
|
|
348
355
|
"15f3eca3fad92b199814a00d42e4005a2838a40d800b9f755f7165d31773e7c9",
|
|
349
356
|
"19207315f23c9a12bf2ced0e4e79bb7dfdc7cc5d0c9dd94095d9c6dfe1e8fea8",
|
|
350
357
|
"1d8c3bd31c8b755acd0c3c9f416acb5024dfa99fbf5e2c14d8d6df00a24fe9b4",
|
|
358
|
+
"2e5b05df1af5891761a6460328d3a1b95aa6ead1b373b5147de6388f7cb98df2",
|
|
351
359
|
"300d1af60cc53285935dc4b5b370e157707218d844a4303b96d7826eef36ed43",
|
|
352
360
|
"302b1a6c109087dce02ed6af184d6aeeca6cecfd40ebe638beecf8bdf21cf59c",
|
|
353
361
|
"338a2b5ddde0f09444040c45922716663b1df76ec1a6c97ebdeb770b9e3ede49",
|
|
@@ -364,6 +372,7 @@
|
|
|
364
372
|
"6374290c74a8c17979e8c128905e106d38bd810c0451f0e9240c30e0f7ecabb0",
|
|
365
373
|
"65770e3f20c3a3afd2822e9acdcfc80b827f84a7737010b79332da70d431b05d",
|
|
366
374
|
"65c781764633b912e5947362eba0837c8027461a904975401d79ad4fe8714084",
|
|
375
|
+
"65ec310be43384375852e31ba3458313b01052668543849186a70715bfa81dcc",
|
|
367
376
|
"694c8920e50d92cba46dbb27eb44dd88035a89ed625d3cbf9f2bce9366d44c96",
|
|
368
377
|
"6b3143bcfe9c60a76f142d533177d85d36f856a163a226351317ed688738c05a",
|
|
369
378
|
"6b403afbf5a117cc08603d3648b9dded4f9ac4ca02fc1d8e1f0035615148bb5c",
|
|
@@ -390,6 +399,7 @@
|
|
|
390
399
|
"c8e375aab574148542f392b093cbbf3f2e8dc623826b45e35df0a1a2c3862226",
|
|
391
400
|
"ce282a55328c2d071cacc377670fbebea4da4b9a7fee81b25293fb26763f17c9",
|
|
392
401
|
"cf61a917a804503ef1e26fbb1cf498181e843a0758ec001f84edb7bcac837448",
|
|
402
|
+
"d622e92c7bf32d315a5aabe2554efbc9f62790ace19a088ead9a7cef195fc715",
|
|
393
403
|
"d6433bf0f96dd9dd485c3623885255738654674b3e23cfcc1a2cfd24f3cda96e",
|
|
394
404
|
"db8d921aabf0d04811b55465a3757c62c4b39631aebaf6eecc994ca241cfbe56",
|
|
395
405
|
"e25da746dc37b2e82c658a28d9481fd69741151045683eee2119582ddaf731cd",
|
|
@@ -812,7 +822,9 @@
|
|
|
812
822
|
"4.3.26",
|
|
813
823
|
"4.3.27",
|
|
814
824
|
"4.3.28",
|
|
825
|
+
"4.3.29",
|
|
815
826
|
"4.3.3",
|
|
827
|
+
"4.3.30",
|
|
816
828
|
"4.3.4",
|
|
817
829
|
"4.3.5",
|
|
818
830
|
"4.3.6",
|
|
@@ -29,7 +29,7 @@ check_binary() {
|
|
|
29
29
|
|
|
30
30
|
check_adapter() {
|
|
31
31
|
# $1 = target name
|
|
32
|
-
if [ -f "$ADAPTERS_DIR/$1.md" ]; then printf 'yes'; else printf 'no'; fi
|
|
32
|
+
if [ -f "$ADAPTERS_DIR/$1.adapter.md" ]; then printf 'yes'; else printf 'no'; fi
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
check_executor() {
|
|
@@ -47,7 +47,7 @@ for i in "${!TARGETS[@]}"; do
|
|
|
47
47
|
kind="${KINDS[$i]}"
|
|
48
48
|
binary="$(check_binary "${BINARIES[$i]}")"
|
|
49
49
|
adapter="$(check_adapter "$target")"
|
|
50
|
-
executor="$(check_executor "$ADAPTERS_DIR/$target.md")"
|
|
50
|
+
executor="$(check_executor "$ADAPTERS_DIR/$target.adapter.md")"
|
|
51
51
|
|
|
52
52
|
note='-'
|
|
53
53
|
case "$kind" in
|