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.
Files changed (81) hide show
  1. package/AGENTS.md +10 -72
  2. package/CLAUDE.md +29 -12
  3. package/README.md +7 -5
  4. package/benchmark/auto-resolve/fixtures/F1-cli-trivial-flag/task.ko.txt +8 -0
  5. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/NOTES.md +28 -0
  6. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/RETIRED.md +15 -0
  7. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/expected.json +57 -0
  8. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/metadata.json +10 -0
  9. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/setup.sh +50 -0
  10. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/spec.md +59 -0
  11. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/task.txt +9 -0
  12. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/verifiers/ordered-settlement.js +88 -0
  13. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/verifiers/reject-invalid.js +45 -0
  14. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/NOTES.md +30 -0
  15. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/RETIRED.md +16 -0
  16. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/expected.json +64 -0
  17. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/metadata.json +10 -0
  18. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/setup.sh +21 -0
  19. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/spec.md +58 -0
  20. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/task.txt +9 -0
  21. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/dup-op.js +54 -0
  22. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/priority-apply.js +82 -0
  23. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/rollback.js +77 -0
  24. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/NOTES.md +99 -0
  25. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/RETIRED.md +18 -0
  26. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/expected.json +64 -0
  27. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/metadata.json +10 -0
  28. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/setup.sh +4 -0
  29. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/spec.md +61 -0
  30. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/task.txt +9 -0
  31. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/correctness-small.js +135 -0
  32. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/scale.js +100 -0
  33. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/validation.js +75 -0
  34. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/NOTES.md +93 -0
  35. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/RETIRED.md +16 -0
  36. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/expected.json +65 -0
  37. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/metadata.json +10 -0
  38. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/setup.sh +18 -0
  39. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/spec.md +66 -0
  40. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/task.txt +9 -0
  41. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/correctness-small.js +84 -0
  42. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/scale.js +184 -0
  43. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/validation.js +74 -0
  44. package/benchmark/auto-resolve/scripts/pair-rejected-fixtures.sh +12 -0
  45. package/benchmark/auto-resolve/scripts/run-fixture.sh +39 -1
  46. package/benchmark/auto-resolve/scripts/test-lint-fixtures.sh +1 -1
  47. package/bin/devlyn.js +71 -83
  48. package/config/skills/_shared/adapters/README.md +7 -0
  49. package/config/skills/_shared/adapters/claude.md +49 -2
  50. package/config/skills/_shared/adapters/omp.md +17 -0
  51. package/config/skills/_shared/archive_run.py +8 -2
  52. package/config/skills/_shared/codex-config.md +22 -6
  53. package/config/skills/_shared/engine-doctor.sh +107 -0
  54. package/config/skills/_shared/engine-preflight.md +18 -11
  55. package/config/skills/_shared/expected.schema.json +26 -0
  56. package/config/skills/_shared/pair-plan-schema.md +2 -2
  57. package/config/skills/_shared/runtime-principles.md +1 -1
  58. package/config/skills/_shared/spec-verify-check.py +773 -486
  59. package/config/skills/_shared/state-phase-write.py +374 -0
  60. package/config/skills/_shared/verify-merge-findings.py +177 -61
  61. package/config/skills/devlyn:engines/SKILL.md +47 -0
  62. package/config/skills/devlyn:ideate/SKILL.md +22 -3
  63. package/config/skills/devlyn:ideate/references/elicitation.md +2 -2
  64. package/config/skills/devlyn:ideate/references/from-spec-mode.md +2 -2
  65. package/config/skills/devlyn:ideate/references/project-mode.md +1 -1
  66. package/config/skills/devlyn:ideate/references/spec-template.md +15 -4
  67. package/config/skills/devlyn:queue/SKILL.md +26 -0
  68. package/config/skills/devlyn:resolve/SKILL.md +55 -25
  69. package/config/skills/devlyn:resolve/references/free-form-mode.md +1 -1
  70. package/config/skills/devlyn:resolve/references/phases/build-gate.md +2 -2
  71. package/config/skills/devlyn:resolve/references/phases/cleanup.md +1 -1
  72. package/config/skills/devlyn:resolve/references/phases/implement.md +1 -1
  73. package/config/skills/devlyn:resolve/references/phases/plan.md +3 -2
  74. package/config/skills/devlyn:resolve/references/phases/probe-derive.md +8 -0
  75. package/config/skills/devlyn:resolve/references/phases/verify.md +18 -9
  76. package/config/skills/devlyn:resolve/references/state-schema.md +11 -7
  77. package/optional-skills/devlyn:pencil-pull/SKILL.md +5 -0
  78. package/optional-skills/devlyn:pencil-push/SKILL.md +5 -0
  79. package/package.json +1 -2
  80. package/scripts/lint-skills.sh +55 -46
  81. 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 4 prompt optimization using Anthropic best practices', type: 'local' },
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 devlyn's previously-appended "# Devlyn Agent Instructions" block from an
600
- // instruction file (or the packaged base) before re-appending, so installs stay
601
- // idempotent and never duplicate the block. Anchors on the LAST marker, not the
602
- // first: the packaged AGENTS.md ships its own "# Devlyn Agent Instructions"
603
- // section, so indexOf would truncate that real content — lastIndexOf targets the
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 marker = '# Devlyn Agent Instructions';
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 — "Gemini" (instructions only) must not look
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 only the instruction file(s) for a CLI (no skills). Returns true when
627
- // instruction content was written. Split from skills so the de-dup driver can
628
- // write a shared destination (e.g. project AGENTS.md for codex/omp/pi) once.
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 agents = fs.existsSync(AGENTS_SOURCE)
634
- ? fs.readdirSync(AGENTS_SOURCE).filter((f) => f.endsWith('.md'))
635
- : [];
636
- if (agents.length === 0) return false;
637
-
638
- log(`\n🤖 Installing agent instructions for ${cli.name}...`, 'cyan');
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
- // CLI uses a single instructions file — append agent content
654
- const destFile = path.join(process.cwd(), cli.instructionsFile);
655
- const separator = '\n\n---\n\n# Devlyn Agent Instructions\n\n';
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.configDir ? `dir:${cli.configDir}` : `file:${cli.instructionsFile}`;
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 Opus adapter
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 Opus 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.
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
- "codex-judge.*",
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 >= 6, 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 (shipped by the `openai-codex` Claude Code plugin).
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 .claude/skills/_shared/codex-monitored.sh \
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 .claude/skills/_shared/codex-monitored.sh \
38
+ bash "$CODEX_MONITORED_PATH" \
23
39
  -C <project-root> \
24
- --full-auto \
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` / `--full-auto` — sandbox policy. `--full-auto` = `-s workspace-write` with auto-approval of sandboxed commands.
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 .claude/skills/_shared/codex-monitored.sh -C <state.base_ref.repo_root> --full-auto -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.
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
- When a run or phase requires Codex, before spawning that phase:
14
+ ## Role resolution
15
15
 
16
- 1. Check if the Codex CLI is installed: `command -v codex >/dev/null 2>&1` (or equivalent bash test).
17
- 2. On failure -> set the current phase/run verdict to `BLOCKED:codex-unavailable`, preserve the failed check evidence, and show setup guidance: install/configure the Codex CLI, run the current Codex auth/login flow, verify `codex --version`, then rerun. If the user intentionally wants solo VERIFY, they may rerun with `--no-pair`.
18
- 3. On success -> proceed with the original engine value.
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
- When a run or phase requires Claude, before spawning that phase:
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
- 1. Confirm the runtime can spawn Claude agents. Where the CLI is the launcher, `command -v claude >/dev/null 2>&1` is the equivalent check.
23
- 2. On failure -> set the current phase/run verdict to `BLOCKED:claude-unavailable` and show setup guidance: install/configure Claude Code, verify `claude --version` where available, then rerun.
24
- 3. On success -> proceed.
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 Codex invocation
49
+ ## Canonical cross-engine invocations
43
50
 
44
- See `config/skills/_shared/codex-config.md` for the canonical wrapper invocation and flag set skills should use after the availability check passes.
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
  }