devlyn-cli 2.6.1 → 2.8.0
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 +10 -72
- package/CLAUDE.md +29 -12
- package/README.md +7 -5
- package/benchmark/auto-resolve/fixtures/F1-cli-trivial-flag/task.ko.txt +8 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/NOTES.md +28 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/RETIRED.md +15 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/expected.json +57 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/metadata.json +10 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/setup.sh +50 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/spec.md +59 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/task.txt +9 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/verifiers/ordered-settlement.js +88 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/verifiers/reject-invalid.js +45 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/NOTES.md +30 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/RETIRED.md +16 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/expected.json +64 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/metadata.json +10 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/setup.sh +21 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/spec.md +58 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/task.txt +9 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/dup-op.js +54 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/priority-apply.js +82 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/rollback.js +77 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/NOTES.md +99 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/RETIRED.md +18 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/expected.json +64 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/metadata.json +10 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/setup.sh +4 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/spec.md +61 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/task.txt +9 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/correctness-small.js +135 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/scale.js +100 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/validation.js +75 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/NOTES.md +93 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/RETIRED.md +16 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/expected.json +65 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/metadata.json +10 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/setup.sh +18 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/spec.md +66 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/task.txt +9 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/correctness-small.js +84 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/scale.js +184 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/validation.js +74 -0
- package/benchmark/auto-resolve/scripts/pair-rejected-fixtures.sh +12 -0
- package/benchmark/auto-resolve/scripts/run-fixture.sh +39 -1
- package/benchmark/auto-resolve/scripts/test-lint-fixtures.sh +1 -1
- package/bin/devlyn.js +71 -83
- package/config/skills/_shared/adapters/README.md +7 -0
- package/config/skills/_shared/adapters/claude.md +49 -2
- package/config/skills/_shared/adapters/omp.md +17 -0
- package/config/skills/_shared/archive_run.py +8 -2
- package/config/skills/_shared/codex-config.md +22 -6
- package/config/skills/_shared/engine-doctor.sh +107 -0
- package/config/skills/_shared/engine-preflight.md +18 -11
- package/config/skills/_shared/expected.schema.json +26 -0
- package/config/skills/_shared/pair-plan-schema.md +2 -2
- package/config/skills/_shared/runtime-principles.md +1 -1
- package/config/skills/_shared/spec-verify-check.py +773 -486
- package/config/skills/_shared/state-phase-write.py +374 -0
- package/config/skills/_shared/verify-merge-findings.py +177 -61
- package/config/skills/devlyn:engines/SKILL.md +47 -0
- package/config/skills/devlyn:ideate/SKILL.md +22 -3
- package/config/skills/devlyn:ideate/references/elicitation.md +2 -2
- package/config/skills/devlyn:ideate/references/from-spec-mode.md +2 -2
- package/config/skills/devlyn:ideate/references/project-mode.md +1 -1
- package/config/skills/devlyn:ideate/references/spec-template.md +15 -4
- package/config/skills/devlyn:queue/SKILL.md +26 -0
- package/config/skills/devlyn:resolve/SKILL.md +55 -25
- package/config/skills/devlyn:resolve/references/free-form-mode.md +1 -1
- package/config/skills/devlyn:resolve/references/phases/build-gate.md +2 -2
- package/config/skills/devlyn:resolve/references/phases/cleanup.md +1 -1
- package/config/skills/devlyn:resolve/references/phases/implement.md +1 -1
- package/config/skills/devlyn:resolve/references/phases/plan.md +3 -2
- package/config/skills/devlyn:resolve/references/phases/probe-derive.md +8 -0
- package/config/skills/devlyn:resolve/references/phases/verify.md +18 -9
- package/config/skills/devlyn:resolve/references/state-schema.md +11 -7
- package/optional-skills/devlyn:pencil-pull/SKILL.md +5 -0
- package/optional-skills/devlyn:pencil-push/SKILL.md +5 -0
- package/package.json +1 -2
- package/scripts/lint-skills.sh +55 -46
- package/agents-config/evaluator.md +0 -64
package/bin/devlyn.js
CHANGED
|
@@ -7,7 +7,6 @@ const readline = require('readline');
|
|
|
7
7
|
const { execSync } = require('child_process');
|
|
8
8
|
|
|
9
9
|
const CONFIG_SOURCE = path.join(__dirname, '..', 'config');
|
|
10
|
-
const AGENTS_SOURCE = path.join(__dirname, '..', 'agents-config');
|
|
11
10
|
const OPTIONAL_SKILLS_SOURCE = path.join(__dirname, '..', 'optional-skills');
|
|
12
11
|
const PKG = require('../package.json');
|
|
13
12
|
|
|
@@ -15,7 +14,8 @@ const PKG = require('../package.json');
|
|
|
15
14
|
// directory. Single source of truth so codex/omp/pi stay in lockstep — adding a
|
|
16
15
|
// skill here installs it everywhere. Standards skills (code-*, root-cause-*,
|
|
17
16
|
// ui-*) stay Claude-core-only, matching the pre-existing Codex bundle.
|
|
18
|
-
const DEVLYN_CORE_SKILLS = ['devlyn:resolve', 'devlyn:ideate', 'devlyn:design-ui', '_shared'];
|
|
17
|
+
const DEVLYN_CORE_SKILLS = ['devlyn:resolve', 'devlyn:ideate', 'devlyn:design-ui', 'devlyn:engines', 'devlyn:queue', '_shared'];
|
|
18
|
+
const DEVLYN_SKILL_DIR_STAMP = '__DEVLYN_SKILL_DIR__';
|
|
19
19
|
|
|
20
20
|
// Cross-agent shared skills directory read by BOTH oh-my-pi and Pi. Verified
|
|
21
21
|
// from the omp binary's skill-provider strings ("skills from .agents/skills —
|
|
@@ -31,7 +31,6 @@ const CLI_TARGETS = {
|
|
|
31
31
|
name: 'Codex CLI (OpenAI)',
|
|
32
32
|
instructionsFile: 'AGENTS.md',
|
|
33
33
|
baseInstructionsFile: 'AGENTS.md',
|
|
34
|
-
configDir: null, // Codex uses AGENTS.md at project root
|
|
35
34
|
// Codex auto-loads skills from ~/.codex/skills/ (user-global). Same
|
|
36
35
|
// SKILL.md format as Claude Code; descriptions must stay ≤1024 chars.
|
|
37
36
|
skillsDir: path.join(os.homedir(), '.codex', 'skills'),
|
|
@@ -42,7 +41,6 @@ const CLI_TARGETS = {
|
|
|
42
41
|
name: 'oh-my-pi (omp)',
|
|
43
42
|
instructionsFile: 'AGENTS.md',
|
|
44
43
|
baseInstructionsFile: 'AGENTS.md',
|
|
45
|
-
configDir: null, // omp discovers AGENTS.md at project root + user home
|
|
46
44
|
// omp loads skills from ~/.agents/skills (user home) — shared with Pi.
|
|
47
45
|
skillsDir: SHARED_AGENTS_SKILLS_DIR,
|
|
48
46
|
skillsToInstall: DEVLYN_CORE_SKILLS,
|
|
@@ -54,37 +52,12 @@ const CLI_TARGETS = {
|
|
|
54
52
|
name: 'Pi (earendil-works)',
|
|
55
53
|
instructionsFile: 'AGENTS.md',
|
|
56
54
|
baseInstructionsFile: 'AGENTS.md',
|
|
57
|
-
configDir: null, // Pi reads AGENTS.md / CLAUDE.md walking up from cwd
|
|
58
55
|
// Pi loads skills from ~/.agents/skills — shared with oh-my-pi.
|
|
59
56
|
skillsDir: SHARED_AGENTS_SKILLS_DIR,
|
|
60
57
|
skillsToInstall: DEVLYN_CORE_SKILLS,
|
|
61
58
|
// Project-scoped only (see omp): machine-level ~/.pi must not auto-trigger.
|
|
62
59
|
detect: () => fs.existsSync(path.join(process.cwd(), '.pi')) || fs.existsSync(path.join(process.cwd(), '.agents')),
|
|
63
60
|
},
|
|
64
|
-
gemini: {
|
|
65
|
-
name: 'Gemini CLI (Google)',
|
|
66
|
-
instructionsFile: 'GEMINI.md',
|
|
67
|
-
configDir: null, // Gemini uses GEMINI.md at project root
|
|
68
|
-
detect: () => fs.existsSync(path.join(process.cwd(), 'GEMINI.md')) || fs.existsSync(path.join(process.cwd(), '.gemini')),
|
|
69
|
-
},
|
|
70
|
-
cursor: {
|
|
71
|
-
name: 'Cursor',
|
|
72
|
-
instructionsFile: '.cursorrules',
|
|
73
|
-
configDir: '.cursor/rules',
|
|
74
|
-
detect: () => fs.existsSync(path.join(process.cwd(), '.cursorrules')) || fs.existsSync(path.join(process.cwd(), '.cursor')),
|
|
75
|
-
},
|
|
76
|
-
copilot: {
|
|
77
|
-
name: 'GitHub Copilot',
|
|
78
|
-
instructionsFile: '.github/copilot-instructions.md',
|
|
79
|
-
configDir: '.github/copilot/agents',
|
|
80
|
-
detect: () => fs.existsSync(path.join(process.cwd(), '.github', 'copilot-instructions.md')) || fs.existsSync(path.join(process.cwd(), '.github', 'copilot')),
|
|
81
|
-
},
|
|
82
|
-
windsurf: {
|
|
83
|
-
name: 'Windsurf',
|
|
84
|
-
instructionsFile: '.windsurfrules',
|
|
85
|
-
configDir: '.windsurf/rules',
|
|
86
|
-
detect: () => fs.existsSync(path.join(process.cwd(), '.windsurfrules')) || fs.existsSync(path.join(process.cwd(), '.windsurf')),
|
|
87
|
-
},
|
|
88
61
|
};
|
|
89
62
|
|
|
90
63
|
// Files removed in previous versions that should be cleaned up on upgrade
|
|
@@ -214,7 +187,7 @@ const OPTIONAL_ADDONS = [
|
|
|
214
187
|
{ name: 'asset-creator', desc: 'AI pixel art game asset pipeline — generate, chroma-key, catalog', type: 'local' },
|
|
215
188
|
{ name: 'cloudflare-nextjs-setup', desc: 'Cloudflare Workers + Next.js deployment with OpenNext', type: 'local' },
|
|
216
189
|
{ name: 'generate-skill', desc: 'Create well-structured Claude Code skills following Anthropic best practices', type: 'local' },
|
|
217
|
-
{ name: 'prompt-engineering', desc: 'Claude
|
|
190
|
+
{ name: 'prompt-engineering', desc: 'Claude prompt optimization using Anthropic best practices', type: 'local' },
|
|
218
191
|
{ name: 'better-auth-setup', desc: 'Production-ready Better Auth + Hono + Drizzle + PostgreSQL auth setup', type: 'local' },
|
|
219
192
|
{ name: 'polar-billing-setup', desc: 'Polar usage-based / metered billing — correct setup + diagnose silent $0-billing failures', type: 'local' },
|
|
220
193
|
{ name: 'pyx-scan', desc: 'Check whether an AI agent skill is safe before installing', type: 'local' },
|
|
@@ -368,6 +341,40 @@ function copyRecursive(src, dest, baseDir) {
|
|
|
368
341
|
}
|
|
369
342
|
}
|
|
370
343
|
|
|
344
|
+
function shellDoubleQuoteDefault(value) {
|
|
345
|
+
if (value.includes('\n')) {
|
|
346
|
+
throw new Error(`Cannot stamp skill path with newline: ${value}`);
|
|
347
|
+
}
|
|
348
|
+
return value.replace(/[\\$"`}]/g, '\\$&');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function stampInstalledSkillDir(rootDir, skillDir) {
|
|
352
|
+
// Stamp ONLY the assignment-default occurrence. The sentinel comparison
|
|
353
|
+
// literal on the guard line must stay intact: stamping it makes the guard
|
|
354
|
+
// compare the stamped default to itself and false-positive
|
|
355
|
+
// BLOCKED:shared-dir-unresolved on every codex/omp run that executes the
|
|
356
|
+
// block (CLAUDE_SKILL_DIR unset) — iter-0040 R3 latent finding.
|
|
357
|
+
const assignmentStamp = '${CLAUDE_SKILL_DIR:-' + DEVLYN_SKILL_DIR_STAMP + '}';
|
|
358
|
+
const stampedAssignment = '${CLAUDE_SKILL_DIR:-' + shellDoubleQuoteDefault(skillDir) + '}';
|
|
359
|
+
|
|
360
|
+
function visit(current) {
|
|
361
|
+
const stats = fs.statSync(current);
|
|
362
|
+
if (stats.isDirectory()) {
|
|
363
|
+
for (const item of fs.readdirSync(current)) {
|
|
364
|
+
visit(path.join(current, item));
|
|
365
|
+
}
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (path.extname(current) !== '.md') return;
|
|
369
|
+
|
|
370
|
+
const content = fs.readFileSync(current, 'utf8');
|
|
371
|
+
if (!content.includes(assignmentStamp)) return;
|
|
372
|
+
fs.writeFileSync(current, content.split(assignmentStamp).join(stampedAssignment));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
visit(rootDir);
|
|
376
|
+
}
|
|
377
|
+
|
|
371
378
|
// Dev artifacts that live under config/skills/ but must never ship or install.
|
|
372
379
|
// Mirrors the `!` exclusions in package.json files[].
|
|
373
380
|
const UNSHIPPED_SKILL_DIRS = new Set([
|
|
@@ -579,6 +586,11 @@ function installSkillsForCLI(cliKey) {
|
|
|
579
586
|
fs.mkdirSync(cli.skillsDir, { recursive: true });
|
|
580
587
|
}
|
|
581
588
|
|
|
589
|
+
const removed = cleanupDeprecated(path.dirname(cli.skillsDir));
|
|
590
|
+
if (removed > 0) {
|
|
591
|
+
log(`\n🧹 Cleaned up ${removed} deprecated file${removed > 1 ? 's' : ''}`, 'yellow');
|
|
592
|
+
}
|
|
593
|
+
|
|
582
594
|
let copied = 0;
|
|
583
595
|
for (const skillName of cli.skillsToInstall) {
|
|
584
596
|
const src = path.join(sourceSkillsDir, skillName);
|
|
@@ -590,21 +602,20 @@ function installSkillsForCLI(cliKey) {
|
|
|
590
602
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
591
603
|
}
|
|
592
604
|
copyRecursive(src, dest, cli.skillsDir);
|
|
605
|
+
stampInstalledSkillDir(dest, dest);
|
|
593
606
|
copied++;
|
|
594
607
|
log(` → ${cli.skillsDir.replace(os.homedir(), '~')}/${skillName}`, 'dim');
|
|
595
608
|
}
|
|
596
609
|
return copied;
|
|
597
610
|
}
|
|
598
611
|
|
|
599
|
-
// Strip
|
|
600
|
-
//
|
|
601
|
-
//
|
|
602
|
-
//
|
|
603
|
-
|
|
604
|
-
// devlyn-appended block (always last) and preserves everything above it.
|
|
612
|
+
// Strip the legacy "# Devlyn Agent Instructions" block (the retired evaluator
|
|
613
|
+
// agent) that earlier installs appended to instruction files, so upgrades scrub
|
|
614
|
+
// it instead of carrying it forever. Anchors on the LAST marker so user content
|
|
615
|
+
// above a stray copy of the block is preserved.
|
|
616
|
+
const DEVLYN_AGENTS_MARKER = '# Devlyn Agent Instructions';
|
|
605
617
|
function stripManagedBlock(content) {
|
|
606
|
-
const
|
|
607
|
-
const markerIdx = content.lastIndexOf(marker);
|
|
618
|
+
const markerIdx = content.lastIndexOf(DEVLYN_AGENTS_MARKER);
|
|
608
619
|
if (markerIdx > 0) {
|
|
609
620
|
const sepIdx = content.lastIndexOf('\n---', markerIdx);
|
|
610
621
|
return content.slice(0, sepIdx > 0 ? sepIdx : markerIdx).trimEnd();
|
|
@@ -613,63 +624,40 @@ function stripManagedBlock(content) {
|
|
|
613
624
|
}
|
|
614
625
|
|
|
615
626
|
// One-line description of exactly what selecting a CLI target installs, so the
|
|
616
|
-
// unified selector stays honest
|
|
617
|
-
// identical to "Codex" (instructions + skills).
|
|
627
|
+
// unified selector stays honest.
|
|
618
628
|
function targetDesc(cli) {
|
|
619
629
|
if (cli.skillsDir) {
|
|
620
630
|
return `${cli.instructionsFile} + devlyn skills → ${cli.skillsDir.replace(os.homedir(), '~')}`;
|
|
621
631
|
}
|
|
622
|
-
if (cli.configDir) return `Agent instructions → ${cli.configDir}/`;
|
|
623
632
|
return `${cli.instructionsFile} instructions only`;
|
|
624
633
|
}
|
|
625
634
|
|
|
626
|
-
// Install
|
|
627
|
-
//
|
|
628
|
-
//
|
|
635
|
+
// Install the packaged base instruction file for a CLI (project AGENTS.md for
|
|
636
|
+
// codex/omp/pi). Creates it from the packaged base when missing; on existing
|
|
637
|
+
// files, scrubs the legacy appended block and otherwise leaves user content
|
|
638
|
+
// untouched. Returns true when the destination was written.
|
|
629
639
|
function installInstructionsForCLI(cliKey) {
|
|
630
640
|
const cli = CLI_TARGETS[cliKey];
|
|
631
|
-
if (!cli) return false;
|
|
632
|
-
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
if (cli.configDir) {
|
|
641
|
-
// CLI supports an agents directory — copy agent files there
|
|
642
|
-
const destDir = path.join(process.cwd(), cli.configDir);
|
|
643
|
-
if (!fs.existsSync(destDir)) {
|
|
644
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
645
|
-
}
|
|
646
|
-
for (const file of agents) {
|
|
647
|
-
const src = path.join(AGENTS_SOURCE, file);
|
|
648
|
-
const dest = path.join(destDir, file);
|
|
649
|
-
fs.copyFileSync(src, dest);
|
|
650
|
-
log(` → ${cli.configDir}/${file}`, 'dim');
|
|
641
|
+
if (!cli || !cli.baseInstructionsFile) return false;
|
|
642
|
+
|
|
643
|
+
const destFile = path.join(process.cwd(), cli.instructionsFile);
|
|
644
|
+
let content = null;
|
|
645
|
+
if (fs.existsSync(destFile)) {
|
|
646
|
+
const current = fs.readFileSync(destFile, 'utf8');
|
|
647
|
+
if (current.includes(DEVLYN_AGENTS_MARKER)) {
|
|
648
|
+
content = stripManagedBlock(current) + '\n';
|
|
651
649
|
}
|
|
652
650
|
} else {
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
const agentContent = agents.map((file) => {
|
|
657
|
-
return fs.readFileSync(path.join(AGENTS_SOURCE, file), 'utf8');
|
|
658
|
-
}).join('\n\n---\n\n');
|
|
659
|
-
|
|
660
|
-
let existing = '';
|
|
661
|
-
if (fs.existsSync(destFile)) {
|
|
662
|
-
existing = stripManagedBlock(fs.readFileSync(destFile, 'utf8'));
|
|
663
|
-
} else if (cli.baseInstructionsFile) {
|
|
664
|
-
const baseInstructionsSrc = path.join(__dirname, '..', cli.baseInstructionsFile);
|
|
665
|
-
if (fs.existsSync(baseInstructionsSrc)) {
|
|
666
|
-
existing = stripManagedBlock(fs.readFileSync(baseInstructionsSrc, 'utf8'));
|
|
667
|
-
}
|
|
651
|
+
const baseInstructionsSrc = path.join(__dirname, '..', cli.baseInstructionsFile);
|
|
652
|
+
if (fs.existsSync(baseInstructionsSrc)) {
|
|
653
|
+
content = fs.readFileSync(baseInstructionsSrc, 'utf8');
|
|
668
654
|
}
|
|
669
|
-
|
|
670
|
-
fs.writeFileSync(destFile, existing + separator + agentContent + '\n');
|
|
671
|
-
log(` → ${cli.instructionsFile} (agent instructions appended)`, 'dim');
|
|
672
655
|
}
|
|
656
|
+
if (content === null) return false;
|
|
657
|
+
|
|
658
|
+
log(`\n🤖 Installing instructions for ${cli.name}...`, 'cyan');
|
|
659
|
+
fs.writeFileSync(destFile, content);
|
|
660
|
+
log(` → ${cli.instructionsFile}`, 'dim');
|
|
673
661
|
return true;
|
|
674
662
|
}
|
|
675
663
|
|
|
@@ -696,7 +684,7 @@ function installSelectedCLITargets(cliKeys) {
|
|
|
696
684
|
for (const cliKey of cliKeys) {
|
|
697
685
|
const cli = CLI_TARGETS[cliKey];
|
|
698
686
|
if (!cli) continue;
|
|
699
|
-
const instrKey = cli.
|
|
687
|
+
const instrKey = cli.instructionsFile;
|
|
700
688
|
if (!instrDone.has(instrKey)) {
|
|
701
689
|
installInstructionsForCLI(cliKey);
|
|
702
690
|
instrDone.add(instrKey);
|
|
@@ -42,6 +42,13 @@ Specific patterns the official guide warns about for this model.
|
|
|
42
42
|
|
|
43
43
|
Keep each section to ≤ 8 lines. Adapters are deltas, not full prompts. If an adapter grows past ~80 lines, the content probably belongs in canonical body.
|
|
44
44
|
|
|
45
|
+
## Role-scoped and non-agentic adapters
|
|
46
|
+
|
|
47
|
+
Adapters may add two more sections beyond the base format:
|
|
48
|
+
|
|
49
|
+
- **`## Role eligibility`** — fixed ASCII fields, one per line: `executor: yes|no`, `pair_judge: yes|no`. Absent section = eligible for both roles (every adapter shipped before iter-0051). Present only when a backend is structurally unable to fill a role — e.g. a completion-only local model has no tool/file-edit access, so it declares `executor: no`. `_shared/engine-preflight.md` and `/devlyn:engines` read this section as a structured marker, not prose, so it validates mechanically.
|
|
50
|
+
- **`## Invocation`** — for backends called over an API instead of a CLI: the concrete request shape (endpoint, JSON body, timeout) and the adapter-declared availability probe (`_shared/engine-preflight.md#role-resolution`'s "an adapter may declare a different probe" hook).
|
|
51
|
+
|
|
45
52
|
## When to add a new adapter
|
|
46
53
|
|
|
47
54
|
A new adapter file ships when:
|
|
@@ -1,10 +1,57 @@
|
|
|
1
|
-
# Claude
|
|
1
|
+
# Claude adapter
|
|
2
2
|
|
|
3
3
|
> Source: <https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices>
|
|
4
4
|
|
|
5
|
+
## Invocation
|
|
6
|
+
|
|
7
|
+
Orchestrator-side contract for spawning a Claude judge from a non-Claude
|
|
8
|
+
orchestrator (Codex CLI, oh-my-pi) — the reverse-direction counterpart of
|
|
9
|
+
`_shared/codex-config.md`. Source: <https://code.claude.com/docs/en/headless>
|
|
10
|
+
and <https://code.claude.com/docs/en/cli-reference>.
|
|
11
|
+
|
|
12
|
+
**Availability probe**: `command -v claude >/dev/null 2>&1`; record
|
|
13
|
+
`claude --version` as evidence. The probe is necessary, not sufficient:
|
|
14
|
+
`claude -p` needs network access to the Anthropic API, so a network-denying
|
|
15
|
+
sandbox (e.g. Codex CLI's default `workspace-write`) fails the spawn even
|
|
16
|
+
though the binary resolves. A failed spawn is the same fail-closed class as a
|
|
17
|
+
failed probe (`_shared/engine-preflight.md`): explicit route →
|
|
18
|
+
`BLOCKED:claude-unavailable`; automatic escalation → solo + reported skip.
|
|
19
|
+
|
|
20
|
+
**Read-only pair-JUDGE call** (bounded by the VERIFY pair contract — at most
|
|
21
|
+
two targeted probes; the invoking phase sets `--effort`, pair-JUDGE uses
|
|
22
|
+
`medium`):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
claude -p "<judge prompt>" \
|
|
26
|
+
--permission-mode dontAsk \
|
|
27
|
+
--allowedTools "Read,Grep,Glob,Bash(<repo test command> *)" \
|
|
28
|
+
--setting-sources project --strict-mcp-config --mcp-config '{"mcpServers":{}}' \
|
|
29
|
+
--effort medium \
|
|
30
|
+
> .devlyn/claude-judge.stdout 2> .devlyn/claude-judge.stderr
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- Omit `--model` — the CLI's configured default is used (zero-touch, same
|
|
34
|
+
rule as codex-config.md's "omit `-m`").
|
|
35
|
+
- `dontAsk` denies anything not allowlisted (official guide: "denies
|
|
36
|
+
anything not in your permissions.allow rules or the read-only command
|
|
37
|
+
set — useful for locked-down CI runs"); in `-p` mode a denied tool call
|
|
38
|
+
fails without prompting, so the child can never hang on a permission ask.
|
|
39
|
+
- The `--setting-sources project --strict-mcp-config --mcp-config
|
|
40
|
+
'{"mcpServers":{}}'` trio is the hermetic-child pattern: no user-global
|
|
41
|
+
settings, hooks, or MCP servers. `--bare` is NOT used — it skips
|
|
42
|
+
OAuth/keychain reads and requires `ANTHROPIC_API_KEY`, which
|
|
43
|
+
subscription-auth machines do not have.
|
|
44
|
+
- Findings come back as JSONL on stdout (same emission shape as the Codex
|
|
45
|
+
direction); the orchestrator writes the canonical
|
|
46
|
+
`.devlyn/verify.pair.findings.jsonl`. Raw stdout stays diagnostic at
|
|
47
|
+
`.devlyn/claude-judge.stdout`; `verify-merge-findings.py` blocks the run
|
|
48
|
+
if stdout contains findings the canonical file lacks.
|
|
49
|
+
- Do not pipe stdout (`| tail`, `| grep`); capture to file. Non-zero exit or
|
|
50
|
+
empty stdout → spawn failure, fail closed per the probe rule above.
|
|
51
|
+
|
|
5
52
|
## Identity
|
|
6
53
|
|
|
7
|
-
You are Claude
|
|
54
|
+
You are Claude by Anthropic. Anthropic's prompt-engineering guide for this model governs your behavior on top of the canonical phase prompt below. When the canonical body and this header conflict on tactics, the canonical body wins on what to deliver; this header wins on how to deliver it.
|
|
8
55
|
|
|
9
56
|
## Output discipline
|
|
10
57
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# oh-my-pi adapter
|
|
2
|
+
|
|
3
|
+
## Identity
|
|
4
|
+
|
|
5
|
+
You are the model running behind the `omp` CLI. Follow the canonical phase prompt exactly; this adapter exists so oh-my-pi can be selected as a first-class native task worker by `/devlyn:resolve`.
|
|
6
|
+
|
|
7
|
+
## Output Discipline
|
|
8
|
+
|
|
9
|
+
Keep phase output machine-consumable and concise. Write the artifacts the canonical body names, update `.devlyn/pipeline.state.json` only where that phase owns the state write, and avoid narrative outside the required phase result.
|
|
10
|
+
|
|
11
|
+
## Validation Pattern
|
|
12
|
+
|
|
13
|
+
When the canonical body names deterministic commands, run those commands and trust their exit codes. Do not replace BUILD_GATE, phase gates, or VERIFY mechanical checks with same-context judgment.
|
|
14
|
+
|
|
15
|
+
## Fresh Context
|
|
16
|
+
|
|
17
|
+
Each native `task` tool invocation with a fresh `context` is a fresh phase worker. Do not call `/devlyn:resolve` recursively from inside a phase prompt; execute only the phase body you received.
|
|
@@ -56,7 +56,9 @@ PER_RUN_PATTERNS = (
|
|
|
56
56
|
# ("pair_judge findings archive distinguishable") would false-fail on
|
|
57
57
|
# every paired fixture without this glob.
|
|
58
58
|
"verify-judge-*.md",
|
|
59
|
-
|
|
59
|
+
# *-judge.*: every engine's pair-judge stdout/stderr capture
|
|
60
|
+
# (codex-judge.*, claude-judge.* — adapters/claude.md ## Invocation).
|
|
61
|
+
"*-judge.*",
|
|
60
62
|
)
|
|
61
63
|
|
|
62
64
|
SAFE_RUN_ID_RE = re.compile(r"^[A-Za-z0-9_.-]+$")
|
|
@@ -140,12 +142,14 @@ def self_test() -> int:
|
|
|
140
142
|
"verify-merge.summary.json",
|
|
141
143
|
"codex-judge.stdout",
|
|
142
144
|
"codex-judge.summary.json",
|
|
145
|
+
"claude-judge.stdout",
|
|
146
|
+
"claude-judge.stderr",
|
|
143
147
|
):
|
|
144
148
|
(devlyn / name).write_text("{}\n", encoding="utf-8")
|
|
145
149
|
run_id = read_run_id(devlyn)
|
|
146
150
|
assert run_id == "run-1", run_id
|
|
147
151
|
moved = move_artifacts(devlyn, devlyn / "runs" / run_id)
|
|
148
|
-
assert moved >=
|
|
152
|
+
assert moved >= 8, moved
|
|
149
153
|
for name in (
|
|
150
154
|
"pipeline.state.json",
|
|
151
155
|
"risk-probes.jsonl",
|
|
@@ -153,6 +157,8 @@ def self_test() -> int:
|
|
|
153
157
|
"verify-merge.summary.json",
|
|
154
158
|
"codex-judge.stdout",
|
|
155
159
|
"codex-judge.summary.json",
|
|
160
|
+
"claude-judge.stdout",
|
|
161
|
+
"claude-judge.stderr",
|
|
156
162
|
):
|
|
157
163
|
assert (devlyn / "runs" / run_id / name).is_file(), name
|
|
158
164
|
|
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
# Shared — Codex Invocation
|
|
2
2
|
|
|
3
|
-
Single source of truth for how every skill calls Codex. **MCP is not used.** Skills shell out via the wrapper at `_shared/codex-monitored.sh`, which fronts the local Codex CLI
|
|
3
|
+
Single source of truth for how every skill calls Codex. **MCP is not used.** Skills shell out via the wrapper at `$CODEX_MONITORED_PATH`, resolved from the invoked skill's sibling `_shared/codex-monitored.sh`, which fronts the local Codex CLI.
|
|
4
4
|
|
|
5
5
|
## Canonical invocations
|
|
6
6
|
|
|
7
7
|
All long-running Codex calls go through `codex-monitored.sh` — a thin wrapper that closes stdin (codex 0.124.0 hangs when both stdin is open and a prompt arg is given), streams Codex stdout fully (no `tail -n` truncation), and prints a `[codex-monitored] heartbeat` line every 30s so the outer `claude -p` byte-watchdog stays fed during long reasoning gaps. The wrapper passes its arguments through verbatim to the underlying CLI, so the canonical flag set is unchanged from a raw call — only the launcher differs.
|
|
8
8
|
|
|
9
|
+
Before the first Codex call, resolve the wrapper from the invoked skill directory:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
DEVLYN_SKILL_DIR="${CLAUDE_SKILL_DIR:-__DEVLYN_SKILL_DIR__}"
|
|
13
|
+
if [ "$DEVLYN_SKILL_DIR" = "__DEVLYN_SKILL_DIR__" ] || [ ! -d "$DEVLYN_SKILL_DIR/../_shared" ]; then
|
|
14
|
+
echo "BLOCKED:shared-dir-unresolved: $DEVLYN_SKILL_DIR/../_shared" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
DEVLYN_SHARED_DIR="$(cd "$DEVLYN_SKILL_DIR/../_shared" && pwd)"
|
|
18
|
+
CODEX_MONITORED_PATH="$DEVLYN_SHARED_DIR/codex-monitored.sh"
|
|
19
|
+
if [ ! -f "$CODEX_MONITORED_PATH" ]; then
|
|
20
|
+
echo "BLOCKED:shared-dir-unresolved: $CODEX_MONITORED_PATH" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
```
|
|
24
|
+
|
|
9
25
|
**Read-only critique / adversarial review / debate** (`/devlyn:resolve` VERIFY pair-mode, plus any future ideate read-only critique). Security review stays native to Claude Code BUILD_GATE. Codex returns findings on stdout; the orchestrator writes files.
|
|
10
26
|
|
|
11
27
|
```bash
|
|
12
|
-
CODEX_MONITORED_ISOLATED=1 bash
|
|
28
|
+
CODEX_MONITORED_ISOLATED=1 bash "$CODEX_MONITORED_PATH" \
|
|
13
29
|
-C <project-root> \
|
|
14
30
|
-s read-only \
|
|
15
31
|
-c model_reasoning_effort=xhigh \
|
|
@@ -19,16 +35,16 @@ CODEX_MONITORED_ISOLATED=1 bash .claude/skills/_shared/codex-monitored.sh \
|
|
|
19
35
|
**Workspace-write implementation** (`/devlyn:resolve` IMPLEMENT phase when `--engine codex` or `--engine auto` routes to Codex, plus codex-routed `/devlyn:ideate` phases):
|
|
20
36
|
|
|
21
37
|
```bash
|
|
22
|
-
bash
|
|
38
|
+
bash "$CODEX_MONITORED_PATH" \
|
|
23
39
|
-C <project-root> \
|
|
24
|
-
|
|
40
|
+
-s workspace-write \
|
|
25
41
|
-c model_reasoning_effort=xhigh \
|
|
26
42
|
"<inlined-prompt>"
|
|
27
43
|
```
|
|
28
44
|
|
|
29
45
|
Notes:
|
|
30
46
|
- `-C` — project root so Codex's working directory matches.
|
|
31
|
-
- `-s read-only` /
|
|
47
|
+
- `-s read-only` / `-s workspace-write` — sandbox policy. Use workspace-write for implementation/probe phases that write tracked files or `.devlyn` artifacts.
|
|
32
48
|
- `-c model_reasoning_effort=xhigh` — config override for reasoning depth. Required for deep critique; skills may choose `high` or `medium` when thoroughness doesn't warrant xhigh.
|
|
33
49
|
- **Omit `-m <model>`** — Codex CLI uses its configured flagship (currently `gpt-5.5`, automatically whatever ships next). This is the zero-touch mechanism. Only name `-m` when a role explicitly needs a different model (e.g., `gpt-5.3-codex` for SWE-bench-heavy coding tasks, `gpt-5.3-codex-spark` for speed).
|
|
34
50
|
- `CODEX_MONITORED_ISOLATED=1` — required for bounded read-only critique/probe/judge calls. The wrapper adds `--ignore-user-config --ignore-rules --ephemeral --disable codex_hooks --disable hooks` so user config, AGENTS.md, hooks, and project rules cannot add hidden context, tool calls, or transcript side effects. Do not set it for workspace-write implementation phases.
|
|
@@ -52,4 +68,4 @@ The local Codex CLI (fronted by `codex-monitored.sh`) is the primary (and only)
|
|
|
52
68
|
|
|
53
69
|
Skills write the invocation as a Bash command the runtime executes. Example shape from `/devlyn:resolve` PHASE 2 IMPLEMENT when routed to Codex:
|
|
54
70
|
|
|
55
|
-
> Run `bash
|
|
71
|
+
> Run `bash "$CODEX_MONITORED_PATH" -C <state.base_ref.repo_root> -s workspace-write -c model_reasoning_effort=xhigh "<IMPLEMENT prompt>"`. Omit `-m` so the CLI flagship is auto-selected. Capture stdout as the IMPLEMENT reply; non-zero exit → treat as subagent failure. The wrapper emits `[codex-monitored]` heartbeat and lifecycle lines on **stderr** — stdout stays clean for Codex output, so the orchestrator can parse the reply without filtering. Heartbeat-on-stderr keeps the orchestrator's combined-output stream non-silent (defeats the iter-0008 byte-watchdog kill) without polluting the codex-reply view of stdout. Do not pipe the wrapper; direct capture or file redirection preserves streaming and avoids the pipe-refusal exit.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# engine-doctor.sh — read-only detection for /devlyn:engines' no-arg output.
|
|
3
|
+
#
|
|
4
|
+
# WHY (iter-0050): users need to see what's actually on the machine, not
|
|
5
|
+
# just what's pinned. This script never writes .devlyn/engines.json, never
|
|
6
|
+
# installs anything, never changes pin validation — it only reports.
|
|
7
|
+
#
|
|
8
|
+
# CATALOG NOTE: `pi` (bin/devlyn.js's Pi/earendil-works install target) has
|
|
9
|
+
# no verified CLI binary name anywhere in this repo — its own installer
|
|
10
|
+
# detect() only checks project marker files, never shells out to a binary.
|
|
11
|
+
# Fabricating a `command -v pi` check would be a guess this repo's evidence
|
|
12
|
+
# rule forbids, so its binary column reports "unknown" rather than "no".
|
|
13
|
+
#
|
|
14
|
+
# macOS-safe bash 3.2: no associative arrays, no mapfile/readarray.
|
|
15
|
+
set -uo pipefail
|
|
16
|
+
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
|
18
|
+
ADAPTERS_DIR="$SCRIPT_DIR/adapters"
|
|
19
|
+
|
|
20
|
+
TARGETS=(claude codex omp pi)
|
|
21
|
+
KINDS=(cli-engine cli-engine cli-engine orchestrator-only)
|
|
22
|
+
BINARIES=(claude codex omp "")
|
|
23
|
+
INSTALL_HINTS=(
|
|
24
|
+
"see https://docs.anthropic.com/en/docs/claude-code"
|
|
25
|
+
"npm install -g @openai/codex"
|
|
26
|
+
"brew install can1357/tap/omp"
|
|
27
|
+
""
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
check_binary() {
|
|
31
|
+
# $1 = binary name, "" means no known binary to probe
|
|
32
|
+
[ -n "$1" ] || { printf 'unknown'; return; }
|
|
33
|
+
if command -v "$1" >/dev/null 2>&1; then printf 'yes'; else printf 'no'; fi
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
check_adapter() {
|
|
37
|
+
# $1 = target name
|
|
38
|
+
if [ -f "$ADAPTERS_DIR/$1.md" ]; then printf 'yes'; else printf 'no'; fi
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
check_role() {
|
|
42
|
+
# $1 = adapter file path. Reads the optional `## Role eligibility` fixed
|
|
43
|
+
# ASCII fields (_shared/adapters/README.md); absent file or section = both
|
|
44
|
+
# roles eligible by default (every adapter shipped before iter-0051).
|
|
45
|
+
[ -f "$1" ] || { printf 'n/a'; return; }
|
|
46
|
+
local exec_ok='yes' judge_ok='yes'
|
|
47
|
+
grep -q '^executor: no' "$1" 2>/dev/null && exec_ok='no'
|
|
48
|
+
grep -q '^pair_judge: no' "$1" 2>/dev/null && judge_ok='no'
|
|
49
|
+
if [ "$exec_ok" = 'yes' ] && [ "$judge_ok" = 'yes' ]; then printf 'executor+judge'
|
|
50
|
+
elif [ "$judge_ok" = 'yes' ]; then printf 'judge-only'
|
|
51
|
+
elif [ "$exec_ok" = 'yes' ]; then printf 'executor-only'
|
|
52
|
+
else printf 'none'
|
|
53
|
+
fi
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
printf '%-8s %-17s %-8s %-8s %-14s %-12s %s\n' \
|
|
57
|
+
'target' 'kind' 'binary' 'adapter' 'role' 'pin_eligible' 'note'
|
|
58
|
+
|
|
59
|
+
pin_eligible_count=0
|
|
60
|
+
missing_hints=()
|
|
61
|
+
|
|
62
|
+
for i in "${!TARGETS[@]}"; do
|
|
63
|
+
target="${TARGETS[$i]}"
|
|
64
|
+
kind="${KINDS[$i]}"
|
|
65
|
+
binary="$(check_binary "${BINARIES[$i]}")"
|
|
66
|
+
adapter="$(check_adapter "$target")"
|
|
67
|
+
role="$(check_role "$ADAPTERS_DIR/$target.md")"
|
|
68
|
+
|
|
69
|
+
pin_eligible='no'
|
|
70
|
+
case "$kind" in
|
|
71
|
+
cli-engine) [ "$binary" = 'yes' ] && [ "$adapter" = 'yes' ] && pin_eligible='yes' ;;
|
|
72
|
+
esac
|
|
73
|
+
|
|
74
|
+
note='-'
|
|
75
|
+
case "$kind" in
|
|
76
|
+
cli-engine)
|
|
77
|
+
if [ "$pin_eligible" = 'yes' ]; then
|
|
78
|
+
pin_eligible_count=$((pin_eligible_count + 1))
|
|
79
|
+
elif [ "$binary" = 'no' ]; then
|
|
80
|
+
note="not installed; ${INSTALL_HINTS[$i]}"
|
|
81
|
+
missing_hints+=("$target: ${INSTALL_HINTS[$i]}")
|
|
82
|
+
else
|
|
83
|
+
note="binary present, no adapter — ship _shared/adapters/$target.md"
|
|
84
|
+
fi
|
|
85
|
+
;;
|
|
86
|
+
orchestrator-only)
|
|
87
|
+
note='informational only; not a routable role engine — no verified CLI binary or adapter'
|
|
88
|
+
;;
|
|
89
|
+
esac
|
|
90
|
+
|
|
91
|
+
printf '%-8s %-17s %-8s %-8s %-14s %-12s %s\n' \
|
|
92
|
+
"$target" "$kind" "$binary" "$adapter" "$role" "$pin_eligible" "$note"
|
|
93
|
+
done
|
|
94
|
+
|
|
95
|
+
printf '\n'
|
|
96
|
+
if [ "$pin_eligible_count" -lt 2 ]; then
|
|
97
|
+
printf 'Recommendation: only %d adapter-valid engine(s) available. VERIFY pair-judge and\n' "$pin_eligible_count"
|
|
98
|
+
printf 'risk-probe escalation need a second, genuinely different model to check against —\n'
|
|
99
|
+
printf 'iter-0045 found different model tiers hit different failure-mode blind spots (not\n'
|
|
100
|
+
printf 'that any arbitrary second model helps); with fewer than 2, those routes can never\n'
|
|
101
|
+
printf 'fire and every run stays solo. Add one of:\n'
|
|
102
|
+
for hint in "${missing_hints[@]}"; do
|
|
103
|
+
printf ' - %s\n' "$hint"
|
|
104
|
+
done
|
|
105
|
+
else
|
|
106
|
+
printf 'Pair-judge diversity: %d adapter-valid engines available.\n' "$pin_eligible_count"
|
|
107
|
+
fi
|
|
@@ -11,21 +11,28 @@ Engine requirements have two classes:
|
|
|
11
11
|
- **Explicit routes** — `--engine`, `--risk-probes`, `--pair-verify`. These are promises. If the required engine is unavailable, fail closed with `BLOCKED:<engine>-unavailable` and never downgrade to solo.
|
|
12
12
|
- **Automatic escalations** — auto high-risk risk-probes and auto VERIFY pair-JUDGE inferred from the spec. These are candidate routes, selected only when their preconditions hold, OTHER-engine availability included. If an auto candidate would fire but the OTHER engine is absent, do not select the cross-engine route: proceed solo and report the skipped escalation and its reason. This is route selection, not a fallback.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
## Role resolution
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
Two configurable roles; the orchestrator role is whichever CLI the user opened and is not configurable here.
|
|
17
|
+
|
|
18
|
+
- **Executor** (IMPLEMENT / CLEANUP / primary VERIFY judge): explicit `--engine` flag > `cwd/.devlyn/engines.json` `executor` > built-in default `claude`. No parent-directory or global config lookup. Requires an adapter that is executor-eligible (below).
|
|
19
|
+
- **Pair judge** (VERIFY pair-JUDGE, risk-probe derivation): first entry of `engines.json` `pair_judge_priority` that is (a) adapter-valid, (b) pair-judge-eligible (below), (c) not the primary judge engine, (d) available. When the key or file is absent: the binary claude↔codex complement.
|
|
20
|
+
|
|
21
|
+
Validation is fail-closed: malformed JSON, any engine name without a `_shared/adapters/<name>.md` adapter file under `DEVLYN_SHARED_DIR`, or an adapter that declares itself ineligible for the requested role, halts with report-level `BLOCKED:invalid-engine-config` naming the offending entry. This is the plug-in point for new engines — ship an adapter file and the name becomes valid with zero skill-body changes. Availability probe default is `command -v <name>`; an adapter may declare a different probe. A pin is an explicit route (same promise class as `--engine`): pinned-but-unavailable → `BLOCKED:<engine>-unavailable`, never a silent downgrade. `engines.json` is machine-local config — not committed, not archived (`archive_run.py` moves only `PER_RUN_PATTERNS`).
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
**Role eligibility** — an adapter's optional `## Role eligibility` section (`_shared/adapters/README.md`) declares `executor: yes|no` / `pair_judge: yes|no` as fixed ASCII fields. Absent section = eligible for both roles (every adapter shipped before iter-0051). A completion-only local backend with no tool/file-edit access declares `executor: no`: an `executor <name>` pin against it is invalid config, not an availability gap, and fails the same `BLOCKED:invalid-engine-config` path above rather than `BLOCKED:<name>-unavailable`.
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
The explicit-vs-automatic split above applies unchanged to resolved engines: explicit routes (`--engine`, `--risk-probes`, `--pair-verify`, config pins) fail closed; automatic escalations select a cross-engine route only when the resolved pair-judge engine is available, else proceed solo and report the skip.
|
|
26
|
+
|
|
27
|
+
When a run or phase requires engine `<name>` (claude, codex, omp, or any adapter-backed engine), before spawning that phase:
|
|
28
|
+
|
|
29
|
+
1. Run the adapter's availability probe — default `command -v <name> >/dev/null 2>&1`; an adapter's `## Invocation` section may declare a stricter contract (e.g. `adapters/claude.md`: the probe is necessary but not sufficient under a network-denying sandbox, so a failed spawn is handled identically to a failed probe).
|
|
30
|
+
2. On failure -> set the current phase/run verdict to `BLOCKED:<name>-unavailable`, preserve the failed check evidence, and show the adapter's setup guidance: install/configure that CLI, run its auth/login flow, verify `<name> --version`, then rerun. If the user intentionally wants solo VERIFY, they may rerun with `--no-pair`.
|
|
31
|
+
3. On success -> proceed with the original engine value.
|
|
25
32
|
|
|
26
33
|
Never prompt the user mid-pipeline. Missing engines for explicit routes are BLOCKED states, not silent fallbacks. Missing OTHER engines for automatic escalations are reported solo-skips, not fallbacks — the auto route was never selected.
|
|
27
34
|
|
|
28
|
-
Per-skill defaults: `/devlyn:resolve` uses Claude for PLAN/IMPLEMENT; VERIFY may invoke the OTHER engine when its pair-JUDGE trigger fires. `/devlyn:ideate` defaults to Claude; `--engine` selects the elicitation/normalization adapter, not an automatic cross-model challenge phase. Any future ideate read-only critique must follow `_shared/codex-config.md` isolation rules. Each SKILL.md flag block is source of truth for that skill's default.
|
|
35
|
+
Per-skill defaults: `/devlyn:resolve` uses Claude for PLAN/IMPLEMENT when the orchestrator has Claude Code's native Agent primitive; Codex CLI and oh-my-pi orchestrators use their own fresh worker as the default route (Codex child process, omp native `task`) so the pipeline still has structural phase isolation. VERIFY may invoke the OTHER engine when its pair-JUDGE trigger fires. `/devlyn:ideate` defaults to Claude; `--engine` selects the elicitation/normalization adapter, not an automatic cross-model challenge phase. Any future ideate read-only critique must follow `_shared/codex-config.md` isolation rules. Each SKILL.md flag block is source of truth for that skill's default.
|
|
29
36
|
|
|
30
37
|
## What a skill must report after a BLOCKED engine check
|
|
31
38
|
|
|
@@ -39,6 +46,6 @@ Setup: install/configure Codex CLI; run the current Codex auth/login flow; verif
|
|
|
39
46
|
|
|
40
47
|
Do not report a downgraded successful run when a required engine is missing.
|
|
41
48
|
|
|
42
|
-
## Canonical
|
|
49
|
+
## Canonical cross-engine invocations
|
|
43
50
|
|
|
44
|
-
|
|
51
|
+
After the availability check passes: a Claude-side orchestrator spawning a Codex judge/worker uses `config/skills/_shared/codex-config.md` (monitored wrapper, canonical flag set); a non-Claude orchestrator spawning a Claude judge uses `_shared/adapters/claude.md` `## Invocation` (headless `claude -p`, locked-down allowlist, hermetic settings).
|
|
@@ -106,6 +106,32 @@
|
|
|
106
106
|
"description": "Hard cap on new entries under dependencies/devDependencies in package.json. Exceeds → DQ.",
|
|
107
107
|
"minimum": 0,
|
|
108
108
|
"default": 0
|
|
109
|
+
},
|
|
110
|
+
"pure_design": {
|
|
111
|
+
"type": "boolean",
|
|
112
|
+
"description": "Declares that every Requirement is pure-design (no observable runtime check). Lets verification_commands stay empty without failing shape validation. Contradictory (and rejected) when true alongside a non-empty verification_commands.",
|
|
113
|
+
"default": false
|
|
114
|
+
},
|
|
115
|
+
"required_risk_probe_requirements": {
|
|
116
|
+
"type": "array",
|
|
117
|
+
"description": "Language-neutral replacement for prose-keyword risk-probe classification: explicit {tag, derived_from} obligations the spec author declares directly. When `--risk-probes` is enabled, risk-probes.jsonl must include a probe whose tags include `tag` and whose derived_from exactly equals `derived_from` for each entry.",
|
|
118
|
+
"items": {
|
|
119
|
+
"type": "object",
|
|
120
|
+
"additionalProperties": false,
|
|
121
|
+
"required": ["tag", "derived_from"],
|
|
122
|
+
"properties": {
|
|
123
|
+
"tag": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"description": "One of the risk-probe tags defined in _shared/spec-verify-check.py:RISK_PROBE_TAGS (e.g. ordering_inversion, rollback_state, shape_contract)."
|
|
126
|
+
},
|
|
127
|
+
"derived_from": {
|
|
128
|
+
"type": "string",
|
|
129
|
+
"description": "Exact substring of the sibling spec.md's visible verification section that demands this tag.",
|
|
130
|
+
"minLength": 1
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"default": []
|
|
109
135
|
}
|
|
110
136
|
}
|
|
111
137
|
}
|