devlyn-cli 4.3.25 → 4.3.28
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 +27 -27
- package/bin/devlyn.js +42 -94
- package/bin/harness-activation.js +4 -8
- package/bin/harness-context.js +395 -0
- package/bin/harness-run.js +148 -0
- package/bin/instruction-templates.json +2 -0
- package/bin/instructions.js +71 -159
- package/bin/skill-history.json +8 -0
- package/config/skills/_shared/runtime-principles.md +1 -1
- package/config/skills/_shared/storage-checkout.py +1 -0
- package/config/skills/_shared/storage-client.py +349 -14
- package/config/skills/_shared/task-complete.py +99 -24
- package/config/skills/_shared/task-completion.md +7 -3
- package/config/skills/devlyn-ideate/SKILL.md +2 -2
- package/config/skills/devlyn-ideate/references/loop.md +2 -2
- package/config/skills/devlyn-ideate/scripts/queue.py +10 -3
- 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/runtime-principles.md +1 -1
- package/native/claude/skills/_shared/storage-checkout.py +1 -0
- package/native/claude/skills/_shared/storage-client.py +349 -14
- package/native/claude/skills/_shared/task-complete.py +99 -24
- package/native/claude/skills/_shared/task-completion.md +7 -3
- package/native/claude/skills/devlyn-ideate/SKILL.md +2 -2
- package/native/claude/skills/devlyn-ideate/references/loop.md +2 -2
- package/native/claude/skills/devlyn-ideate/scripts/queue.py +10 -3
- 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/runtime-principles.md +1 -1
- package/native/codex/plugins/devlyn/skills/_shared/storage-checkout.py +1 -0
- package/native/codex/plugins/devlyn/skills/_shared/storage-client.py +349 -14
- package/native/codex/plugins/devlyn/skills/_shared/task-complete.py +99 -24
- package/native/codex/plugins/devlyn/skills/_shared/task-completion.md +7 -3
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/SKILL.md +2 -2
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/loop.md +2 -2
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/scripts/queue.py +10 -3
- package/package.json +2 -2
- package/release.json +51 -41
package/bin/instructions.js
CHANGED
|
@@ -8,6 +8,7 @@ const cleanupHistory = require('./cleanup-history.json');
|
|
|
8
8
|
const BEGIN = '<!-- devlyn:instructions:begin';
|
|
9
9
|
const END = '<!-- devlyn:instructions:end -->';
|
|
10
10
|
const LEGACY = /^devlyn-cli installs |^# Devlyn Agent Instructions\r?$|^This contract serves one goal: any capable engine|^Engine roles: orchestrator =/m;
|
|
11
|
+
const VOID_ELEMENT = /^(?:area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/i;
|
|
11
12
|
const normalize = (text) => text.replace(/\r\n/g, '\n');
|
|
12
13
|
const digest = (text) => crypto.createHash('sha256').update(text).digest('hex');
|
|
13
14
|
|
|
@@ -39,33 +40,15 @@ function matchedEnd(text, start, template) {
|
|
|
39
40
|
// A digest is ownership evidence; a familiar heading alone is not.
|
|
40
41
|
function legacyRanges(text, templates) {
|
|
41
42
|
const bom = text.startsWith('\uFEFF') ? 1 : 0;
|
|
42
|
-
const starts = [...new Set([0, ...(
|
|
43
|
+
const starts = [...new Set([0, ...(bom ? [1] : []),
|
|
43
44
|
...instructionParagraphs(text.slice(bom))
|
|
44
45
|
.filter((p) => p.level === 1 && /^# (?:Codex )?Project Instructions\r?\n/.test(p.text))
|
|
45
|
-
.map((p) => p.start + bom)])]
|
|
46
|
+
.map((p) => p.start + bom)])];
|
|
46
47
|
const ranges = [];
|
|
47
48
|
for (const start of starts) {
|
|
48
49
|
for (const template of templates) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (end === null && template.body) {
|
|
52
|
-
const prefix = text.slice(start, starts.find((offset) => offset > start));
|
|
53
|
-
const body = instructionParagraphs(prefix).find((p) => p.level === 2 && /^## North Star\r?\n/.test(p.text));
|
|
54
|
-
const header = template.preamble.slice(0, template.preamble.indexOf('\n\n') + 2);
|
|
55
|
-
if (body && normalize(prefix).startsWith(header)) {
|
|
56
|
-
const bodyStart = start + body.start;
|
|
57
|
-
end = matchedEnd(text, bodyStart, template.body);
|
|
58
|
-
// Remove only an exact original preamble; preserve an edited one in full.
|
|
59
|
-
const preambleEnd = matchedEnd(text, start, { length: template.preamble.length, sha256: digest(template.preamble) });
|
|
60
|
-
custom = text.slice(preambleEnd ?? start, bodyStart);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (end !== null) {
|
|
64
|
-
const existing = ranges.find((range) => range.start === start && range.end === end);
|
|
65
|
-
if (!existing) ranges.push({ start, end, custom });
|
|
66
|
-
// Releases may share a body. Prefer the exact owned preamble, if found.
|
|
67
|
-
else if (custom.length < existing.custom.length) existing.custom = custom;
|
|
68
|
-
}
|
|
50
|
+
const end = matchedEnd(text, start, template);
|
|
51
|
+
if (end !== null && !ranges.some((range) => range.start === start && range.end === end)) ranges.push({ start, end });
|
|
69
52
|
}
|
|
70
53
|
}
|
|
71
54
|
return ranges;
|
|
@@ -102,7 +85,7 @@ function instructionParagraphs(text) {
|
|
|
102
85
|
if (!comment) markup += line.slice(cursor);
|
|
103
86
|
const opening = /^ {0,3}<([a-z][\w:-]*)\b[^>]*>/i.exec(markup);
|
|
104
87
|
if (!html && opening && !/\/>$/.test(opening[0])
|
|
105
|
-
&&
|
|
88
|
+
&& !VOID_ELEMENT.test(opening[1])) {
|
|
106
89
|
html = { tag: opening[1].toLowerCase(), depth: 0 };
|
|
107
90
|
}
|
|
108
91
|
if (html) {
|
|
@@ -130,42 +113,14 @@ function instructionParagraphs(text) {
|
|
|
130
113
|
return paragraphs;
|
|
131
114
|
}
|
|
132
115
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
function importsAgentsMd(text) {
|
|
136
|
-
// Match the complete native path token before dropping its optional fragment.
|
|
137
|
-
const matchesImport = (value) => [...value.matchAll(/(?:^|\s)@((?:[^\s\\]|\\ )+)/g)].some((match) => {
|
|
138
|
-
const file = match[1].split('#', 1)[0];
|
|
139
|
-
return file === 'AGENTS.md' || file === './AGENTS.md';
|
|
140
|
-
});
|
|
141
|
-
const containsImport = (tokens) => tokens.some((token) => {
|
|
142
|
-
if (['code', 'codespan'].includes(token.type)) return false;
|
|
143
|
-
if (token.type === 'html') {
|
|
144
|
-
const raw = token.raw || '';
|
|
145
|
-
// Native Claude scans text left after complete comments in a comment-shaped HTML token.
|
|
146
|
-
return raw.trimStart().startsWith('<!--') && raw.includes('-->')
|
|
147
|
-
&& matchesImport(raw.replace(/<!--[\s\S]*?-->/g, ''));
|
|
148
|
-
}
|
|
149
|
-
if (token.tokens) return containsImport(token.tokens);
|
|
150
|
-
if (token.items) return containsImport(token.items);
|
|
151
|
-
return token.type === 'text' && matchesImport(token.text);
|
|
152
|
-
});
|
|
153
|
-
// Claude strips this bounded frontmatter prefix before expanding imports.
|
|
154
|
-
const body = text.replace(/^\uFEFF/, '').replace(/^---\s*\n([\s\S]*?)---\s*\n?/, '');
|
|
155
|
-
return containsImport(new Lexer({ gfm: false }).lex(body));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function customInstructions(text, name, template, managed = false) {
|
|
159
|
-
const known = new Set([
|
|
160
|
-
...(legacyTemplates.paragraphs[name] || []),
|
|
161
|
-
...instructionParagraphs(template).map((p) => p.key),
|
|
162
|
-
]);
|
|
116
|
+
function customInstructions(text, name) {
|
|
117
|
+
const known = new Set(legacyTemplates.paragraphs[name] || []);
|
|
163
118
|
const bom = text.startsWith('\uFEFF') ? '\uFEFF' : '';
|
|
164
119
|
const paragraphs = instructionParagraphs(text.slice(bom.length));
|
|
165
120
|
const keep = new Set();
|
|
166
|
-
let legacy =
|
|
121
|
+
let legacy = false;
|
|
167
122
|
for (const [index, paragraph] of paragraphs.entries()) {
|
|
168
|
-
if (
|
|
123
|
+
if (paragraph.level === 1) {
|
|
169
124
|
const next = paragraphs.findIndex((p, i) => i > index && p.level === 1);
|
|
170
125
|
const section = paragraphs.slice(index, next < 0 ? undefined : next);
|
|
171
126
|
legacy = /^# (?:Codex )?Project Instructions\r?\n|^# Devlyn Agent Instructions\r?\n/.test(paragraph.text)
|
|
@@ -196,9 +151,42 @@ function removeEvaluatorBlocks(text) {
|
|
|
196
151
|
}
|
|
197
152
|
|
|
198
153
|
// Devlyn defaults in an instruction file: a managed block, or stock paragraphs of a template
|
|
199
|
-
// that a release before managed blocks copied in whole
|
|
154
|
+
// that a release before managed blocks copied in whole.
|
|
200
155
|
function holdsDevlynDefaults(name, text) {
|
|
201
|
-
return text.includes(BEGIN) || customInstructions(text, name
|
|
156
|
+
return text.includes(BEGIN) || customInstructions(text, name) !== text;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// An example quotes a block: Markdown reads its marker as part of something else, or an element
|
|
160
|
+
// opened before it is still open. An owner's stray tag can only keep a block, never lose text.
|
|
161
|
+
function quoted(text) {
|
|
162
|
+
const open = new Map();
|
|
163
|
+
for (const token of new Lexer({ gfm: false }).lex(normalize(text.replace(/^\uFEFF/, '')))) {
|
|
164
|
+
if (token.type !== 'html') continue;
|
|
165
|
+
if (token.raw.startsWith(BEGIN)) return [...open.values()].some((depth) => depth > 0);
|
|
166
|
+
for (const [tag, close, element] of token.raw.replace(/<!--[\s\S]*?-->/g, '').matchAll(/<(\/?)([a-z][\w:-]*)\b[^>]*>/gi)) {
|
|
167
|
+
if (tag.endsWith('/>') || VOID_ELEMENT.test(element)) continue;
|
|
168
|
+
const name = element.toLowerCase();
|
|
169
|
+
open.set(name, (open.get(name) || 0) + (close ? -1 : 1));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// The range a release provably wrote, or null: one block whose bytes still match its own digest
|
|
176
|
+
// and that no example quotes, or one whole template without markers. Anything else is the owner's.
|
|
177
|
+
function devlynCopy(name, text, template) {
|
|
178
|
+
if (text.includes(BEGIN) || text.includes(END)) {
|
|
179
|
+
const starts = [...text.matchAll(/^(\uFEFF?)<!-- devlyn:instructions:begin sha256=([a-f0-9]{64}) -->\r?\n/gm)];
|
|
180
|
+
const ends = [...text.matchAll(/^<!-- devlyn:instructions:end -->(?:\r?\n|$)/gm)];
|
|
181
|
+
if (starts.length !== 1 || ends.length !== 1 || text.split(BEGIN).length !== 2 || text.split(END).length !== 2) return null;
|
|
182
|
+
const [start] = starts, [end] = ends;
|
|
183
|
+
const body = start.index + start[0].length;
|
|
184
|
+
if (end.index < body || digest(normalize(text.slice(body, end.index))) !== start[2] || quoted(text)) return null;
|
|
185
|
+
return { start: start.index + start[1].length, end: end.index + end[0].length };
|
|
186
|
+
}
|
|
187
|
+
const normalized = normalize(template);
|
|
188
|
+
const ranges = legacyRanges(text, [{ length: normalized.length, sha256: digest(normalized) }, ...(legacyTemplates[name] || [])]);
|
|
189
|
+
return ranges.length === 1 && !LEGACY.test(text.slice(0, ranges[0].start) + text.slice(ranges[0].end)) ? ranges[0] : null;
|
|
202
190
|
}
|
|
203
191
|
|
|
204
192
|
function retainFile(file, bytes) {
|
|
@@ -220,129 +208,53 @@ function retainFile(file, bytes) {
|
|
|
220
208
|
}
|
|
221
209
|
}
|
|
222
210
|
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
function
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
&& JSON.parse(fs.readFileSync(projectPackage, 'utf8'))?.name === 'devlyn-cli') {
|
|
229
|
-
console.log(` → Skipping ${name}; shipped instruction template in devlyn-cli source checkout`);
|
|
230
|
-
return false;
|
|
231
|
-
}
|
|
232
|
-
const templateName = path.basename(name);
|
|
233
|
-
const templatePath = path.join(__dirname, '..', templateName);
|
|
234
|
-
const template = ['CLAUDE.md', 'AGENTS.md'].includes(templateName) ? fs.readFileSync(templatePath, 'utf8') : '';
|
|
211
|
+
// Removes the devlyn defaults the file holds, keeping everything else. `write` false writes nothing and returns
|
|
212
|
+
// whether the file needs the removal. `evaluatorOnly` removes only appended evaluator blocks.
|
|
213
|
+
function removeInstructions(name, write = true, directory = process.cwd()) {
|
|
214
|
+
const templatePath = path.join(__dirname, '..', name);
|
|
215
|
+
const template = ['CLAUDE.md', 'AGENTS.md'].includes(name) ? fs.readFileSync(templatePath, 'utf8') : '';
|
|
235
216
|
if (template.includes(BEGIN) || template.includes(END)) {
|
|
236
217
|
throw new Error(`Packaged instruction template contains managed markers: ${templatePath}`);
|
|
237
218
|
}
|
|
238
219
|
const dest = path.join(directory, name);
|
|
239
220
|
const stat = fs.lstatSync(dest, { throwIfNoEntry: false });
|
|
221
|
+
if (!stat) return false;
|
|
240
222
|
const sourceStat = template ? fs.statSync(templatePath) : null;
|
|
241
|
-
if (stat
|
|
242
|
-
console.log(` → Using source ${name}; packaged template left unchanged`);
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
245
|
-
const exists = Boolean(stat);
|
|
223
|
+
if (stat.isFile() && stat.dev === sourceStat?.dev && stat.ino === sourceStat?.ino) return false; // The shipped template itself.
|
|
246
224
|
// Do not replace a shared instruction symlink or follow it outside the project.
|
|
247
|
-
if (
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const before = exists ? fs.readFileSync(dest) : Buffer.alloc(0);
|
|
251
|
-
let current = initial;
|
|
225
|
+
if (!stat.isFile()) throw new InstructionError(`Instruction file must be a regular file; preserved: ${dest}.`);
|
|
226
|
+
const before = fs.readFileSync(dest);
|
|
227
|
+
let current;
|
|
252
228
|
try {
|
|
253
|
-
|
|
229
|
+
current = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(before);
|
|
254
230
|
} catch (cause) {
|
|
255
|
-
throw new InstructionError(`${name} must be UTF-8; original preserved
|
|
256
|
-
}
|
|
257
|
-
current = removeEvaluatorBlocks(current);
|
|
258
|
-
const eol = current.match(/\r?\n/)?.[0] || '\n';
|
|
259
|
-
const block = managedBlock(template, eol);
|
|
260
|
-
const inserted = withBlock ? block : '';
|
|
261
|
-
const recovery = path.join(directory, '.devlyn', 'instructions');
|
|
262
|
-
const conflict = (reason) => {
|
|
263
|
-
if (!write) return true;
|
|
264
|
-
const incoming = path.join(recovery, `${encodeURIComponent(name)}.${digest(block)}.incoming`);
|
|
265
|
-
const backup = path.join(recovery, `${encodeURIComponent(name)}.${digest(before)}.backup`);
|
|
266
|
-
const guide = path.join(recovery, `${encodeURIComponent(name)}.${digest(before)}.${digest(block)}.merge.md`);
|
|
267
|
-
retainFile(backup, before);
|
|
268
|
-
retainFile(incoming, Buffer.from(block));
|
|
269
|
-
retainFile(guide, Buffer.from(`# Merge ${name}\n\n`
|
|
270
|
-
+ `Installation stopped: ${reason}. The original file is unchanged.\n\n`
|
|
271
|
-
+ `- Backup before this install attempt: ${backup}\n- New Devlyn defaults: ${incoming}\n- File to update: ${dest}\n\n`
|
|
272
|
-
+ `1. Compare the backup and new defaults side by side. Identify all project-specific additions and edits; do not discard them.\n`
|
|
273
|
-
+ `2. Update ${name} with exactly one complete managed block from the new defaults. Keep project-specific rules outside that block; they take precedence. Resolve conflicting rules explicitly. Do not copy the new defaults over the whole file.\n`
|
|
274
|
-
+ `3. Review the result, then rerun the same install command (for example, npx devlyn-cli). Other selected agents may still need installation.\n\n`
|
|
275
|
-
+ `To undo this manual merge, restore that backup to ${dest}. Earlier backups are retained too.\n`));
|
|
276
|
-
throw new InstructionError(`${name} needs merge (${reason}).\n`
|
|
277
|
-
+ `Original preserved: ${dest}\nBackup: ${backup}\nNew defaults: ${incoming}\n`
|
|
278
|
-
+ `Merge instructions: ${guide}\n`
|
|
279
|
-
+ `Keep custom rules outside the managed block, then rerun the same install command.\n`
|
|
280
|
-
+ `Installation is incomplete; earlier selected agents may already have been updated.`);
|
|
281
|
-
};
|
|
282
|
-
let content;
|
|
283
|
-
if (evaluatorOnly) {
|
|
284
|
-
content = current;
|
|
285
|
-
} else if (current.includes(BEGIN) || current.includes(END)) {
|
|
286
|
-
const starts = [...current.matchAll(/^\uFEFF?<!-- devlyn:instructions:begin sha256=([a-f0-9]{64}) -->\r?\n/gm)];
|
|
287
|
-
const ends = [...current.matchAll(/^<!-- devlyn:instructions:end -->(?:\r?\n|$)/gm)];
|
|
288
|
-
if (starts.length !== 1 || ends.length !== 1
|
|
289
|
-
|| current.split(BEGIN).length !== 2 || current.split(END).length !== 2) {
|
|
290
|
-
return conflict('ambiguous managed markers');
|
|
291
|
-
}
|
|
292
|
-
const start = starts[0];
|
|
293
|
-
const end = ends[0];
|
|
294
|
-
const bodyStart = start.index + start[0].length;
|
|
295
|
-
if (end.index < bodyStart) return conflict('reversed managed markers');
|
|
296
|
-
const previous = current.slice(bodyStart, end.index);
|
|
297
|
-
const custom = digest(normalize(previous)) === start[1] ? ''
|
|
298
|
-
: customInstructions(previous, templateName,
|
|
299
|
-
'Project-specific instructions outside this managed block take precedence over these defaults.\n\n' + template, true);
|
|
300
|
-
const after = current.slice(end.index + end[0].length);
|
|
301
|
-
content = current.slice(0, start.index + (start[0].startsWith('\uFEFF') ? 1 : 0))
|
|
302
|
-
+ custom + (custom ? eol : '') + inserted + after;
|
|
303
|
-
} else {
|
|
304
|
-
const normalized = normalize(template);
|
|
305
|
-
const bodyStart = normalized.indexOf('## North Star\n');
|
|
306
|
-
const preamble = normalized.slice(0, bodyStart);
|
|
307
|
-
const body = normalized.slice(bodyStart);
|
|
308
|
-
const ranges = legacyRanges(current, [
|
|
309
|
-
...(normalized ? [{ length: normalized.length, sha256: digest(normalized),
|
|
310
|
-
...(bodyStart > 0 ? { preamble, body: { length: body.length, sha256: digest(body) } } : {}) }] : []),
|
|
311
|
-
...(legacyTemplates[templateName] || []),
|
|
312
|
-
]);
|
|
313
|
-
if (ranges.length === 1 && !LEGACY.test(current.slice(0, ranges[0].start)
|
|
314
|
-
+ ranges[0].custom + current.slice(ranges[0].end))) {
|
|
315
|
-
const { start, end, custom } = ranges[0];
|
|
316
|
-
content = current.slice(0, start) + custom + inserted + current.slice(end);
|
|
317
|
-
} else {
|
|
318
|
-
const custom = customInstructions(current, templateName, template);
|
|
319
|
-
content = custom + (custom && inserted ? eol + eol : '') + inserted;
|
|
320
|
-
}
|
|
231
|
+
throw new InstructionError(`${name} must be UTF-8; original preserved.`, { cause });
|
|
321
232
|
}
|
|
233
|
+
let content = removeEvaluatorBlocks(current);
|
|
234
|
+
const copy = template ? devlynCopy(name, content, template) : null;
|
|
235
|
+
if (copy) content = content.slice(0, copy.start) + content.slice(copy.end);
|
|
322
236
|
const bytes = Buffer.from(content);
|
|
323
237
|
if (bytes.equals(before)) return false;
|
|
324
238
|
if (!write) return true;
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
239
|
+
const backup = path.join(directory, '.devlyn', 'instructions', `${encodeURIComponent(name)}.${digest(before)}.backup`);
|
|
240
|
+
retainFile(backup, before);
|
|
241
|
+
console.log(` → Saved previous ${name}: ${backup}`);
|
|
242
|
+
const empty = content.replace(/^\uFEFF/, '') === '';
|
|
330
243
|
const temp = `${dest}.${crypto.randomUUID()}.tmp`;
|
|
331
244
|
try {
|
|
332
|
-
fs.writeFileSync(temp, bytes, { flag: 'wx', mode:
|
|
333
|
-
|
|
334
|
-
if (fs.existsSync(dest)
|
|
335
|
-
throw new InstructionError(`${name} changed during
|
|
245
|
+
fs.writeFileSync(temp, bytes, { flag: 'wx', mode: stat.mode & 0o777 });
|
|
246
|
+
fs.chmodSync(temp, stat.mode & 0o777);
|
|
247
|
+
if (!fs.existsSync(dest) || !fs.readFileSync(dest).equals(before)) {
|
|
248
|
+
throw new InstructionError(`${name} changed during removal; preserved it.`);
|
|
336
249
|
}
|
|
337
|
-
if (
|
|
250
|
+
if (empty) fs.unlinkSync(dest);
|
|
338
251
|
else fs.renameSync(temp, dest);
|
|
339
252
|
} finally {
|
|
340
253
|
if (fs.existsSync(temp)) fs.unlinkSync(temp);
|
|
341
254
|
}
|
|
342
|
-
console.log(
|
|
343
|
-
: ` → ${withBlock ? 'Updated Devlyn defaults in' : 'Removed Devlyn defaults from'} ${name}; project-specific instructions preserved`);
|
|
255
|
+
console.log(empty ? ` ✕ ${name} (removed)` : ` → Removed Devlyn defaults from ${name}; project-specific instructions preserved`);
|
|
344
256
|
return true;
|
|
345
257
|
}
|
|
346
258
|
|
|
347
|
-
module.exports = {
|
|
259
|
+
module.exports = { removeInstructions, InstructionError, instructionParagraphs, holdsDevlynDefaults,
|
|
348
260
|
managedBody, managedBlock, sessionDefaults };
|
package/bin/skill-history.json
CHANGED
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
"6d1a5458b23196b6f6fde6b7e9dc097ed173cb5914de439e512a032ca1892812",
|
|
91
91
|
"6e70044cab5a3fb1f78b7148c2111e4298ce8f5f8e902220430ca316bb61255d",
|
|
92
92
|
"6fa2b5e089dcd00613c6e9800d2e763da9e5b2c80e6a7ef5d54d5ad7113e8399",
|
|
93
|
+
"701c6a010a526429642123a2be1a56d724cd47b7e5000f78c5bde7d66c3b133b",
|
|
93
94
|
"713921f9a442770fc4de24b990dee6a4a5358a0d69bc26af80c33dc6f15a7274",
|
|
94
95
|
"71fb125e20420d6f3fcd3396093fcdc8d552bab9626c3cbd962351e0d19adac8",
|
|
95
96
|
"725395f70eec7e2ceae1f3aa122893433923b098a1e0d6202dd410979ef4eea0",
|
|
@@ -160,11 +161,13 @@
|
|
|
160
161
|
"d201802016f06626bd9fa4074d61a54ab9f70b7febfccc0ea0c70605d1f1b811",
|
|
161
162
|
"d2273f7404df92436c4c2ebcf38bdf1e5dbc65c5d1544f95dd1332c123ef17df",
|
|
162
163
|
"d22bbe15dff66d4801320ea81d7952af50615a7ed57ffe7033545589b8d946bd",
|
|
164
|
+
"d243bff7bb0a780d933dc66ed5a899d0d4e2a00e9dcdde66beeb518247c859a6",
|
|
163
165
|
"d3b40b9f0e15bf90ca557c0198a8fe5ff1d046b9aaa54b181c743d8e006f9c34",
|
|
164
166
|
"d424feceffc005f220e33a19cac73f33bd40d120cc14da33725b8a990704bcf6",
|
|
165
167
|
"d7388e36d76647a89ce52de615271928cf9fc78f980df399d2bc2e6d6675ec82",
|
|
166
168
|
"d85fa24b9b2c439742c9a62d5d4e07a031a1cfe6024bbc06f98eff7063424179",
|
|
167
169
|
"dac3da11ea5686382f5c9892a0b8e4ec25e625e5946283d8e2ad944aa84e1df2",
|
|
170
|
+
"db2a57a81fc9ffddb7f7f2b7a59ac4821563c345e2a47f825eade308f2707040",
|
|
168
171
|
"db867d1fd98058bb20e41e942abd95f08fbaa9465928025f69f0a0308fa71514",
|
|
169
172
|
"dd0bfdfd2db0b5f9ddef75d1b9ce3247698739fc551d292366dccd506fef6a36",
|
|
170
173
|
"ddb0c8dac58e95298330ab54e4d1fe4a108c968d9af713f267b4f8f4c6808507",
|
|
@@ -358,6 +361,7 @@
|
|
|
358
361
|
"5824f07e455fe1c3942bf595a12e935258d4c0a851ca1bf741db915603d1c4e0",
|
|
359
362
|
"59261610d60ca0f94cc8fa33af61f8ddafd8e5616f9358591547dd23fcdc6328",
|
|
360
363
|
"5954e371659ae1010757f0eec4b80dd65a899f9fcf76d4d84d9a75476a963218",
|
|
364
|
+
"6374290c74a8c17979e8c128905e106d38bd810c0451f0e9240c30e0f7ecabb0",
|
|
361
365
|
"65770e3f20c3a3afd2822e9acdcfc80b827f84a7737010b79332da70d431b05d",
|
|
362
366
|
"65c781764633b912e5947362eba0837c8027461a904975401d79ad4fe8714084",
|
|
363
367
|
"694c8920e50d92cba46dbb27eb44dd88035a89ed625d3cbf9f2bce9366d44c96",
|
|
@@ -365,6 +369,7 @@
|
|
|
365
369
|
"6b403afbf5a117cc08603d3648b9dded4f9ac4ca02fc1d8e1f0035615148bb5c",
|
|
366
370
|
"6c3e2e4b84b0b4f81aed3759704da7c14f3cbabf3beeb0d1654feda3a15a8675",
|
|
367
371
|
"6d9df58c117165e9d297aa6be0df8d076bac1ed49dc29ae93906af1969798ab1",
|
|
372
|
+
"6e2b54571bfbc1fab571b81564fb0625ed83333fc12277c473fdf5a231f7d40c",
|
|
368
373
|
"6e7228610628fc4bca31dc0b0aaf5eb4445e1f123f2c54bd13921c5cd01129fe",
|
|
369
374
|
"75cecbce4d1f4adbba608cef498ae7e421ea75512c6bf9282b66cd94d821f672",
|
|
370
375
|
"7f451e48e391643b34f6249244bc47d93d6303875e7b092771e3af0718343bc1",
|
|
@@ -804,6 +809,9 @@
|
|
|
804
809
|
"4.3.23",
|
|
805
810
|
"4.3.24",
|
|
806
811
|
"4.3.25",
|
|
812
|
+
"4.3.26",
|
|
813
|
+
"4.3.27",
|
|
814
|
+
"4.3.28",
|
|
807
815
|
"4.3.3",
|
|
808
816
|
"4.3.4",
|
|
809
817
|
"4.3.5",
|
|
@@ -17,7 +17,7 @@ Seven rules govern every change. Cite them by name when a decision touches one.
|
|
|
17
17
|
3. **No guesswork** — verify with the actual files, logs, diffs, and run output before forming conclusions. State the falsifiable prediction BEFORE the experiment; record raw results AFTER. Retroactive prediction edits are dishonest.
|
|
18
18
|
4. **Worldclass** — zero CRITICAL, zero HIGH security/design findings on the shippable path. Any review is one pass in which every reviewer, including each one the user requires, reads the source the checks ran on; fix each finding or reject it with a reason, re-check the fixes and the invariants they touch, and repeat the full review only while a CRITICAL or HIGH finding remains.
|
|
19
19
|
5. **Best practice** — idiomatic for the language and framework. Use standard primitives; do not hand-roll what the library already provides.
|
|
20
|
-
6. **Optimized** —
|
|
20
|
+
6. **Optimized** — efficient on the resource that matters (the user's time, tokens, attention, runtime) without weakening correctness or requirements. An extra check or process step must beat the simpler path.
|
|
21
21
|
7. **Production ready** — error states are explicit and visible; behavior under failure is what the user expects, not silent corruption.
|
|
22
22
|
|
|
23
23
|
Three discipline rules govern HOW the principles are applied:
|
|
@@ -52,6 +52,7 @@ def validate_closure(value):
|
|
|
52
52
|
require(type(value) is dict, 'positive reservation closure missing')
|
|
53
53
|
tag = value.get('established_by')
|
|
54
54
|
shapes = {'released': {'established_by': ('released',), 'reservation': 'reservation'},
|
|
55
|
+
'authority_migrated': {'established_by': ('authority_migrated',), 'operation_id': 'str'},
|
|
55
56
|
'authority_expired': {'established_by': ('authority_expired',), 'operation_id': 'str'},
|
|
56
57
|
'never_admitted': {'established_by': ('never_admitted',), 'operation_id': 'str'},
|
|
57
58
|
'legacy_unreserved': {'established_by': ('legacy_unreserved',), 'receipt_version': 'u32'}}
|