devlyn-cli 4.3.29 → 4.3.31

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.
Files changed (33) hide show
  1. package/AGENTS.md +2 -2
  2. package/CLAUDE.md +2 -2
  3. package/bin/engines.js +7 -9
  4. package/bin/harness-activation.js +166 -17
  5. package/bin/harness-context.js +7 -7
  6. package/bin/instruction-templates.json +4 -0
  7. package/bin/skill-history.json +8 -1
  8. package/config/skills/_shared/engine-preflight.md +0 -2
  9. package/config/skills/_shared/runtime-principles.md +2 -2
  10. package/config/skills/_shared/task-completion.md +3 -15
  11. package/config/skills/devlyn-ideate/references/executor.md +1 -1
  12. package/config/skills/devlyn-ideate/references/loop.md +2 -4
  13. package/config/skills/devlyn-ideate/references/package-format.md +0 -1
  14. package/harness/AGENTS.md +3 -3
  15. package/harness/CLAUDE.md +3 -3
  16. package/native/claude/.claude-plugin/plugin.json +1 -1
  17. package/native/claude/skills/_shared/engine-preflight.md +0 -2
  18. package/native/claude/skills/_shared/runtime-principles.md +2 -2
  19. package/native/claude/skills/_shared/task-completion.md +3 -15
  20. package/native/claude/skills/devlyn-ideate/references/executor.md +1 -1
  21. package/native/claude/skills/devlyn-ideate/references/loop.md +2 -4
  22. package/native/claude/skills/devlyn-ideate/references/package-format.md +0 -1
  23. package/native/codex/.agents/plugins/marketplace.json +1 -1
  24. package/native/codex/plugins/devlyn/.codex-plugin/plugin.json +1 -1
  25. package/native/codex/plugins/devlyn/skills/_shared/engine-preflight.md +0 -2
  26. package/native/codex/plugins/devlyn/skills/_shared/runtime-principles.md +2 -2
  27. package/native/codex/plugins/devlyn/skills/_shared/task-completion.md +3 -15
  28. package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/executor.md +1 -1
  29. package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/loop.md +2 -4
  30. package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/package-format.md +0 -1
  31. package/optional-skills/code-review-standards/SKILL.md +10 -6
  32. package/package.json +1 -1
  33. package/release.json +35 -35
package/AGENTS.md CHANGED
@@ -13,7 +13,7 @@ Seven rules govern every change. Cite them by name when a decision touches one.
13
13
  1. **No workaround** — fix the root cause, never the symptom. No `any`, no `@ts-ignore`, no silent `catch`, no hardcoded fallback that hides a broken contract. No config bypasses.
14
14
  2. **No overengineering** — smallest sufficient change, one responsibility per module. New abstractions need an observed failure. Subtractive-first: ask "what can I delete instead?" before writing anything new. Remove code your change makes unused; only report unrelated pre-existing dead code.
15
15
  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.
16
- 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.
16
+ 4. **Worldclass** — zero CRITICAL, zero HIGH findings on the shippable path; each must name a concrete case of wrong behavior or results, a security hole, or lost or corrupted data. A review is one pass on the source the checks ran on, by the reviewers the user requires, else by one reviewer who has not seen the author's reasoning. Fix each finding or reject it with a reason, and verify every edit and the invariants it touches. Only a CRITICAL or HIGH calls for another reviewer pass, which reads the fixes, the rejections and the invariants they touch. If one survives that pass, or its fix caused a new one, repair once more; if the next pass finds either again, stop: the work does not ship and is reported blocked.
17
17
  5. **Best practice** — idiomatic for the language and framework. Use standard primitives; do not hand-roll what the library already provides.
18
18
  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.
19
19
  7. **Production ready** — error states are explicit and visible; behavior under failure is what the user expects, not silent corruption.
@@ -27,7 +27,7 @@ Three discipline rules govern HOW the principles are applied:
27
27
  ## Quick Start
28
28
 
29
29
  - `devlyn-ideate` — loop designer and intent queue: `plan <intent or document>` writes a validated loop package, `add` queues its tasks, `status` (or no arguments) reports the queue, `drain` executes it serially.
30
- - Delivery — direct work edits the current checkout; deliver completed, verified requests with changes by project policy (default PR and merge), following `_shared/task-completion.md` in `.claude/skills/` or `.agents/skills/` (project, else `~`). With known concurrent writers, isolate before editing; on unexplained changes, pause writes until safe or isolated. An ideate drain delivers every task. Explicit limits win; commit alone stays local.
30
+ - Delivery — direct work edits the current checkout; deliver completed, verified requests with changes by project policy (default PR and merge), following `_shared/task-completion.md`. With known concurrent writers, isolate before editing; on unexplained changes, pause writes until safe or isolated. An ideate drain delivers every task. Explicit limits win; commit alone stays local.
31
31
  - Executor — `devlyn-engines` shows it and pins it in `.devlyn/engines.json`. A pinned executor does the implementation work, direct or drained: when it is not you, delegate to it, and when it is unavailable stop with `BLOCKED:<engine>-unavailable`. Without a pin, you are the executor.
32
32
 
33
33
  Each skill's `SKILL.md` is the source of truth for its flags and workflow.
package/CLAUDE.md CHANGED
@@ -13,7 +13,7 @@ Seven rules govern every change. Cite them by name when a decision touches one.
13
13
  1. **No workaround** — fix the root cause, never the symptom. No `any`, no `@ts-ignore`, no silent `catch`, no hardcoded fallback that hides a broken contract. No config bypasses.
14
14
  2. **No overengineering** — smallest sufficient change, one responsibility per module. New abstractions need an observed failure. Subtractive-first: ask "what can I delete instead?" before writing anything new. Remove code your change makes unused; only report unrelated pre-existing dead code.
15
15
  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.
16
- 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.
16
+ 4. **Worldclass** — zero CRITICAL, zero HIGH findings on the shippable path; each must name a concrete case of wrong behavior or results, a security hole, or lost or corrupted data. A review is one pass on the source the checks ran on, by the reviewers the user requires, else by one reviewer who has not seen the author's reasoning. Fix each finding or reject it with a reason, and verify every edit and the invariants it touches. Only a CRITICAL or HIGH calls for another reviewer pass, which reads the fixes, the rejections and the invariants they touch. If one survives that pass, or its fix caused a new one, repair once more; if the next pass finds either again, stop: the work does not ship and is reported blocked.
17
17
  5. **Best practice** — idiomatic for the language and framework. Use standard primitives; do not hand-roll what the library already provides.
18
18
  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.
19
19
  7. **Production ready** — error states are explicit and visible; behavior under failure is what the user expects, not silent corruption.
@@ -27,7 +27,7 @@ Three discipline rules govern HOW the principles are applied:
27
27
  ## Quick Start
28
28
 
29
29
  - `devlyn-ideate` — loop designer and intent queue: `plan <intent or document>` writes a validated loop package, `add` queues its tasks, `status` (or no arguments) reports the queue, `drain` executes it serially.
30
- - Delivery — direct work edits the current checkout; deliver completed, verified requests with changes by project policy (default PR and merge), following `_shared/task-completion.md` in `.claude/skills/` or `.agents/skills/` (project, else `~`). With known concurrent writers, isolate before editing; on unexplained changes, pause writes until safe or isolated. An ideate drain delivers every task. Explicit limits win; commit alone stays local.
30
+ - Delivery — direct work edits the current checkout; deliver completed, verified requests with changes by project policy (default PR and merge), following `_shared/task-completion.md`. With known concurrent writers, isolate before editing; on unexplained changes, pause writes until safe or isolated. An ideate drain delivers every task. Explicit limits win; commit alone stays local.
31
31
  - Executor — `devlyn-engines` shows it and pins it in `.devlyn/engines.json`. A pinned executor does the implementation work, direct or drained: when it is not you, delegate to it, and when it is unavailable stop with `BLOCKED:<engine>-unavailable`. Without a pin, you are the executor.
32
32
 
33
33
  Each skill's `SKILL.md` is the source of truth for its flags and workflow.
package/bin/engines.js CHANGED
@@ -1,10 +1,7 @@
1
1
  // Engine IDs in the order of devlyn-os-v1/packages/contracts/src/engines.ts. An ID joins only once its
2
- // channels are confirmed on the real CLI, with that examined version as its floor below; the planner
2
+ // channels are confirmed on the real CLI, with that examined version as its floor; the planner
3
3
  // refuses every other ID. CLI binary names (for example agy) are not engine IDs.
4
- const ENGINE_IDS = Object.freeze([
5
- 'claude', 'codex', 'grok', 'omp', 'antigravity', 'cursor', 'opencode', 'kimi', 'copilot', 'qwen', 'pi',
6
- ]);
7
-
4
+ //
8
5
  // The versions each channel was examined on. Any newer release, a new major included, is taken as
9
6
  // compatible: a version number is not evidence that a channel broke (omp 18 kept both flags devlyn
10
7
  // uses), and refusing unexamined majors turned working engines off. Only a version below the examined
@@ -23,18 +20,19 @@ const ENGINE_CAPABILITIES = Object.freeze({
23
20
  qwen: { minimum: '0.26.0', executor: false },
24
21
  pi: { minimum: '1.4.0', executor: false },
25
22
  });
23
+ const ENGINE_IDS = Object.freeze(Object.keys(ENGINE_CAPABILITIES));
26
24
 
27
25
  function readableVersion(engine, version) {
28
26
  return typeof version === 'string'
29
27
  && (engine === 'cursor' ? /^\d{4}\.\d{2}\.\d{2}-[0-9a-f]+$/ : /^\d+\.\d+\.\d+$/).test(version);
30
28
  }
31
29
 
32
- function supportedVersion(engine, version) {
30
+ // Whether `version` is at least `minimum`, the engine's floor unless a channel names a later examined one.
31
+ function supportedVersion(engine, version, minimum = ENGINE_CAPABILITIES[engine].minimum) {
33
32
  if (!readableVersion(engine, version)) return false;
34
- const capability = ENGINE_CAPABILITIES[engine];
35
- if (engine === 'cursor') return version.slice(0, 10).replace(/\./g, '') >= capability.minimum.slice(0, 10).replace(/\./g, '');
33
+ if (engine === 'cursor') return version.slice(0, 10).replace(/\./g, '') >= minimum.slice(0, 10).replace(/\./g, '');
36
34
  const actual = version.split('.').map(Number);
37
- const floor = capability.minimum.split('.').map(Number);
35
+ const floor = minimum.split('.').map(Number);
38
36
  for (let i = 0; i < 3; i++) {
39
37
  if (actual[i] !== floor[i]) return actual[i] > floor[i];
40
38
  }
@@ -8,6 +8,10 @@ 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);
@@ -139,6 +143,10 @@ function discoveryRoots(context, env, config) {
139
143
  const roots = [...(context.skillRoots || [])];
140
144
  const add = (base, ...parts) => { if (base) roots.push(path.resolve(base, ...parts)); };
141
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';
142
150
  const native = {
143
151
  claude: [env.CLAUDE_CONFIG_DIR || path.join(home, '.claude'), '.claude'],
144
152
  codex: [context.accountHome || env.CODEX_HOME || path.join(home, '.codex'), '.agents'],
@@ -153,7 +161,9 @@ function discoveryRoots(context, env, config) {
153
161
  }[engine];
154
162
  if (engine !== 'codex') add(native[0], 'skills');
155
163
  const copilotDefaultHome = !env.COPILOT_HOME || path.resolve(env.COPILOT_HOME) === path.join(home, '.copilot');
156
- if (!['claude', 'cursor'].includes(engine) && (engine !== 'copilot' || copilotDefaultHome)) add(engine === 'pi' ? pi.agentsHome : 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'); }
157
167
  if (['omp', 'opencode', 'grok'].includes(engine)) add(home, '.claude/skills');
158
168
  if (engine === 'omp') {
159
169
  add(home, '.codex/skills'); add(home, '.agent/skills');
@@ -166,12 +176,24 @@ function discoveryRoots(context, env, config) {
166
176
  }
167
177
  if (engine === 'copilot') for (const dir of (config?.skillsDirs ?? env.COPILOT_SKILLS_DIRS ?? '').split(',').filter(Boolean)) add(context.cwd, dir);
168
178
  if (engine === 'pi') add(context.cwd, '.pi/skills');
179
+ const walk = [];
169
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');
170
192
  if (native[1]) add(dir, native[1], 'skills');
171
193
  if (!['claude', 'cursor'].includes(engine)) add(dir, '.agents/skills');
172
194
  if (['grok', 'omp', 'opencode', 'copilot'].includes(engine)) add(dir, '.claude/skills');
173
195
  if (['omp', 'antigravity'].includes(engine)) add(dir, '.agent/skills');
174
- if (fs.existsSync(path.join(dir, '.git')) || dir === path.dirname(dir)) break;
196
+ if (antigravity) { add(dir, '_agents/skills'); add(dir, '_agent/skills'); }
175
197
  }
176
198
  return [...new Set(roots)];
177
199
  }
@@ -377,10 +399,73 @@ function skillCatalog(release, paths = path) {
377
399
  return ['Devlyn session skill catalog (read the absolute file to invoke):',
378
400
  ...SKILLS.map((name) => `${name}: ${paths.join(root, name, 'SKILL.md')}`),
379
401
  `This session's _shared folder is ${paths.join(root, '_shared')}.`,
380
- 'Use that absolute folder wherever the Devlyn defaults name _shared/, including the Quick Start references to .claude/skills/ or .agents/skills/.',
402
+ 'Use that absolute folder wherever the Devlyn defaults name _shared/.',
381
403
  ].join('\n');
382
404
  }
383
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
+
384
469
  function codexRegistrations(config, accountHome) {
385
470
  const owned = [], errors = [];
386
471
  for (const [key, plugin] of Object.entries(config.plugins || {})) {
@@ -478,11 +563,7 @@ function planActivation(releaseRoot, input, env = process.env) {
478
563
  const catalog = skillCatalog(release);
479
564
  const instructions = joinText(defaults, catalog);
480
565
  const file = (relativePath, utf8) => {
481
- let parent = path.resolve(context.runDir);
482
- const missing = [];
483
- while (!fs.existsSync(parent)) { missing.unshift(path.basename(parent)); parent = path.dirname(parent); }
484
- const physical = path.join(fs.realpathSync(parent), ...missing);
485
- const relative = path.relative(release.root, physical);
566
+ const relative = path.relative(release.root, physicalPath(context.runDir));
486
567
  if (!relative || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative))) {
487
568
  fail('invalid-arguments', { argument: 'runDir must be outside the release' }, 2);
488
569
  }
@@ -497,6 +578,19 @@ function planActivation(releaseRoot, input, env = process.env) {
497
578
  status.invocation = { kind: 'read-file', text: `Read ${path.join(release.root, 'config/skills/devlyn-ideate/SKILL.md')}` };
498
579
  };
499
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.
500
594
  const collisions = found.map((entry) => reason('name-collision', { name: entry.name, path: entry.path }));
501
595
  switch (context.engine) {
502
596
  case 'claude': {
@@ -645,18 +739,73 @@ function planActivation(releaseRoot, input, env = process.env) {
645
739
  } else fallback([...collisions, ...errors, ...kimi.errors]);
646
740
  }
647
741
  break;
648
- case 'antigravity':
649
- case 'cursor':
650
- // Only a first task carries the principles and the catalog: without one nothing reaches the session.
651
- if (context.initialPrompt) {
652
- status.principles = channel('partial', 'initial-task', [reason('principles-task-only')]);
653
- plan.prompt = { ...context.initialPrompt, text: joinText(instructions, context.initialPrompt.text) };
654
- fallback();
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 })]);
655
763
  } else {
656
- status.principles = channel('unavailable', null, [reason('principles-unavailable', { detail: 'no-initial-task' })]);
657
- status.skills = channel('unavailable', null, [reason('skills-unavailable', { detail: 'no-initial-task' })]);
764
+ file('antigravity/AGENTS.md', instructions);
765
+ principles('add-dir');
658
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);
659
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
+ }
807
+ break;
808
+ }
660
809
  }
661
810
  if (plan.engineArgs.length || Object.keys(plan.envPatch).length || plan.prompt) plan.envPatch.DEVLYN_HARNESS_ROOT = release.root;
662
811
  return finish(plan);
@@ -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 { verifyRelease, materializeRelease, fail } = require('./release');
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 verify as recorded; it never yields to another release.
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 release;
215
- try { release = verifyRelease(root).release; }
216
- catch (error) { fail('release-unavailable', { source, version, detail: error.reason?.code || error.message }, 3); }
217
- if (release.version !== version || release.digest !== digest) fail('release-version-conflict', { source, version }, 3);
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,
@@ -469,6 +469,7 @@
469
469
  "bddb4f88a0c11165b9cafe7a31027e9769313e1aee7c0a9ed9dad490e4fe1bcc",
470
470
  "be11c198a50be2884c4c1efec52bbddff55c7547a5daf0ec2ef046279c95dc45",
471
471
  "bfdb7c7e4db21e1c452e730cecadc8e97abd317c6e54ae58c298f4288b1f1354",
472
+ "c1970e6a96f0c4241907d29fb4cdf831ad255b2bbfdb5270fe0aefc32cb91f0d",
472
473
  "c1a1e9722a0f3c13c693eeaacdac8cc3d13d36d242f2bab58311170496ea4e2e",
473
474
  "c236da36a45d4013ffb7e131575229b605f662a30b5174f14754f519acc24252",
474
475
  "c276962617888c5ac2d352c5e2ed018c5ce9d00f199c73e1dd61438ec4a914fb",
@@ -494,6 +495,7 @@
494
495
  "cc8bf51efb9ef8e5fa4134743f2d25434f4fd51595128edcfd2331dbf29d7dd5",
495
496
  "ccacf091bab4102d7156631c6227120a661bb4ec0df514e14a59db44b7bd5dfd",
496
497
  "ce1b0d9d0621fd057e684dc18b43156d30f589c72ed20189ea1232ea4f7ce6c6",
498
+ "ce8c1ea5c851fed75b898759fcf8359bd95ec04b30e547ed06bdf4101a2dddd8",
497
499
  "cebacda3b11db7aa161d96b12edd6f3f790ee2850404cd42ecdf2912ed53de08",
498
500
  "cefad7acb7dcd92777f888081aa6585b1418aa581366736fd44db68b1bd66dd0",
499
501
  "cf43eb0460beebdfe1efbfb1a35a39e5fe450137252ea2c83e51d978faa1f17e",
@@ -996,6 +998,7 @@
996
998
  "c115b2ba9ec7abe2fa5261a5894e3c4899794b527f01ec682ae7fd2479199c08",
997
999
  "c150c40a04099d7b726eb8aecc8eec8e92f9f9248a6a5c15556c08000c4bd231",
998
1000
  "c16ff4fe013ee2cef905d767a09305d9c1b2f778a06b439f0bc4645ac05bc82b",
1001
+ "c1970e6a96f0c4241907d29fb4cdf831ad255b2bbfdb5270fe0aefc32cb91f0d",
999
1002
  "c1c7e45fedafb97770b6726539694eb0668c70677c2031fe2db2bc8b5339423a",
1000
1003
  "c227759474dc46ec6df00fe7baf8ba4b6b0adc71f5068ef522ff46526c861a48",
1001
1004
  "c27b13c3ae4e6fb34d9b28b8f4e899e29bfbf606a69207b9532d0e44e6078a2e",
@@ -1024,6 +1027,7 @@
1024
1027
  "ce351bfa301b000f0786cac24959108a3db6cdf1cad3733cb07b394c62d35227",
1025
1028
  "ce3cceeb74119953afde95da84dbe1bd6e3da3dbde5f85dd123a0fe8afec5d36",
1026
1029
  "ce4b307083addc337a833547cb6998cb34a38d82e572587e9a57dc2cf46d275a",
1030
+ "ce8c1ea5c851fed75b898759fcf8359bd95ec04b30e547ed06bdf4101a2dddd8",
1027
1031
  "ce9c73a3305e4ec9cd559cec6fdd10b8f4a9f3124b42ea4598340dc16a575505",
1028
1032
  "ceddbf09792093c7417e72c7e2cf08e4780e9798dcd107292cdd095b023c2a8c",
1029
1033
  "cf04e7c73e16d8e0e561e89d4a6519a1c47b9ffa08f88d1e5bda75ad2983f4df",
@@ -12,6 +12,7 @@
12
12
  "08c91060cfec04518de582599d1892ad50f6e6cec69aecd8678760c3e0039fa0",
13
13
  "0a60dd9b96442be24a571dd5fe6cac426bba7dd7a06ad17f3211f4f9fd9b1741",
14
14
  "0b5c3f72d18f8eea538bfef7192b207ff5cbac25c7e0d2ce080cc725bfdaa2e2",
15
+ "0d7e8097f36623f68b7cdc26e9c2eb80d9792b3776edf308d306febdfd9f26ae",
15
16
  "0f397a4edd6b1b3063d27f6205dc6b3d069fa616624bccf661539b174535fc7e",
16
17
  "110005e3c7052d35502186d907b169c9fc7c85d558ab7c7ad0ba2f0308aa7a5d",
17
18
  "11ac38b21ecc139211af1b335bb3acc4f0f1bc4b5c133e81a80cafebcaaca298",
@@ -124,6 +125,7 @@
124
125
  "97e7ad89c74c609b15dfbf6f9f7ac61df8b50e97e4bc318f72922e058ecffba4",
125
126
  "9815b6e6963e068872a6dbeae24068143bf92042bb035f67bafd1a224d65bcb0",
126
127
  "998343c148618b937556b58b83749c05d5aa3283280d751c118da6a542fb87f2",
128
+ "9ca5956a73070555a93f19ecb1f92a8f57cec6fd1170a2809eeacc49cc0841be",
127
129
  "9dd3ec225818efe0ad3726192db94ff129154e995617a9d3c150fc58ad0f9f2c",
128
130
  "9e4d06fd466fa896040109bac0018fcfb2f1604d55abb4180ff1f1a0b665bbc8",
129
131
  "9f14ca629e41730b1a9fb5d0ad1a39cff558fe34758d6ceccbb9de498f9f384e",
@@ -223,7 +225,8 @@
223
225
  "3fe0742825aff05fa6fda49d3e3e0fbf5457307af944eb90f2771cbf611edd69",
224
226
  "4a8bed603191eeb6834a5008691bb3942e77462abd2818340b808a752ee5a224",
225
227
  "57c2481f9ae36623f59b365eae82f22d54253f8ba0fd690949a12858b5f6e308",
226
- "b352d21d2a86def2997d89ae1ffa35b0fe428781f75cfa9e1b69a3384416ec31"
228
+ "b352d21d2a86def2997d89ae1ffa35b0fe428781f75cfa9e1b69a3384416ec31",
229
+ "be2562b5541d754c34e42b4242751bbaf3b3b2780f4314552ebb63a350e23170"
227
230
  ],
228
231
  "devlyn-auto-resolve": [
229
232
  "0740bb91fb99953631b0c2c273f00210544576f9497af5eecfdd5fe1e58349e2",
@@ -362,6 +365,7 @@
362
365
  "36b58dbdc26e51abc05a60872c4582faeebd67e9fe5626a3933467ba07c68dc4",
363
366
  "40237cd5259dbcf7c490f130e844a9e32eea48a8edabd50800edee5029bf95a4",
364
367
  "4213143689f7e7fe62ab8743c256a45492fafffc1757d64901b7b02725aee5c3",
368
+ "472d2ad853c96651324aa9a3de00cb61c0cecacb389e850ab1b380d535d22922",
365
369
  "49d6e024b343dd77e4c0cea0e513e40fe679a7a8192953190d042b3a21bbf69c",
366
370
  "5536f8391ec4442f1b793dba8dd5d7820d45f4b6dadf1111cbac9d54469a01ca",
367
371
  "573a6059a7383b37affd9c2e8bc6c88a4fb54117628a5a0746406cc32111e9aa",
@@ -398,6 +402,7 @@
398
402
  "c8e375aab574148542f392b093cbbf3f2e8dc623826b45e35df0a1a2c3862226",
399
403
  "ce282a55328c2d071cacc377670fbebea4da4b9a7fee81b25293fb26763f17c9",
400
404
  "cf61a917a804503ef1e26fbb1cf498181e843a0758ec001f84edb7bcac837448",
405
+ "d622e92c7bf32d315a5aabe2554efbc9f62790ace19a088ead9a7cef195fc715",
401
406
  "d6433bf0f96dd9dd485c3623885255738654674b3e23cfcc1a2cfd24f3cda96e",
402
407
  "db8d921aabf0d04811b55465a3757c62c4b39631aebaf6eecc994ca241cfbe56",
403
408
  "e25da746dc37b2e82c658a28d9481fd69741151045683eee2119582ddaf731cd",
@@ -822,6 +827,8 @@
822
827
  "4.3.28",
823
828
  "4.3.29",
824
829
  "4.3.3",
830
+ "4.3.30",
831
+ "4.3.31",
825
832
  "4.3.4",
826
833
  "4.3.5",
827
834
  "4.3.6",
@@ -1,7 +1,5 @@
1
1
  # Shared — Executor Pre-flight
2
2
 
3
- Used by `/devlyn-ideate` and `/devlyn-engines`: one rule for which engine executes implementation work and what happens when it is missing.
4
-
5
3
  ## Selection
6
4
 
7
5
  The executor is the `executor` pin in `.devlyn/engines.json` at the checkout root (set with `/devlyn-engines executor <name>`), else the CLI the user opened. A linked worktree or subdirectory has no pin unless one is set there. Only Claude and Codex can execute, so a drain from any other CLI needs a claude or codex pin. Keys earlier releases wrote for retired pipeline roles (`pair_judge_priority`, `roles`) stay as written and select nothing: a worker profile never becomes the executor.
@@ -15,7 +15,7 @@ Seven rules govern every change. Cite them by name when a decision touches one.
15
15
  1. **No workaround** — fix the root cause, never the symptom. No `any`, no `@ts-ignore`, no silent `catch`, no hardcoded fallback that hides a broken contract. No config bypasses.
16
16
  2. **No overengineering** — smallest sufficient change, one responsibility per module. New abstractions need an observed failure. Subtractive-first: ask "what can I delete instead?" before writing anything new. Remove code your change makes unused; only report unrelated pre-existing dead code.
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
- 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.
18
+ 4. **Worldclass** — zero CRITICAL, zero HIGH findings on the shippable path; each must name a concrete case of wrong behavior or results, a security hole, or lost or corrupted data. A review is one pass on the source the checks ran on, by the reviewers the user requires, else by one reviewer who has not seen the author's reasoning. Fix each finding or reject it with a reason, and verify every edit and the invariants it touches. Only a CRITICAL or HIGH calls for another reviewer pass, which reads the fixes, the rejections and the invariants they touch. If one survives that pass, or its fix caused a new one, repair once more; if the next pass finds either again, stop: the work does not ship and is reported blocked.
19
19
  5. **Best practice** — idiomatic for the language and framework. Use standard primitives; do not hand-roll what the library already provides.
20
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.
@@ -29,7 +29,7 @@ Three discipline rules govern HOW the principles are applied:
29
29
  ## Quick Start
30
30
 
31
31
  - `devlyn-ideate` — loop designer and intent queue: `plan <intent or document>` writes a validated loop package, `add` queues its tasks, `status` (or no arguments) reports the queue, `drain` executes it serially.
32
- - Delivery — direct work edits the current checkout; deliver completed, verified requests with changes by project policy (default PR and merge), following `_shared/task-completion.md` in `.claude/skills/` or `.agents/skills/` (project, else `~`). With known concurrent writers, isolate before editing; on unexplained changes, pause writes until safe or isolated. An ideate drain delivers every task. Explicit limits win; commit alone stays local.
32
+ - Delivery — direct work edits the current checkout; deliver completed, verified requests with changes by project policy (default PR and merge), following `_shared/task-completion.md`. With known concurrent writers, isolate before editing; on unexplained changes, pause writes until safe or isolated. An ideate drain delivers every task. Explicit limits win; commit alone stays local.
33
33
  - Executor — `devlyn-engines` shows it and pins it in `.devlyn/engines.json`. A pinned executor does the implementation work, direct or drained: when it is not you, delegate to it, and when it is unavailable stop with `BLOCKED:<engine>-unavailable`. Without a pin, you are the executor.
34
34
 
35
35
  Each skill's `SKILL.md` is the source of truth for its flags and workflow.
@@ -62,9 +62,6 @@ The repository comes from the remote's GitHub URL and the base from the reposito
62
62
  A non-default base is refused at allocation.
63
63
  Every delivery allocation owns a linked worktree; `--worktree` is required and
64
64
  must name an absent path.
65
- Cleanup removes empty parents through the exact per-repo root shown above or its legacy `.devlyn.noindex`/`.devlyn` root, keeping the shared `.devlyn-worktrees` container. Allocation also sweeps empty legacy roots for this repo.
66
- On Windows, allocation marks only a newly created shared container hidden and creates the worktree not content-indexed (content only; names stay indexed); files copied in keep their own attributes.
67
- A marking failure continues allocation and is reported in `warnings`.
68
65
  Remote allocation first cleans earlier merged deliveries, then allocation refuses with `BLOCKED:disk`
69
66
  without creating a receipt, branch or worktree when that filesystem has less than max(10 GiB, 2%) free; free space and allocate again.
70
67
  A publication allocation normally starts at the exact fetched remote base,
@@ -122,9 +119,7 @@ Git config `devlyn.warmBuildDirs` is true, or it is unset and the Cargo workspac
122
119
  Cargo.toml sets `[workspace.metadata.devlyn] warm-build-dirs = true`,
123
120
  allocation seeds that directory from a built checkout of the repository, so
124
121
  the first build does not rebuild every dependency; `build_dirs` in its result
125
- and receipt says what it did, and a skip means an ordinary cold build. The
126
- Git setting takes precedence on this machine: false disables and invalid values
127
- skip; global, system, include and per-worktree values are not consulted. Seeding
122
+ and receipt says what it did, and a skip means an ordinary cold build. Seeding
128
123
  keeps dependency outputs that may hold the other checkout's paths, and those
129
124
  follow that checkout's later rebuilds. `warm-build-dirs.py <worktree>` does the same for a
130
125
  worktree made without allocation. A warmed directory leaves with the worktree
@@ -146,11 +141,7 @@ python3 "$DEVLYN_SHARED_DIR/task-complete.py" clean-scratch \
146
141
  --receipt '<returned receipt.json>' --writers-stopped
147
142
  ```
148
143
 
149
- This empties only the prospectively owned scratch, preserves its directory
150
- identity for interrupted cleanup and later builds, checks live users and reports
151
- the logical byte count separately from actual free disk space.
152
- Unknown historical roots, source/recovery data, shared caches and terminal
153
- history are never adopted or swept. A refused cleanup does not block source
144
+ A refused cleanup does not block source
154
145
  delivery; it stays visible in the handoff with its receipt and retry command
155
146
  (`CLEANUP_PENDING` after delivered source). No task is described as cleaned
156
147
  while owned disposable files remain. On resumption, finish any pending scratch
@@ -257,9 +248,6 @@ are preserved beside it as `records.<digest>/.devlyn`, never rewriting earlier c
257
248
  Non-regular files or symlinks there, partial/corrupt custody, an unreadable
258
249
  earlier copy or a mismatched `records.<digest>` copy retain the tree for repair.
259
250
 
260
- Legacy in-place receipts retire refs only once their task branch is no longer
261
- checked out; their checkout is never modified. Local ref deletion is guarded by
262
- the expected SHA; remote deletion uses an exact force-with-lease solely as
263
- compare-and-delete, never for product publication. A changed remote branch is
251
+ A changed remote branch is
264
252
  retained. Retry uses the external receipt/evidence after removal. Recovery
265
253
  refs/evidence have no automatic expiry.
@@ -59,4 +59,4 @@ Each `review_requirements` ID needs review evidence: a JSON record under `<workt
59
59
  }
60
60
  ```
61
61
 
62
- A record counts only when it binds this task, the candidate source and both contract digests; any other well-formed record is reported under `ignored_reviews` (a source change invalidates earlier reviews). A listed review or runner record that is unreadable or not this shape fails acceptance with its path named; keys a review record or finding adds beyond this shape are ignored. The counted records' `requirements` must cover `review_requirements`. Each finding's `disposition` is `open`, `resolved` or `rejected` (`rejected` needs a `reason`); an open binding finding blocks acceptance. The loop requires no particular engine or number of reviewers.
62
+ A record counts only when it binds this task, the candidate source and both contract digests; any other well-formed record is reported under `ignored_reviews` (a source change invalidates earlier reviews; the record for the changed source may come from a pass that reads only the change and the invariants it touches). A listed review or runner record that is unreadable or not this shape fails acceptance with its path named; keys a review record or finding adds beyond this shape are ignored. The counted records' `requirements` must cover `review_requirements`. Each finding's `disposition` is `open`, `resolved` or `rejected` (`rejected` needs a `reason`); an open binding finding blocks acceptance. The loop requires no particular engine or number of reviewers.
@@ -55,11 +55,9 @@ Package cleanup, parent pruning and report failures appear as diagnostics under
55
55
 
56
56
  ## Release binding and read-only consumers
57
57
 
58
- `<git-common-dir>/devlyn-loops/harness.json` is authoritative across linked worktrees: `{schemaVersion: 1, release: {package, version, digest, root}, defaults: {path, sha256}}`. Paths are absolute and hashes cover exact bytes. The queue lock covers binding before the first capture. An interrupted first add, unfinished tasks, recovery conflicts and pending delivery retain it; settlement removes it. A different release refuses: use the exact recovery argv when it gives one (a JSON argv array preserving the caller’s arguments); otherwise do what it says (Drain in Devlyn, the tasks to finish first, or a repair). Never build the bound queue’s path yourself. No caller substitutes another implementation.
58
+ `<git-common-dir>/devlyn-loops/harness.json` is authoritative across linked worktrees: `{schemaVersion: 1, release: {package, version, digest, root}, defaults: {path, sha256}}`. Paths are absolute and hashes cover exact bytes. The queue lock covers binding before the first capture. An interrupted first add, unfinished tasks, recovery conflicts and pending delivery retain it; settlement removes it. No caller substitutes another implementation.
59
59
 
60
- `queue.py inspect --repo <dir> --json` reads without cleanup, locks or writes. It returns the status summary plus `binding` (null for legacy) and `compatibility`: `compatible`, `legacy`, `reasons`, `recoveryArgv`, `taskPacket` ranges and `queueBinding` ranges. Incompatible or unreadable state is reported, never migrated. Apps should use this reader instead of `status`, which still reconciles cleanup. For a mismatch, `recoveryArgv` names the bound queue’s inspect invocation when handoff is allowed, and is null when the refusal names Drain in Devlyn, tasks to finish first, or a repair. Use an exact recovery argv when provided; otherwise follow the refusal.
61
-
62
- Codex projection writers place `.devlyn-harness-projection.json` at the copied plugin root, beside `skills` and `.codex-plugin`: `{schemaVersion: 1, release: <ReleaseRef>, source: "native/codex/plugins/devlyn"}`. The reader verifies the named release and every projected file’s bytes/mode against that subtree (excluding only the marker). Symlinked skill invocations resolve through their real path. Standalone installs and existing unfinished unbound queues retain legacy behavior; new legacy packets use v2 with a null release, and their `defaults` name the invoking installation's own `_shared/runtime-principles.md` (`source: "skill"`). No engine applicability is inferred from instruction filenames: bound queues use verified release defaults with native instructions taking precedence, except a Devlyn managed block, which the defaults replace. Worker and runner paths point to the bound release, independent of a projection cache’s lifetime.
60
+ `queue.py inspect --repo <dir> --json` reads without cleanup, locks or writes. It returns the status summary plus `binding` (null for legacy) and `compatibility`: `compatible`, `legacy`, `reasons`, `recoveryArgv`, `taskPacket` ranges and `queueBinding` ranges. Incompatible or unreadable state is reported, never migrated. For a mismatch, `recoveryArgv` names the bound queue’s inspect invocation when handoff is allowed, and is null when the refusal names Drain in Devlyn, tasks to finish first, or a repair. Use an exact recovery argv when provided; otherwise follow the refusal.
63
61
 
64
62
  ## Executor exchange
65
63
 
@@ -107,7 +107,6 @@ Coverage proves that every obligation has evidence. Whether the checks express t
107
107
  - The only legal transition turns one pending row into `[x]` or `[F]` with every other byte unchanged, so a terminal commit changes one line of its loop's file. Terminal rows are never reinterpreted or rerun. The blank lines let transitions of adjacent rows merge, and loops never share a file.
108
108
  - Identities are unique; equal titles are fine.
109
109
  - `docs/specs/queue.md` is the legacy queue. Its raw rows are intents: readable, never executed, never written (a row there that fully matches this grammar is ignored). `add --materialize <line>` plans one pending legacy row as a loop whose `## Intent` reproduces it verbatim (whitespace may reflow); that loop's queue file starts with `<!-- replaces docs/specs/queue.md row (occurrence <n>): <row> -->`, the row's exact text and its occurrence among equal rows, line endings aside. Every checkout, a fresh clone included, then shows the loop's rows in that row's place. A claim matching no row replaces nothing; a row two loops claim (two clones each materializing it) is hidden, neither loop taking its place, so it is never planned again.
110
- - Status and drain read the legacy rows in file order, each replaced row as its loop's rows; then the other recorded loops in add order, from their captures; then loops without a record here, as in a fresh clone, from the checkout's tracked queue files in loop-id order.
111
110
 
112
111
  ## Commands
113
112
 
package/harness/AGENTS.md CHANGED
@@ -1,4 +1,4 @@
1
- <!-- devlyn:instructions:begin sha256=f2206e5ed1c96a6c5fc368836bb041e8e77328048ca617f902ed6fb72dcb742d -->
1
+ <!-- devlyn:instructions:begin sha256=7a484752888c37d7e91a9edfaa17d36b34ada221fc1d533ab9d73bc08c56ae7e -->
2
2
  Project-specific instructions outside this managed block take precedence over these defaults.
3
3
 
4
4
  # Project Instructions
@@ -16,7 +16,7 @@ Seven rules govern every change. Cite them by name when a decision touches one.
16
16
  1. **No workaround** — fix the root cause, never the symptom. No `any`, no `@ts-ignore`, no silent `catch`, no hardcoded fallback that hides a broken contract. No config bypasses.
17
17
  2. **No overengineering** — smallest sufficient change, one responsibility per module. New abstractions need an observed failure. Subtractive-first: ask "what can I delete instead?" before writing anything new. Remove code your change makes unused; only report unrelated pre-existing dead code.
18
18
  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.
19
- 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
+ 4. **Worldclass** — zero CRITICAL, zero HIGH findings on the shippable path; each must name a concrete case of wrong behavior or results, a security hole, or lost or corrupted data. A review is one pass on the source the checks ran on, by the reviewers the user requires, else by one reviewer who has not seen the author's reasoning. Fix each finding or reject it with a reason, and verify every edit and the invariants it touches. Only a CRITICAL or HIGH calls for another reviewer pass, which reads the fixes, the rejections and the invariants they touch. If one survives that pass, or its fix caused a new one, repair once more; if the next pass finds either again, stop: the work does not ship and is reported blocked.
20
20
  5. **Best practice** — idiomatic for the language and framework. Use standard primitives; do not hand-roll what the library already provides.
21
21
  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.
22
22
  7. **Production ready** — error states are explicit and visible; behavior under failure is what the user expects, not silent corruption.
@@ -30,7 +30,7 @@ Three discipline rules govern HOW the principles are applied:
30
30
  ## Quick Start
31
31
 
32
32
  - `devlyn-ideate` — loop designer and intent queue: `plan <intent or document>` writes a validated loop package, `add` queues its tasks, `status` (or no arguments) reports the queue, `drain` executes it serially.
33
- - Delivery — direct work edits the current checkout; deliver completed, verified requests with changes by project policy (default PR and merge), following `_shared/task-completion.md` in `.claude/skills/` or `.agents/skills/` (project, else `~`). With known concurrent writers, isolate before editing; on unexplained changes, pause writes until safe or isolated. An ideate drain delivers every task. Explicit limits win; commit alone stays local.
33
+ - Delivery — direct work edits the current checkout; deliver completed, verified requests with changes by project policy (default PR and merge), following `_shared/task-completion.md`. With known concurrent writers, isolate before editing; on unexplained changes, pause writes until safe or isolated. An ideate drain delivers every task. Explicit limits win; commit alone stays local.
34
34
  - Executor — `devlyn-engines` shows it and pins it in `.devlyn/engines.json`. A pinned executor does the implementation work, direct or drained: when it is not you, delegate to it, and when it is unavailable stop with `BLOCKED:<engine>-unavailable`. Without a pin, you are the executor.
35
35
 
36
36
  Each skill's `SKILL.md` is the source of truth for its flags and workflow.