devlyn-cli 3.1.2 → 3.1.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Codex Project Instructions
2
2
 
3
- devlyn-cli installs `/devlyn:ideate` (optional) and `/devlyn:resolve` (required) into Claude Code, plus the contract below. Codex CLI reads this file when invoked inside a project that has it. The principles are non-negotiable on every change.
3
+ devlyn-cli installs planning and full-pipeline skills alongside the execution contract below. Codex CLI reads this file when invoked inside a project that has it. The principles are non-negotiable on every change.
4
4
 
5
5
  ## North Star
6
6
 
@@ -27,22 +27,22 @@ Three discipline rules govern HOW the principles are applied:
27
27
  ## Quick Start
28
28
 
29
29
  ```text
30
- ideate (optional) -> resolve -> ship
30
+ inspect intent -> direct work or full resolve -> ship
31
31
  ```
32
32
 
33
33
  - `/devlyn:ideate` (optional) — unstructured idea → `docs/specs/<id>/spec.md` + `spec.expected.json`. Modes: default Q&A, `--quick` (autonomous-pipeline-safe), `--from-spec <path>`, `--project` (multi-feature).
34
- - `/devlyn:resolve` — hands-free pipeline for any coding task. Free-form goal, `--spec <path>`, or `--verify-only <ref> --spec <path>`. Phases run inline: PLAN → IMPLEMENT → BUILD_GATE → CLEANUP → VERIFY (fresh-subagent, findings-only).
34
+ - `/devlyn:resolve` — full pipeline for work selected by the conversational-entry rules below. Free-form goal, `--spec <path>`, or `--verify-only <ref> --spec <path>`. Phases: PLAN → IMPLEMENT → BUILD_GATE → CLEANUP → VERIFY (fresh-subagent, findings-only).
35
35
  - `/devlyn:design-ui` — required creative UI exploration surface. Spawns a 5-specialist design team (Creative Director, Product Designer, Visual Designer, Interaction Designer, Accessibility Designer) to generate N (default 5) portfolio-worthy HTML/CSS samples. The optional `/devlyn:reap` companion lives in `optional-skills/` and installs only when the user opts in.
36
36
 
37
37
  Each skill's `SKILL.md` is the source of truth for flags and workflow. Do not duplicate.
38
38
 
39
39
  Engine roles: orchestrator = whichever CLI the user opened (this contract is symmetric with CLAUDE.md. If you are Codex orchestrating `/devlyn:resolve`, the phase machinery is mandatory regardless of task size; if you will not run it, say so explicitly and stop — do not silently degrade to ad-hoc execution); legacy executor (with optional separate worker/primary/pair profiles) uses the canonical skill's orchestrator-supported default, overridable per run with `--engine <name>` or durably via machine-local `.devlyn/engines.json` `{"executor": "<name>", "pair_judge_priority": ["<name>", ...]}`; PLAN is orchestrator-fixed and never inherits `--engine` or the executor pin; pair judge = first available OTHER engine, default for VERIFY whenever available; risk probes remain conditional and --no-pair opts out. Pins fail closed (`BLOCKED:<engine>-unavailable` / `BLOCKED:invalid-engine-config`); new engines plug in by shipping `_shared/adapters/<name>.md`. `/devlyn:engines` (no args) shows the role table + detected engines; `executor <name>` / `pair <name>,...` / `clear` manage the pins. The executor pin binds the orchestrator in plain conversation too, not only inside a skill run: when you would implement directly and executor is pinned to a non-default engine, route that work through the pin — via `/devlyn:resolve` (reads it at PHASE 0) or by delegating to that engine; no pin / no file → use the canonical skill's orchestrator-supported default. The pair-judge pin stays pipeline-scoped. `/devlyn:engines role` configures explicit engine/model/effort for supported worker and judge routes; `/devlyn:resolve --role-config <path>` overrides roles for one run. Canonical skills define precedence, evidence and unsupported-route errors; absent profiles preserve existing defaults.
40
40
 
41
- Conversational entry: Before writes, inspect requested files and relevant callers/tests. Default to direct execution only for clear, local, reversible, low-risk work with a decisive existing or small task-specific acceptance check. Preserve requested behavior, scope, explicit constraints and the executor pin; make scoped edits, run required checks, review the final diff, and report changes with evidence. One concise route/check explanation suffices; ordinary direct work needs no spec, pipeline state, phase workers or extra approval. For direct/full/queue work, the outer owner reads `references/task-completion.md` relative to the installed `devlyn:resolve` skill's directory: prospective task-branch ownership (linked worktree optional), scoped commit acceptance, then PR/merge and recoverable owned-resource cleanup after required archive/queue commits. Local-only/no-push wins; delivery pending/failure stays separate from product verification.
41
+ Conversational entry: Before loading a workflow or writing files, inspect the requested change and relevant callers/tests. Quoted phase names, past logs and skill-file paths are context, not an instruction to invoke that workflow; follow the current user request. Default to direct execution when inspection makes the requested behavior, affected boundaries and verification tractable in the current context, including bounded multi-file work. Preserve requested behavior, scope, explicit constraints and the executor pin; make scoped edits, run required checks, review the final diff, and report changes with evidence. One concise route/check explanation suffices; ordinary direct work needs no spec, pipeline state, phase workers or extra approval. For direct/full/queue work, the outer owner reads `references/task-completion.md` relative to the installed `devlyn:resolve` skill's directory: prospective task-branch ownership (linked worktree optional), scoped commit acceptance, then PR/merge and recoverable owned-resource cleanup after required archive/queue commits. Local-only/no-push wins; delivery pending/failure stays separate from product verification.
42
42
 
43
- Use full `/devlyn:resolve` for material ambiguity, subsystem/design work, security/auth/payment/persistence/concurrency/public-API-contract changes, or inconclusive inspection/checks. Counts of lines, files or words alone do not establish low risk. Explicit resolve (even small), formal spec workflows and queue drains retain all canonical phases, independent verification, pins and failure handling. If new risk appears mid-edit, preserve the delta in the full run's scope/evidence; do not hide it in a new baseline or reconfirm existing authorization.
43
+ Use full `/devlyn:resolve` automatically only when inspection identifies interacting requirements or verification work too complex to manage reliably in the current context, such as coupled durable state, concurrent ownership and failure recovery across boundaries. Name the concrete interaction or verification gap. Domain labels (security/auth/payment/persistence/concurrency/API), file count and a spec document alone do not trigger it. Investigate or clarify missing intent/access first; a pipeline cannot supply them. Direct work still requires risk-proportionate checks and independent review for consequential changes. Explicit resolve (even small), explicit spec-mode workflows and queue drains retain all canonical phases, independent verification, pins and failure handling. If the full-route condition emerges mid-edit, carry the existing delta into its scope/evidence; do not reset the baseline or reconfirm existing authorization.
44
44
 
45
- Full-route handoff + loop engineering: the orchestrating model — not the user — invokes the skills. `/devlyn:queue` fronts the intent queue (`docs/specs/queue.md`): no args = status, `add <intent>`, `drain` = serial unattended drain per the contract below. Large agreed work is written to `docs/specs/<id>/spec.md` (always a spec file — the user's reviewed contract; free-form large assumes-and-logs instead of halting, zero-scope-signal goals still halt to ideate), summarized once for user review, then run hands-free via `--spec`. Outer loop per task: before the first full run, commit the scoped queue-item delta (when applicable) and linked spec bundle; findings-backed verdicts (NEEDS_WORK, verify/build-gate exhaustion) get a committed spec amendment before re-run, max 3 iterations; infrastructure / invalid-input / engine-availability / implement-empty BLOCKED verdicts surface immediately. Unattended queue drain (`docs/specs/queue.md`, strictly serial): assumptions may only take scope-narrowing, reversible, non-user-visible defaults — material ambiguity marks the item `[F] needs-review`; commit every terminal `[x]` / `[F]` transition before advancing. The canonical commit order is `config/skills/devlyn:resolve/references/outer-loop.md`.
45
+ Full-route handoff + loop engineering: the orchestrating model — not the user — invokes the skills. `/devlyn:queue` fronts the intent queue (`docs/specs/queue.md`): no args = status, `add <intent>`, `drain` = serial unattended drain per the contract below. Work selected for full resolve is written to `docs/specs/<id>/spec.md` (always a spec file — the user's reviewed contract; free-form large assumes-and-logs instead of halting, zero-scope-signal goals still halt to ideate), summarized once for user review, then run hands-free via `--spec`. Outer loop per task: before the first full run, commit the scoped queue-item delta (when applicable) and linked spec bundle; findings-backed verdicts (NEEDS_WORK, verify/build-gate exhaustion) get a committed spec amendment before re-run, max 3 iterations; infrastructure / invalid-input / engine-availability / implement-empty BLOCKED verdicts surface immediately. Unattended queue drain (`docs/specs/queue.md`, strictly serial): assumptions may only take scope-narrowing, reversible, non-user-visible defaults — material ambiguity marks the item `[F] needs-review`; commit every terminal `[x]` / `[F]` transition before advancing. The canonical commit order is `config/skills/devlyn:resolve/references/outer-loop.md`.
46
46
 
47
47
  ## Subtractive-First Editing
48
48
 
package/CLAUDE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Project Instructions
2
2
 
3
- devlyn-cli installs `/devlyn:ideate` (optional) and `/devlyn:resolve` (required) into Claude Code, plus the contract below. These principles are non-negotiable on every change — yours and any sub-agent's.
3
+ devlyn-cli installs planning and full-pipeline skills alongside the execution contract below. These principles are non-negotiable on every change — yours and any sub-agent's.
4
4
 
5
5
  ## North Star
6
6
 
@@ -28,10 +28,10 @@ The runtime sub-agent contract below (Subtractive-first / Goal-locked / No-worka
28
28
 
29
29
  ## Quick Start
30
30
 
31
- Engine selection follows the role map below.
31
+ Choose direct work or full resolve using the conversational-entry rules below; engine selection follows the role map.
32
32
 
33
33
  1. `/devlyn:ideate` (optional) — unstructured idea → `docs/specs/<id>/spec.md` + `spec.expected.json`. Modes: default Q&A, `--quick` (autonomous-pipeline-safe), `--from-spec <path>`, `--project`.
34
- 2. `/devlyn:resolve` — hands-free pipeline for any coding task. Free-form goal, `--spec <path>`, or `--verify-only <diff> --spec <path>`. Phases: PLAN → IMPLEMENT → BUILD_GATE → CLEANUP → VERIFY (fresh subagent, findings-only).
34
+ 2. `/devlyn:resolve` — full pipeline for work selected by the conversational-entry rules below. Free-form goal, `--spec <path>`, or `--verify-only <diff> --spec <path>`. Phases: PLAN → IMPLEMENT → BUILD_GATE → CLEANUP → VERIFY (fresh subagent, findings-only).
35
35
 
36
36
  Each skill's `SKILL.md` is the source of truth for its flags and workflow — don't duplicate them here.
37
37
 
@@ -51,13 +51,13 @@ Each skill's `SKILL.md` is the source of truth for its flags and workflow — do
51
51
 
52
52
  ### Conversational entry + full-route handoff
53
53
 
54
- Before writes, inspect requested files and relevant callers/tests. Default to direct execution only for clear, local, reversible, low-risk work with a decisive existing or small task-specific acceptance check. Preserve requested behavior, scope, explicit constraints and the executor pin; make scoped edits, run required checks, review the final diff, and report changes with evidence. One concise route/check explanation suffices; ordinary direct work needs no spec, pipeline state, phase workers or extra approval. For direct/full/queue work, the outer owner reads `references/task-completion.md` relative to the installed `devlyn:resolve` skill's directory: prospective task-branch ownership (linked worktree optional), scoped commit acceptance, then PR/merge and recoverable owned-resource cleanup after required archive/queue commits. Local-only/no-push wins; delivery pending/failure stays separate from product verification.
54
+ Before loading a workflow or writing files, inspect the requested change and relevant callers/tests. Quoted phase names, past logs and skill-file paths are context, not an instruction to invoke that workflow; follow the current user request. Default to direct execution when inspection makes the requested behavior, affected boundaries and verification tractable in the current context, including bounded multi-file work. Preserve requested behavior, scope, explicit constraints and the executor pin; make scoped edits, run required checks, review the final diff, and report changes with evidence. One concise route/check explanation suffices; ordinary direct work needs no spec, pipeline state, phase workers or extra approval. For direct/full/queue work, the outer owner reads `references/task-completion.md` relative to the installed `devlyn:resolve` skill's directory: prospective task-branch ownership (linked worktree optional), scoped commit acceptance, then PR/merge and recoverable owned-resource cleanup after required archive/queue commits. Local-only/no-push wins; delivery pending/failure stays separate from product verification.
55
55
 
56
- Use full `/devlyn:resolve` for material ambiguity, subsystem/design work, security/auth/payment/persistence/concurrency/public-API-contract changes, or inconclusive inspection/checks. Counts of lines, files or words alone do not establish low risk. Explicit resolve (even small), formal spec workflows and queue drains retain all canonical phases, independent verification, pins and failure handling. If new risk appears mid-edit, preserve the delta in the full run's scope/evidence; do not hide it in a new baseline or reconfirm existing authorization.
56
+ Use full `/devlyn:resolve` automatically only when inspection identifies interacting requirements or verification work too complex to manage reliably in the current context, such as coupled durable state, concurrent ownership and failure recovery across boundaries. Name the concrete interaction or verification gap. Domain labels (security/auth/payment/persistence/concurrency/API), file count and a spec document alone do not trigger it. Investigate or clarify missing intent/access first; a pipeline cannot supply them. Direct work still requires risk-proportionate checks and independent review for consequential changes. Explicit resolve (even small), explicit spec-mode workflows and queue drains retain all canonical phases, independent verification, pins and failure handling. If the full-route condition emerges mid-edit, carry the existing delta into its scope/evidence; do not reset the baseline or reconfirm existing authorization.
57
57
 
58
- The user does not invoke skills manually; the orchestrating model does. Large tasks agreed in conversation:
58
+ The user does not invoke skills manually; the orchestrating model does. For work selected for full resolve:
59
59
 
60
- 1. Write the agreed contract to `docs/specs/<id>/spec.md` (+ `spec.expected.json` when mechanical verifications exist). Always a spec file for large work — a written spec is the user's reviewed contract; free-form large now assumes-and-logs instead of halting (zero-scope-signal goals still halt to ideate).
60
+ 1. Write the agreed contract to `docs/specs/<id>/spec.md` (+ `spec.expected.json` when mechanical verifications exist). Use a spec file for the selected work — a written spec is the user's reviewed contract; free-form large now assumes-and-logs instead of halting (zero-scope-signal goals still halt to ideate).
61
61
  2. Present a one-screen plan-contract summary — the user's single review checkpoint, BEFORE the pipeline starts.
62
62
  3. On go-ahead, commit the scoped queue-item delta (when applicable) and linked spec bundle, then invoke `/devlyn:resolve --spec <path>` hands-free to completion.
63
63
  4. **Per-task outer loop**: read the terminal verdict. Successful normal-run archive → outer-owner completion per the canonical task-completion reference, after any terminal queue commit. Verdicts backed by spec/verification findings (NEEDS_WORK, verify/build-gate exhaustion) → adjudicate the findings, amend the spec (recorded in the spec file; spec stays read-only inside a run), commit that scoped amendment, then re-invoke — at most 3 outer iterations, then surface with the findings trail. Infrastructure, invalid-input, engine-availability, and implement-empty BLOCKED verdicts are not spec-amendable: surface immediately. Every iteration re-enters through committed durable artifacts (spec, findings, run archive), never conversation memory.
package/README.md CHANGED
@@ -31,12 +31,12 @@ That's it. The installer opens with a single **agent selector** — pick any com
31
31
 
32
32
  ---
33
33
 
34
- ## How It Works — Two Skills, Full Cycle
34
+ ## How It Works — Direct Work or Full Pipeline
35
35
 
36
- devlyn-cli turns your AI coding agent into a hands-free development pipeline. The pipeline surface is two skills, with `/devlyn:design-ui` installed as the required creative UI surface:
36
+ devlyn-cli supports direct execution for clear, low-risk work with decisive checks and a full pipeline for work that needs it. The pipeline surface is two skills, with `/devlyn:design-ui` installed as the required creative UI surface:
37
37
 
38
38
  ```
39
- ideate (optional) → resolve → ship
39
+ inspect intent → direct work or full resolve → ship
40
40
  ```
41
41
 
42
42
  Non-Claude agents (Codex / omp / Pi / Grok): when one of these is selected, the workflows install as that agent's skills. In Codex / omp / Pi, use `$devlyn:ideate`, `$devlyn:resolve`, or `$devlyn:design-ui`; in Grok, use `/devlyn:ideate`, `/devlyn:resolve`, or `/devlyn:design-ui`, the same slash-command form as Claude Code.
@@ -60,9 +60,9 @@ Default mode produces a `docs/specs/<id>-<slug>/spec.md` plus `spec.expected.jso
60
60
 
61
61
  Skip ideate entirely if you have a spec or just want to describe the work — `/devlyn:resolve` accepts free-form goals too.
62
62
 
63
- ### Step 2 — Resolve with `/devlyn:resolve`
63
+ ### Choose direct execution or `/devlyn:resolve`
64
64
 
65
- Before writes, inspect requested files and relevant callers/tests. Clear, local, reversible, low-risk conversational edits with a decisive acceptance check run directly, honoring scope, constraints and executor pins; finish required checks and diff review, then accept the scoped commit for owner completion below. Material ambiguity, subsystem/design work, security/auth/payment/persistence/concurrency/public-API-contract changes or no decisive check use full `/devlyn:resolve`. Explicit resolve (including small tasks), formal specs and queue drains keep the full workflow below. Pass a spec, a free-form goal, or a diff to verify.
65
+ Inspect the requested behavior, affected callers and tests first. Default to direct work when its scope and verification are tractable, including bounded multi-file changes; honor constraints and executor pins, run risk-proportionate checks and independent review for consequential changes, then review the diff and deliver. Automatically use full `/devlyn:resolve` only for concrete interacting requirements or verification too complex to manage reliably in the current context, such as coupled durable state, concurrent ownership and failure recovery. Domain labels, file count and a spec document alone do not trigger it. Investigate or clarify missing intent/access first. Explicit resolve (including small tasks), explicit spec-mode workflows and queue drains keep the full workflow below.
66
66
 
67
67
  ```
68
68
  /devlyn:resolve "fix the login bug" # free-form
@@ -227,6 +227,21 @@ ids.
227
227
 
228
228
  ### Migration from earlier versions
229
229
 
230
+ Reinstall with the new version to refresh instructions and skills for the selected
231
+ CLIs. Claude uses `CLAUDE.md`; Codex, Grok, omp and Pi share `AGENTS.md`.
232
+ Devlyn updates its checksum-marked instruction block and preserves project-specific
233
+ rules outside it. Keep custom rules outside the block; they take precedence over
234
+ its defaults. Exact pre-update backups are saved in `.devlyn/instructions/`.
235
+
236
+ Unmodified templates from versions 3.0.0–3.1.2 migrate automatically, including
237
+ custom text before or after the template. Edited or unrecognized older Devlyn
238
+ templates with recognizable Devlyn contract text, changed managed blocks and
239
+ ambiguous markers stop installation without replacing the original.
240
+ The error names an `.incoming` file with current defaults:
241
+ merge it, move custom rules outside its managed block, and rerun installation.
242
+ Plain custom instruction files retain their contents and gain a managed block.
243
+ Use 3.1.3 or newer consistently: older installers still overwrite `CLAUDE.md`.
244
+
230
245
  <!-- legacy-surface-map:begin — retired command names below are documented as OLD, not current; lint Check 10c skips this block -->
231
246
  Earlier versions of devlyn-cli shipped 16+ slash commands. The iter-0034 Phase 4 cutover (2026-05-04) and the 2026-05-14 follow-up consolidated them down to the three current commands. Upgrades automatically purge the legacy skill directories from `~/.claude/skills/`.
232
247
 
package/bin/devlyn.js CHANGED
@@ -9,6 +9,7 @@ const { execSync } = require('child_process');
9
9
  const CONFIG_SOURCE = path.join(__dirname, '..', 'config');
10
10
  const OPTIONAL_SKILLS_SOURCE = path.join(__dirname, '..', 'optional-skills');
11
11
  const PKG = require('../package.json');
12
+ const { updateInstructions } = require('./instructions');
12
13
 
13
14
  // The devlyn skill bundle installed into every skill-capable agent's loader
14
15
  // directory. Single source of truth so codex/omp/pi stay in lockstep — adding a
@@ -700,20 +701,6 @@ function installSkillsForCLI(cliKey) {
700
701
  return copied;
701
702
  }
702
703
 
703
- // Strip the legacy "# Devlyn Agent Instructions" block (the retired evaluator
704
- // agent) that earlier installs appended to instruction files, so upgrades scrub
705
- // it instead of carrying it forever. Anchors on the LAST marker so user content
706
- // above a stray copy of the block is preserved.
707
- const DEVLYN_AGENTS_MARKER = '# Devlyn Agent Instructions';
708
- function stripManagedBlock(content) {
709
- const markerIdx = content.lastIndexOf(DEVLYN_AGENTS_MARKER);
710
- if (markerIdx > 0) {
711
- const sepIdx = content.lastIndexOf('\n---', markerIdx);
712
- return content.slice(0, sepIdx > 0 ? sepIdx : markerIdx).trimEnd();
713
- }
714
- return content.trimEnd();
715
- }
716
-
717
704
  // One-line description of exactly what selecting a CLI target installs, so the
718
705
  // unified selector stays honest.
719
706
  function targetDesc(cli) {
@@ -723,35 +710,10 @@ function targetDesc(cli) {
723
710
  return `${cli.instructionsFile} instructions only`;
724
711
  }
725
712
 
726
- // Install the packaged base instruction file for a CLI (project AGENTS.md for
727
- // codex/omp/pi). Creates it from the packaged base when missing; on existing
728
- // files, scrubs the legacy appended block and otherwise leaves user content
729
- // untouched. Returns true when the destination was written.
713
+ // Shared instruction ownership and preservation rules apply to every CLI.
730
714
  function installInstructionsForCLI(cliKey) {
731
715
  const cli = CLI_TARGETS[cliKey];
732
- if (!cli || !cli.baseInstructionsFile) return false;
733
-
734
- const destFile = path.join(process.cwd(), cli.instructionsFile);
735
- const baseInstructionsSrc = path.join(__dirname, '..', cli.baseInstructionsFile);
736
- let content = null;
737
- if (fs.existsSync(destFile)) {
738
- const current = fs.readFileSync(destFile, 'utf8');
739
- if (current.includes(DEVLYN_AGENTS_MARKER)) {
740
- content = stripManagedBlock(current) + '\n';
741
- } else {
742
- log(` → Preserved existing ${cli.instructionsFile}. Compare with the bundled template at ${baseInstructionsSrc} and merge relevant instructions.`, 'dim');
743
- }
744
- } else {
745
- if (fs.existsSync(baseInstructionsSrc)) {
746
- content = fs.readFileSync(baseInstructionsSrc, 'utf8');
747
- }
748
- }
749
- if (content === null) return false;
750
-
751
- log(`\n🤖 Installing instructions for ${cli.name}...`, 'cyan');
752
- fs.writeFileSync(destFile, content);
753
- log(` → ${cli.instructionsFile}`, 'dim');
754
- return true;
716
+ return cli?.baseInstructionsFile ? updateInstructions(cli.baseInstructionsFile) : false;
755
717
  }
756
718
 
757
719
  // Install both instructions and skills for a single CLI. Used by the
@@ -803,8 +765,9 @@ function installAgentsForAllDetected() {
803
765
  // Install the Claude Code core config: project .claude/ (skills, templates,
804
766
  // settings, CLAUDE.md, .gitignore) plus global ~/.claude/settings.json tweaks.
805
767
  // Extracted so the unified target selector installs it only when "Claude Code"
806
- // is chosen. Behavior is unchanged from the previous unconditional core install.
768
+ // is chosen. Check instruction conflicts before changing settings or skills.
807
769
  function installClaudeCore() {
770
+ updateInstructions('CLAUDE.md');
808
771
  const targetDir = getTargetDir();
809
772
  const skillsDir = path.join(targetDir, 'skills');
810
773
  log('\n📁 Installing Claude Code config to .claude/', 'green');
@@ -831,15 +794,6 @@ function installClaudeCore() {
831
794
  log(`\n🧹 Cleaned up ${removed} deprecated file${removed > 1 ? 's' : ''}`, 'yellow');
832
795
  }
833
796
 
834
- // Copy Claude project instructions to project root. Other CLI instruction
835
- // files are installed only when explicitly selected below or via `agents`.
836
- const claudeMdSrc = path.join(__dirname, '..', 'CLAUDE.md');
837
- const claudeMdDest = path.join(process.cwd(), 'CLAUDE.md');
838
- if (fs.existsSync(claudeMdSrc)) {
839
- fs.copyFileSync(claudeMdSrc, claudeMdDest);
840
- log(' → CLAUDE.md', 'dim');
841
- }
842
-
843
797
  // Keep installer-managed pipeline state and install metadata out of git.
844
798
  const gitignorePath = path.join(process.cwd(), '.gitignore');
845
799
  const gitignoreEntries = ['.devlyn/', '.claude/skills/.devlyn-install.json'];
@@ -1314,10 +1268,14 @@ switch (command) {
1314
1268
  log('\n🤖 Installing agents for all supported CLIs...', 'blue');
1315
1269
  const count = installSelectedCLITargets(Object.keys(CLI_TARGETS));
1316
1270
  log(`\n✅ Agents installed for ${count} CLI${count !== 1 ? 's' : ''}`, 'green');
1317
- } else if (subArg && CLI_TARGETS[subArg]) {
1271
+ } else if (Object.keys(CLI_TARGETS).includes(subArg)) {
1318
1272
  // Install for a specific CLI
1319
1273
  installAgentsForCLI(subArg);
1320
1274
  log('\n✅ Done!', 'green');
1275
+ } else if (subArg !== undefined) {
1276
+ log(`Unknown CLI target: ${JSON.stringify(subArg)}`, 'yellow');
1277
+ log(`Supported: ${Object.keys(CLI_TARGETS).join(', ')}, all`, 'dim');
1278
+ process.exit(1);
1321
1279
  } else {
1322
1280
  // Auto-detect and install
1323
1281
  const detected = detectOtherCLIs();
@@ -0,0 +1,52 @@
1
+ {
2
+ "AGENTS.md": [
3
+ {
4
+ "versions": [
5
+ "3.0.0"
6
+ ],
7
+ "length": 11091,
8
+ "sha256": "099e68f3e203fa735bb1ebcd780a0e18bde462ea7de5f74fb3d17cee5e64b63a"
9
+ },
10
+ {
11
+ "versions": [
12
+ "3.0.1"
13
+ ],
14
+ "length": 12166,
15
+ "sha256": "33fbd5b01c8e65f4c663f95423a45b16dcf5bb28f1fd0c6791079abd20c267ae"
16
+ },
17
+ {
18
+ "versions": [
19
+ "3.1.0",
20
+ "3.1.1",
21
+ "3.1.2"
22
+ ],
23
+ "length": 12581,
24
+ "sha256": "acd3672efaa33e75455f8530a7425e950c01c822c6f5d85d7dbc9d7ff1958028"
25
+ }
26
+ ],
27
+ "CLAUDE.md": [
28
+ {
29
+ "versions": [
30
+ "3.0.0"
31
+ ],
32
+ "length": 24434,
33
+ "sha256": "29ddcc5ef4d5b44b2ba6aae22bd3c0c033ceaa47b240bd647aaefbe00f2a6e4e"
34
+ },
35
+ {
36
+ "versions": [
37
+ "3.0.1"
38
+ ],
39
+ "length": 25417,
40
+ "sha256": "50301bf98507c83da1576be7f443885236e7d421ff2e40fd077ed9652ea3a2e0"
41
+ },
42
+ {
43
+ "versions": [
44
+ "3.1.0",
45
+ "3.1.1",
46
+ "3.1.2"
47
+ ],
48
+ "length": 26152,
49
+ "sha256": "d11a863907dabdf827063df7133a8a46a14e018f608884f08c601bf36e0f3f0c"
50
+ }
51
+ ]
52
+ }
@@ -0,0 +1,137 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+ const legacyTemplates = require('./instruction-templates.json');
5
+
6
+ const BEGIN = '<!-- devlyn:instructions:begin';
7
+ const END = '<!-- devlyn:instructions:end -->';
8
+ const normalize = (text) => text.replace(/\r\n/g, '\n');
9
+ const digest = (text) => crypto.createHash('sha256').update(text).digest('hex');
10
+
11
+ function managedBlock(template, eol) {
12
+ const body = 'Project-specific instructions outside this managed block take precedence over these defaults.\n\n'
13
+ + normalize(template).replace(/\n?$/, '\n');
14
+ return `${BEGIN} sha256=${digest(body)} -->\n${body}${END}\n`.replace(/\n/g, eol);
15
+ }
16
+
17
+ // A digest is ownership evidence; a familiar heading alone is not.
18
+ function legacyRanges(text, templates) {
19
+ const starts = new Set([0, ...Array.from(text.matchAll(/^# (?:Codex )?Project Instructions\r?$/gm), (m) => m.index)]);
20
+ if (text.startsWith('\uFEFF')) starts.add(1);
21
+ const ranges = [];
22
+ for (const start of starts) {
23
+ for (const template of templates) {
24
+ let end = start;
25
+ for (let n = 0; n < template.length && end < text.length; n++, end++) {
26
+ if (text[end] === '\r' && text[end + 1] === '\n') end++;
27
+ }
28
+ if (digest(normalize(text.slice(start, end))) === template.sha256
29
+ && !ranges.some((range) => range.start === start && range.end === end)) {
30
+ ranges.push({ start, end });
31
+ }
32
+ }
33
+ }
34
+ return ranges;
35
+ }
36
+
37
+ function retainFile(file, bytes) {
38
+ fs.mkdirSync(path.dirname(file), { recursive: true });
39
+ const ignore = path.join(path.dirname(file), '.gitignore');
40
+ if (!fs.existsSync(ignore)) fs.writeFileSync(ignore, '*\n', { flag: 'wx', mode: 0o600 });
41
+ if (fs.existsSync(file)) {
42
+ if (!fs.lstatSync(file).isFile() || !fs.readFileSync(file).equals(bytes)) {
43
+ throw new Error(`Instruction recovery file differs; preserved it: ${file}. Move it aside before retrying.`);
44
+ }
45
+ } else {
46
+ fs.writeFileSync(file, bytes, { flag: 'wx', mode: 0o600 });
47
+ }
48
+ }
49
+
50
+ function updateInstructions(name) {
51
+ const templatePath = path.join(__dirname, '..', name);
52
+ const template = fs.readFileSync(templatePath, 'utf8');
53
+ if (template.includes(BEGIN) || template.includes(END)) {
54
+ throw new Error(`Packaged instruction template contains managed markers: ${templatePath}`);
55
+ }
56
+ const dest = path.join(process.cwd(), name);
57
+ const stat = fs.lstatSync(dest, { throwIfNoEntry: false });
58
+ const sourceStat = fs.statSync(templatePath);
59
+ if (stat?.isFile() && stat.dev === sourceStat.dev && stat.ino === sourceStat.ino) {
60
+ console.log(` → Using source ${name}; packaged template left unchanged`);
61
+ return false;
62
+ }
63
+ const exists = Boolean(stat);
64
+ // Do not replace a shared instruction symlink or follow it outside the project.
65
+ if (stat && !stat.isFile()) {
66
+ throw new Error(`Instruction file must be a regular file; preserved: ${dest}. Move it aside and rerun, then merge your shared rules outside the managed block.`);
67
+ }
68
+ const before = exists ? fs.readFileSync(dest) : Buffer.alloc(0);
69
+ let current;
70
+ try {
71
+ current = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(before);
72
+ } catch (cause) {
73
+ throw new Error(`${name} must be UTF-8; original preserved. Convert its encoding and rerun installation.`, { cause });
74
+ }
75
+ const eol = current.match(/\r?\n/)?.[0] || '\n';
76
+ const block = managedBlock(template, eol);
77
+ const recovery = path.join(process.cwd(), '.devlyn', 'instructions');
78
+ const conflict = (reason) => {
79
+ const incoming = path.join(recovery, `${name}.${digest(block)}.incoming`);
80
+ retainFile(incoming, Buffer.from(block));
81
+ throw new Error(`${name} needs merge (${reason}); original preserved. New defaults: ${incoming}. Keep custom rules outside the managed block, then rerun installation.`);
82
+ };
83
+ let content;
84
+ if (current.includes(BEGIN) || current.includes(END)) {
85
+ const starts = [...current.matchAll(/^\uFEFF?<!-- devlyn:instructions:begin sha256=([a-f0-9]{64}) -->\r?\n/gm)];
86
+ const ends = [...current.matchAll(/^<!-- devlyn:instructions:end -->(?:\r?\n|$)/gm)];
87
+ if (starts.length !== 1 || ends.length !== 1
88
+ || current.split(BEGIN).length !== 2 || current.split(END).length !== 2) {
89
+ return conflict('ambiguous managed markers');
90
+ }
91
+ const start = starts[0];
92
+ const end = ends[0];
93
+ const bodyStart = start.index + start[0].length;
94
+ if (end.index < bodyStart || digest(normalize(current.slice(bodyStart, end.index))) !== start[1]) {
95
+ return conflict('managed defaults were edited');
96
+ }
97
+ content = current.slice(0, start.index + (start[0].startsWith('\uFEFF') ? 1 : 0)) + block + current.slice(end.index + end[0].length);
98
+ } else {
99
+ const normalized = normalize(template);
100
+ const ranges = legacyRanges(current, [
101
+ { length: normalized.length, sha256: digest(normalized) },
102
+ ...(legacyTemplates[name] || []),
103
+ ]);
104
+ if (ranges.length > 1) return conflict('multiple legacy templates');
105
+ if (ranges.length === 1) {
106
+ const { start, end } = ranges[0];
107
+ content = current.slice(0, start) + block + current.slice(end);
108
+ } else {
109
+ if (/^devlyn-cli installs |^# Devlyn Agent Instructions\r?$|^This contract serves one goal: any capable engine|^Engine roles: orchestrator =/m.test(current)) {
110
+ return conflict('unrecognized or edited legacy template');
111
+ }
112
+ content = current + (current ? eol + eol : '') + block;
113
+ }
114
+ }
115
+ const bytes = Buffer.from(content);
116
+ if (bytes.equals(before)) return false;
117
+ if (exists) {
118
+ const backup = path.join(recovery, `${name}.${digest(before)}.backup`);
119
+ retainFile(backup, before);
120
+ console.log(` → Saved previous ${name}: ${backup}`);
121
+ }
122
+ const temp = `${dest}.${crypto.randomUUID()}.tmp`;
123
+ try {
124
+ fs.writeFileSync(temp, bytes, { flag: 'wx', mode: exists ? stat.mode & 0o777 : 0o644 });
125
+ if (exists) fs.chmodSync(temp, stat.mode & 0o777);
126
+ if (fs.existsSync(dest) !== exists || (exists && !fs.readFileSync(dest).equals(before))) {
127
+ throw new Error(`${name} changed during installation; preserved it. Rerun installation.`);
128
+ }
129
+ fs.renameSync(temp, dest);
130
+ } finally {
131
+ if (fs.existsSync(temp)) fs.unlinkSync(temp);
132
+ }
133
+ console.log(` → Updated Devlyn defaults in ${name}; project-specific instructions preserved`);
134
+ return true;
135
+ }
136
+
137
+ module.exports = { updateInstructions };
@@ -55,7 +55,7 @@ python3 "$DEVLYN_SHARED_DIR/run-bounded.py" 600 --stdin-file "<judge-prompt-file
55
55
 
56
56
  ## Identity
57
57
 
58
- 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.
58
+ You are Claude by Anthropic. Anthropic's prompt-engineering guide for this model governs your behavior on top of the canonical phase prompt below.
59
59
 
60
60
  ## Output discipline
61
61
 
@@ -73,10 +73,6 @@ When the canonical body lists tools, use them when their result would change you
73
73
 
74
74
  For long-horizon coding, review, and agentic runs, assume the harness selected `high` or `xhigh` effort unless told otherwise. Spend that depth on upfront task/constraint understanding and end-state verification, not on verbose narration. If the user or orchestrator gives a complete task in one turn, proceed autonomously instead of requiring progressive clarification.
75
75
 
76
- ## Validation pattern
77
-
78
- When the canonical body asks you to verify your output before declaring done ("self-check" instructions), execute that step literally — re-read the spec's acceptance criteria, run the listed verification commands if available, list any gap. This is not optional. Mechanical gates owned by the harness (spec-verify-check.py, build-gate.py) are the primary correctness guard; your self-check is the secondary layer that catches what regex cannot.
79
-
80
76
  ## Anti-patterns
81
77
 
82
78
  You interpret instructions literally and explicitly. The official guide is explicit about three failure modes:
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Identity
6
6
 
7
- You are OpenAI's Codex model. OpenAI's prompt-guidance 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.
7
+ You are OpenAI's Codex model. OpenAI's prompt-guidance for this model governs your behavior on top of the canonical phase prompt below.
8
8
 
9
9
  ## Output discipline
10
10
 
@@ -19,10 +19,6 @@ canonical file and its `.agents` mirror are byte-identical, inspect the
19
19
  canonical file only. Open self-test bodies or MECHANICAL raw streams only for a
20
20
  named clause that remains unresolved after the broad pass.
21
21
 
22
- ## Validation pattern
23
-
24
- Validation is concrete commands and tools, not self-belief. When the canonical body lists verification commands, execute them and trust their output. Do not substitute your judgment for a deterministic check the harness has provided. When validation tools are available (test runners, lint, type-check, the harness's `spec-verify-check.py`), run them before declaring success. The minimum evidence sufficient to answer correctly, cited precisely — then stop.
25
-
26
22
  ## Anti-patterns
27
23
 
28
24
  The official guide warns explicitly about carrying over instructions from older prompt stacks — earlier models needed more help, and process-heavy directives now narrow GPT-5.5's search space.
@@ -8,10 +8,6 @@ You are the model running behind the `omp` CLI. Follow the canonical phase promp
8
8
 
9
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
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
11
  ## Fresh Context
16
12
 
17
13
  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.
@@ -147,13 +147,13 @@
147
147
  },
148
148
  "required_files": {
149
149
  "type": "array",
150
- "description": "Files that must exist after the arm runs.",
150
+ "description": "Files specified by literal paths that must exist after the arm runs; globs are not expanded.",
151
151
  "items": { "type": "string", "minLength": 1 },
152
152
  "default": []
153
153
  },
154
154
  "forbidden_files": {
155
155
  "type": "array",
156
- "description": "Files that must NOT appear in the arm's diff (e.g. tooling artifacts the spec didn't request).",
156
+ "description": "Exact Git-relative paths (forward slashes, no leading ./) that must NOT appear in the diff, including both rename names; globs and directory prefixes are not expanded.",
157
157
  "items": { "type": "string", "minLength": 1 },
158
158
  "default": []
159
159
  },
@@ -107,6 +107,7 @@ def system_exit_code(code):
107
107
 
108
108
 
109
109
  if os.name == "nt":
110
+ import _winapi
110
111
  import ctypes
111
112
  from ctypes import wintypes
112
113
  import msvcrt
@@ -226,7 +227,9 @@ if os.name == "nt":
226
227
  try:
227
228
  if self.child.poll() is None:
228
229
  self.child.kill()
229
- self.child.wait(timeout=5)
230
+ # kill() can cache an exit code before the native handle is signaled.
231
+ if _winapi.WaitForSingleObject(self.child._handle, 5000) == _winapi.WAIT_TIMEOUT:
232
+ raise subprocess.TimeoutExpired(self.child.args, 5)
230
233
  finally:
231
234
  self.child._handle.Close()
232
235
  finally:
@@ -289,7 +289,7 @@ def init_spec_source(
289
289
  block("BLOCKED:invalid-flags", error or f"expected contract not found: {expected}")
290
290
  else:
291
291
  run_checked([sys.executable, str(helper), "--check", str(path)], cwd)
292
- _staged, error = module.stage_from_source(path, staging_dir)
292
+ _found, _staged, error = module.stage_from_source(path, staging_dir)
293
293
  if error:
294
294
  block("BLOCKED:invalid-flags", error)
295
295
  staged_path = staging_dir / "spec-verify.json"
@@ -333,7 +333,7 @@ def capture_external_diff(cwd: pathlib.Path, ref: str) -> bytes:
333
333
  if source.is_file():
334
334
  raw = source.read_bytes()
335
335
  else:
336
- proc = subprocess.run(["git", "diff", "--binary", ref], cwd=cwd, capture_output=True)
336
+ proc = subprocess.run(["git", "diff", "--binary", "--no-ext-diff", "--src-prefix=a/", "--dst-prefix=b/", ref], cwd=cwd, capture_output=True)
337
337
  if proc.returncode != 0:
338
338
  block("BLOCKED:invalid-flags", os.fsdecode(proc.stderr or proc.stdout).strip())
339
339
  raw = proc.stdout
@@ -1184,6 +1184,16 @@ def self_test() -> int:
1184
1184
  staged = strict_json((work / ".devlyn" / "spec-verify.json").read_text(encoding="utf-8"))
1185
1185
  assert staged["verification_commands"][0]["cmd"] == "printf ok"
1186
1186
  assert spec_result["source"]["spec_sha256"] == sha256(spec_raw)
1187
+ pure_spec = spec_dir / "design.md"
1188
+ pure_spec.write_text(
1189
+ "# Design\n\n<!-- devlyn:verification -->\n## Verification\n\n"
1190
+ '```json\n{"pure_design":true,"verification_commands":[]}\n```\n',
1191
+ encoding="utf-8",
1192
+ )
1193
+ complete_prior(work)
1194
+ pure_result = bootstrap(["--spec", str(pure_spec.relative_to(work))], work, script_shared)
1195
+ assert pure_result["source"]["spec_sha256"] == sha256(pure_spec.read_bytes())
1196
+ assert not (work / ".devlyn/spec-verify.json").exists()
1187
1197
  external_patch.write_bytes(b"stale free-form patch\n")
1188
1198
  complete_prior(work)
1189
1199
  bootstrap(["fresh", "goal"], work, script_shared)
@@ -1205,6 +1215,16 @@ def self_test() -> int:
1205
1215
  assert verify_result["mode"] == "verify-only"
1206
1216
  assert external_patch.read_bytes() == patch_raw
1207
1217
  subprocess.run(["git", "restore", "app.py"], cwd=work, check=True)
1218
+ # Ambient no-prefix config must not change the reader's a/b contract.
1219
+ (work / "app.py").write_text("print('prefix control')\n", encoding="utf-8")
1220
+ settings = ["diff.noprefix"] + (["diff.external"] if os.name != "nt" else [])
1221
+ for setting in settings:
1222
+ subprocess.run(["git", "config", setting, "true"], cwd=work, check=True)
1223
+ try:
1224
+ assert b"diff --git a/app.py b/app.py\n" in capture_external_diff(work, "HEAD")
1225
+ finally:
1226
+ subprocess.run(["git", "config", "--unset", setting], cwd=work, check=True)
1227
+ subprocess.run(["git", "restore", "app.py"], cwd=work, check=True)
1208
1228
  print("PASS bootstrap self-test patch lifecycle: full-mode removal + dirty verify-only exact capture")
1209
1229
 
1210
1230
  for valid_named in (True, False):