devlyn-cli 4.3.25 → 4.3.27

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 (32) hide show
  1. package/README.md +27 -27
  2. package/bin/devlyn.js +42 -94
  3. package/bin/harness-context.js +395 -0
  4. package/bin/harness-run.js +148 -0
  5. package/bin/instructions.js +71 -159
  6. package/bin/skill-history.json +6 -0
  7. package/config/skills/_shared/storage-checkout.py +1 -0
  8. package/config/skills/_shared/storage-client.py +349 -14
  9. package/config/skills/_shared/task-complete.py +99 -24
  10. package/config/skills/_shared/task-completion.md +7 -3
  11. package/config/skills/devlyn-ideate/SKILL.md +2 -2
  12. package/config/skills/devlyn-ideate/references/loop.md +2 -2
  13. package/config/skills/devlyn-ideate/scripts/queue.py +10 -3
  14. package/native/claude/.claude-plugin/plugin.json +1 -1
  15. package/native/claude/skills/_shared/storage-checkout.py +1 -0
  16. package/native/claude/skills/_shared/storage-client.py +349 -14
  17. package/native/claude/skills/_shared/task-complete.py +99 -24
  18. package/native/claude/skills/_shared/task-completion.md +7 -3
  19. package/native/claude/skills/devlyn-ideate/SKILL.md +2 -2
  20. package/native/claude/skills/devlyn-ideate/references/loop.md +2 -2
  21. package/native/claude/skills/devlyn-ideate/scripts/queue.py +10 -3
  22. package/native/codex/.agents/plugins/marketplace.json +1 -1
  23. package/native/codex/plugins/devlyn/.codex-plugin/plugin.json +1 -1
  24. package/native/codex/plugins/devlyn/skills/_shared/storage-checkout.py +1 -0
  25. package/native/codex/plugins/devlyn/skills/_shared/storage-client.py +349 -14
  26. package/native/codex/plugins/devlyn/skills/_shared/task-complete.py +99 -24
  27. package/native/codex/plugins/devlyn/skills/_shared/task-completion.md +7 -3
  28. package/native/codex/plugins/devlyn/skills/devlyn-ideate/SKILL.md +2 -2
  29. package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/loop.md +2 -2
  30. package/native/codex/plugins/devlyn/skills/devlyn-ideate/scripts/queue.py +10 -3
  31. package/package.json +2 -2
  32. package/release.json +41 -31
package/README.md CHANGED
@@ -29,13 +29,13 @@ npx devlyn-cli
29
29
 
30
30
  That's it. The installer asks:
31
31
 
32
- 1. **What** — `AGENTS.md — Codex · omp · Pi · Grok` (checked) and `CLAUDE.md — Claude Code` (checked when this project already has a devlyn Claude install). Space toggles, Enter confirms.
32
+ 1. **What** — `Codex · omp · Pi · Grok` (checked) and `Claude Code` (checked when this project already has a devlyn Claude install). Space toggles, Enter confirms.
33
33
  2. **Where** — `This project` (default) or `Global — every project on this machine`.
34
- 3. **Optional skills & packs** — none checked; see [Optional Add-ons](#optional-add-ons) below. MCP servers are offered only with CLAUDE.md.
34
+ 3. **Optional skills & packs** — none checked; see [Optional Add-ons](#optional-add-ons) below. MCP servers are offered only with Claude Code.
35
35
 
36
- In a project, AGENTS.md readers load the skills from `.agents/skills/`; Claude Code loads only `.claude/skills/` and reads AGENTS.md when the project has no CLAUDE.md; a CLAUDE.md the installer creates imports an existing AGENTS.md (`@AGENTS.md`). Global installs skills only: `~/.agents/skills/` (omp, Pi, Grok) and `~/.codex/skills/` (Codex), plus `~/.claude/skills/` for Claude Code. Every target gets the `devlyn-ideate` skill and the `devlyn-engines` utility. In Codex / omp / Pi, invoke them as skills (`$devlyn-ideate`); in Claude Code and Grok they're slash commands (`/devlyn-ideate`). Rerunning refreshes skills and the managed instruction block while preserving project rules outside it. See [Migration from earlier versions](#migration-from-earlier-versions) for legacy migration and merge recovery.
36
+ In a project, Codex, omp, Pi and Grok load the skills from `.agents/skills/`; Claude Code loads only `.claude/skills/`. The installer never writes `CLAUDE.md` or `AGENTS.md`: those files stay yours. Global installs skills only: `~/.agents/skills/` (omp, Pi, Grok) and `~/.codex/skills/` (Codex), plus `~/.claude/skills/` for Claude Code. Every target gets the `devlyn-ideate` skill and the `devlyn-engines` utility. In Codex / omp / Pi, invoke them as skills (`$devlyn-ideate`); in Claude Code and Grok they're slash commands (`/devlyn-ideate`). Rerunning refreshes skills. See [Migration from earlier versions](#migration-from-earlier-versions) for projects that carry an earlier copy of the principles.
37
37
 
38
- Without prompts, `npx devlyn-cli -y` installs AGENTS.md + `.agents/skills/` plus every target this project already has; add `--claude` for Claude Code. With `--global` it installs for every project on this machine, plus `~/.claude/skills/` with `--claude` or when it already has a devlyn install. The installer leaves Git to you: it prints no commit guidance after a project install or update. When Git ignores some, but not all, files of an installed skill folder, it prints one line with their count and an example ignore rule.
38
+ Without prompts, `npx devlyn-cli -y` installs `.agents/skills/` plus every target this project already has; add `--claude` for Claude Code. With `--global` it installs for every project on this machine, plus `~/.claude/skills/` with `--claude` or when it already has a devlyn install. The installer leaves Git to you: it prints no commit guidance after a project install or update. When Git ignores some, but not all, files of an installed skill folder, it prints one line with their count and an example ignore rule.
39
39
 
40
40
  `npx devlyn-cli update` — Update every devlyn install already here (this project and your global skill folders) to this version; installs nothing new.
41
41
 
@@ -43,13 +43,17 @@ Without prompts, `npx devlyn-cli -y` installs AGENTS.md + `.agents/skills/` plus
43
43
 
44
44
  ## How It Works — Principles and Loops
45
45
 
46
- The managed `CLAUDE.md` / `AGENTS.md` block carries the North Star, seven principles (no workaround, no overengineering, no guesswork, worldclass, best practice, optimized, production ready) and three discipline rules, plus short pointers to the skills. Agents do the work directly under it; for work that splits into tasks, ideate designs a loop and drains it:
46
+ The Devlyn defaults carry the North Star, seven principles (no workaround, no overengineering, no guesswork, worldclass, best practice, optimized, production ready) and three discipline rules, plus short pointers to the skills. Each session gets them from the release, through the Devlyn app or `devlyn run`, so projects hold no copy and a new release reaches every project at once. A clone without either gets no Devlyn defaults. Agents do the work directly under them; for work that splits into tasks, ideate designs a loop and drains it:
47
47
 
48
48
  ```
49
49
  intent → direct work under the installed principles, or an ideate loop → ship
50
50
  ```
51
51
 
52
- Non-Claude agents (Codex / omp / Pi / Grok): the AGENTS.md choice installs the skills for them. In Codex / omp / Pi, use `$devlyn-ideate`; in Grok, use `/devlyn-ideate`, the same slash-command form as Claude Code. A drain from Pi or Grok needs [a pinned executor](#executor--devlyn-engines).
52
+ Non-Claude agents (Codex / omp / Pi / Grok): the default choice installs the skills for them. In Codex / omp / Pi, use `$devlyn-ideate`; in Grok, use `/devlyn-ideate`, the same slash-command form as Claude Code. A drain from Pi or Grok needs [a pinned executor](#executor--devlyn-engines).
53
+
54
+ ### Terminal sessions — `devlyn run`
55
+
56
+ `devlyn run claude`, `devlyn run codex` or `devlyn run omp` starts the engine in this terminal with the same principles and skills the Devlyn app delivers, from the selected release. Arguments after the engine name go to the engine. See [docs/harness-run.md](docs/harness-run.md).
53
57
 
54
58
  ### Plan and drain loops — `/devlyn-ideate`
55
59
 
@@ -62,7 +66,7 @@ Give ideate an intent or a document. It writes a loop package — a meta-prompt
62
66
  | `/devlyn-ideate status` | Pending, active, accepted and failed counts; the next runnable task; delivery and recovery blockers. |
63
67
  | `/devlyn-ideate drain` | Runs the queue serially and hands-free. |
64
68
 
65
- A bare intent is planned and, when your request authorizes the work, added and drained without another confirmation; no arguments shows status. Questions come only when the answer changes behavior, scope, data semantics, acceptance or delivery, each with a recommended answer; `--autonomous` plans without questions, recording ordinary low-consequence, reversible defaults within the request, including user-visible details; material ambiguity in intended outcomes, persistent data or state semantics, or public surface beyond the request stops planning with a concrete question. Each task runs in its own worktree with the executor pinned by `/devlyn-engines` (default: the CLI you opened; Pi and Grok need a pin) under your installed CLAUDE.md/AGENTS.md instructions. Tasks start from committed state, the branch you added the loop on or the remote base for `auto`/`pr`, so a task's worktree holds only what is committed there. A task would run without installed CLAUDE.md/AGENTS.md that exist only in this checkout, so add stops while they differ from HEAD, and drain waits while they differ from the task's start commit. It is marked `[x]` only when its declared checks pass on its committed source and its required reviews cover it: the checks run through the drain's runner (a wholly clean run the executor lists for that source is reused; the drain runs any other check itself), and review coverage is attested by records the executor submits; a failed task becomes `[F]` with its reason and blocks only its dependents. An interrupted drain resumes without repeating accepted work. `--local-only` (or `--no-push`) keeps delivery local: each accepted task stays on its `devlyn/<loop-id>/<task-id>` branch in a retained worktree, nothing is merged or pushed, and the drain report's `Bring into` command brings the result into your branch, stopping if your files cannot be preserved.
69
+ A bare intent is planned and, when your request authorizes the work, added and drained without another confirmation; no arguments shows status. Questions come only when the answer changes behavior, scope, data semantics, acceptance or delivery, each with a recommended answer; `--autonomous` plans without questions, recording ordinary low-consequence, reversible defaults within the request, including user-visible details; material ambiguity in intended outcomes, persistent data or state semantics, or public surface beyond the request stops planning with a concrete question. Each task runs in its own worktree with the executor pinned by `/devlyn-engines` (default: the CLI you opened; Pi and Grok need a pin) under the Devlyn defaults and your project's CLAUDE.md/AGENTS.md. Tasks start from committed state, the branch you added the loop on or the remote base for `auto`/`pr`, so a task's worktree holds only what is committed there. A task would run without installed CLAUDE.md/AGENTS.md that exist only in this checkout, so add stops while they differ from HEAD, and drain waits while they differ from the task's start commit. It is marked `[x]` only when its declared checks pass on its committed source and its required reviews cover it: the checks run through the drain's runner (a wholly clean run the executor lists for that source is reused; the drain runs any other check itself), and review coverage is attested by records the executor submits; a failed task becomes `[F]` with its reason and blocks only its dependents. An interrupted drain resumes without repeating accepted work. `--local-only` (or `--no-push`) keeps delivery local: each accepted task stays on its `devlyn/<loop-id>/<task-id>` branch in a retained worktree, nothing is merged or pushed, and the drain report's `Bring into` command brings the result into your branch, stopping if your files cannot be preserved.
66
70
 
67
71
  ### Delivery
68
72
 
@@ -107,29 +111,25 @@ An account server you never list remains available.
107
111
 
108
112
  ### Migration from earlier versions
109
113
 
110
- Reinstall with the new version to refresh instructions and skills for the selected
111
- targets. Claude uses `CLAUDE.md`; Codex, Grok, omp and Pi share `AGENTS.md`.
112
- Devlyn updates its checksum-marked instruction block and preserves project-specific
113
- rules outside it. Keep custom rules outside the block; they take precedence over
114
- its defaults. Exact pre-update backups are saved in `.devlyn/instructions/`.
115
- When recovery is needed, pre-existing symlinks or non-directory entries at `.devlyn/` or
116
- `.devlyn/instructions/` stop installation without replacing the instruction file.
117
-
118
- Older and mixed-version templates migrate automatically. Within recognizable
119
- Devlyn sections, exact historical paragraphs and list items are replaced;
120
- modified or unknown text and its headings are preserved outside the new block.
121
- Edits inside an existing managed block are preserved outside it too. Plain custom
122
- files and rules outside managed blocks retain their contents. Unknown old text
123
- is kept conservatively, even when it may contain obsolete defaults.
124
- Malformed or duplicate managed markers still stop with an exact `.backup`, new
125
- defaults in `.incoming`, and a `.merge.md` guide; the original remains unchanged.
114
+ Reinstall with the new version to refresh skills for the selected targets. Install and
115
+ update leave `CLAUDE.md` and `AGENTS.md` byte-identical.
116
+
117
+ Earlier installs put the principles in those files, inside a checksum-marked block or as
118
+ a whole template. [Cleanup](docs/harness-cleanup.md) removes an intact copy, keeps the text
119
+ around it byte-for-byte and deletes a file that held only the copy; a `@AGENTS.md` import
120
+ left without its file is harmless. Exact pre-removal backups are saved in
121
+ `.devlyn/instructions/`; pre-existing symlinks or non-directory entries at `.devlyn/` or
122
+ `.devlyn/instructions/` stop the removal without touching the file.
123
+
124
+ A copy someone edited stays exactly as it is, as does any text Devlyn cannot prove it wrote:
125
+ a quoted example, or malformed or duplicate markers. Devlyn's defaults replace a remaining
126
+ block for each session.
126
127
  Portability refreshes the offline fingerprints from Git history using
127
128
  `node scripts/update-instruction-templates.js` before testing; publish ships that tested tarball.
128
129
  Installation needs no Git or network.
129
- Use 3.1.3 or newer consistently: older installers still overwrite `CLAUDE.md`.
130
130
 
131
131
  Upgrading to 4.1.0: `npx devlyn-cli -y` in a 4.x project keeps its Claude Code install
132
- current and adds `AGENTS.md` + `.agents/skills/`. Earlier releases put the Codex, omp, Pi
132
+ current and adds `.agents/skills/`. Earlier releases put the Codex, omp, Pi
133
133
  and Grok skills in `~/.codex/skills`, `~/.agents/skills` and `~/.grok/skills`; a project
134
134
  install no longer refreshes them and names each one it finds. Refresh them with `--global`
135
135
  or delete them; global installs and `update` clean Devlyn’s retired `~/.grok/skills` entries automatically.
@@ -224,7 +224,7 @@ Selected during install. Run `npx devlyn-cli` again to add more.
224
224
  </details>
225
225
 
226
226
  <details>
227
- <summary><strong>MCP Servers</strong> — installed via <code>claude mcp add</code>, offered with the CLAUDE.md choice</summary>
227
+ <summary><strong>MCP Servers</strong> — installed via <code>claude mcp add</code>, offered with the Claude Code choice</summary>
228
228
 
229
229
  | Server | Description |
230
230
  |---|---|
@@ -246,7 +246,7 @@ Only `__pycache__` directories containing exclusively regular `*.pyc` files coun
246
246
 
247
247
  - **Node.js 20 (20.17+), 22 (22.13+) or 23.5+** and npm
248
248
  - **Python 3.11+** available as `python3` for the harness, and **Git** for safe installation and the harness
249
- - **An agent CLI** installed and configured: Codex, omp, Pi or Grok (AGENTS.md), or [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (CLAUDE.md)
249
+ - **An agent CLI** installed and configured: Codex, omp, Pi or Grok, or [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
250
250
  - **For `auto`/`pr` delivery**, an `origin` remote naming one GitHub repository and an authenticated [`gh`](https://cli.github.com/)
251
251
 
252
252
  On native Windows, use native Node/npm and Python plus Git for Windows Bash for the shipped shell wrapper. Run `npx devlyn-cli -y` in the project (add `--claude` for Claude Code). Skill folders follow the Agent Skills naming standard (for example `devlyn-ideate`), so a project that commits them checks out on Windows. Harness text is UTF-8 without requiring `PYTHONUTF8`.
package/bin/devlyn.js CHANGED
@@ -10,7 +10,7 @@ const { parseTree, findNodeAtLocation, applyEdits } = require('jsonc-parser');
10
10
  const CONFIG_SOURCE = path.join(__dirname, '..', 'config');
11
11
  const OPTIONAL_SKILLS_SOURCE = path.join(__dirname, '..', 'optional-skills');
12
12
  const PKG = require('../package.json');
13
- const { updateInstructions, InstructionError, holdsDevlynDefaults, importsAgentsMd } = require('./instructions');
13
+ const { removeInstructions, InstructionError, holdsDevlynDefaults } = require('./instructions');
14
14
  const { contentHash, fingerprint, gitEnvironment, isIgnoredEntry, trackedPaths } = require('./skill-ownership');
15
15
  const SKILL_HISTORY = require('./skill-history.json');
16
16
  const CLEANUP_HISTORY = require('./cleanup-history.json');
@@ -105,18 +105,13 @@ function hasDevlynInstall(root) {
105
105
  }
106
106
 
107
107
  // A devlyn Claude install in this scope. Globally only the marker a `--global --claude` run
108
- // writes counts; in a project also a devlyn skill (0.6.0 and later), command (0.2-0.5) or
109
- // CLAUDE.md with devlyn defaults, held or imported from AGENTS.md, which a team may commit
110
- // while ignoring .claude/. An optional devlyn skill is not one: 4.0.1 put those into
111
- // .claude/skills without the Claude target. A CLAUDE.md link (often to AGENTS.md) is not the
112
- // Claude target's file: updateInstructions refuses links.
108
+ // writes counts; in a project also a devlyn skill (0.6.0 and later) or command (0.2-0.5). An
109
+ // optional devlyn skill is not one: 4.0.1 put those into .claude/skills without the Claude target.
113
110
  function hasDevlynClaude(global) {
114
111
  const claudeDir = path.dirname(skillRoots('claude', global)[0]);
115
112
  if (global) return fs.existsSync(path.join(claudeDir, 'skills', DEVLYN_INSTALL_MARKER));
116
113
  const names = (dir) => (fs.existsSync(dir) ? fs.readdirSync(dir) : []);
117
114
  const optional = new Set(OPTIONAL_ADDONS.map((addon) => addon.name));
118
- const instructions = path.join(path.dirname(claudeDir), 'CLAUDE.md');
119
- const claude = fs.lstatSync(instructions, { throwIfNoEntry: false })?.isFile() ? fs.readFileSync(instructions, 'utf8') : '';
120
115
  const root = path.join(claudeDir, 'skills');
121
116
  const deprecated = new Set([...deprecatedSkillPaths(root)].map((full) => path.basename(full).toLowerCase()));
122
117
  return names(root).some((name) => {
@@ -124,8 +119,7 @@ function hasDevlynClaude(global) {
124
119
  return name === DEVLYN_INSTALL_MARKER
125
120
  || (name.startsWith('devlyn') && !optional.has(name.replace(/[:\uF03A]/g, '-')));
126
121
  })
127
- || names(path.join(claudeDir, 'commands')).some((name) => name.startsWith('devlyn.'))
128
- || holdsDevlynDefaults('CLAUDE.md', claude) || importsAgentsDefaults(claude);
122
+ || names(path.join(claudeDir, 'commands')).some((name) => name.startsWith('devlyn.'));
129
123
  }
130
124
 
131
125
  // Exact Devlyn command names removed by previous versions, in either scope.
@@ -764,67 +758,19 @@ function updateProjectGitignore(markerEntry) {
764
758
 
765
759
  // With the Claude target in the same run, an AGENTS.md that links to this project's CLAUDE.md (a
766
760
  // common Claude-first setup) gets its devlyn block through CLAUDE.md. Any other link is refused.
767
- // AGENTS.md that is this project's CLAUDE.md under another name: a symlink to it, or the plain
768
- // file Git for Windows checks out in a symlink's place when core.symlinks is off (its whole
769
- // content is the link target). Compared by real path: a link to another hard link of CLAUDE.md
770
- // would go stale once CLAUDE.md is replaced.
771
- function agentsMdIsClaudeMd() {
772
- const agents = path.join(projectDir(), 'AGENTS.md');
773
- const claude = path.join(projectDir(), 'CLAUDE.md');
774
- const stat = fs.lstatSync(agents, { throwIfNoEntry: false });
775
- if (!stat || !fs.lstatSync(claude, { throwIfNoEntry: false })?.isFile()) return false;
776
- const target = stat.isSymbolicLink() ? agents
777
- : stat.isFile() && stat.size < 256 ? path.resolve(projectDir(), fs.readFileSync(agents, 'utf8'))
778
- : null;
779
- return target !== null && fs.existsSync(target) && fs.realpathSync.native(target) === fs.realpathSync.native(claude);
780
- }
781
-
782
- function installAgentsProject(withClaude, installedSkills = new Map()) {
783
- if (!agentsMdIsClaudeMd()) {
784
- updateInstructions('AGENTS.md');
785
- } else if (withClaude) {
786
- log(' → AGENTS.md is CLAUDE.md here, so it gets the devlyn block written there', 'dim');
787
- } else {
788
- throw new InstructionError('AGENTS.md is CLAUDE.md under another name here. Choose CLAUDE.md as well '
789
- + '(npx devlyn-cli -y --claude) so the devlyn block is written once, into CLAUDE.md.');
790
- }
761
+ function installAgentsProject(installedSkills = new Map()) {
791
762
  installCoreSkills(skillRoots('agents', false)[0], installedSkills);
792
763
  updateProjectGitignore('.agents/skills/.devlyn-install.json');
793
764
  }
794
765
 
795
- // Whether Claude Code reads the devlyn defaults of AGENTS.md through an `@AGENTS.md` import in this CLAUDE.md text.
796
- // An AGENTS.md that is CLAUDE.md under another name holds CLAUDE.md's own defaults.
797
- function importsAgentsDefaults(claude) {
798
- const agents = path.join(projectDir(), 'AGENTS.md');
799
- return importsAgentsMd(claude) && !agentsMdIsClaudeMd() && fs.statSync(agents, { throwIfNoEntry: false })?.isFile() === true
800
- && holdsDevlynDefaults('AGENTS.md', fs.readFileSync(agents, 'utf8'));
801
- }
802
-
803
- // Claude Code reads AGENTS.md only where no CLAUDE.md exists, or through a CLAUDE.md that imports it. So a new
804
- // CLAUDE.md imports an existing AGENTS.md, and a CLAUDE.md importing an AGENTS.md that holds devlyn defaults holds no
805
- // copy of its own, not even one it got while AGENTS.md lacked the block. Only the AGENTS.md target updates those defaults,
806
- // together with .agents/skills, so the Claude target stops while they need an update.
807
- function installClaudeInstructions() {
808
- const file = path.join(projectDir(), 'CLAUDE.md');
809
- const stat = fs.lstatSync(file, { throwIfNoEntry: false });
810
- const initial = !stat && fs.statSync(path.join(projectDir(), 'AGENTS.md'), { throwIfNoEntry: false })?.isFile() ? '@AGENTS.md\n' : '';
811
- const imported = importsAgentsDefaults(stat?.isFile() ? fs.readFileSync(file, 'utf8') : initial);
812
- if (imported && updateInstructions('AGENTS.md', '', true, false)) {
813
- throw new InstructionError('Claude Code reads AGENTS.md here, and its devlyn defaults need an update. Codex, omp, Pi and Grok '
814
- + 'read them too, with .agents/skills, so choose AGENTS.md as well (npx devlyn-cli -y --claude) to update both together.');
815
- }
816
- updateInstructions('CLAUDE.md', initial, !imported);
817
- if (imported) log(' → CLAUDE.md imports AGENTS.md, which holds the devlyn block', 'dim');
818
- }
819
-
820
- // Project CLAUDE.md and .claude/: skills and settings.
766
+ // Project .claude/: skills and settings.
821
767
  function installClaudeCore(installedSkills = new Map()) {
822
768
  const skillsDir = skillRoots('claude', false)[0];
823
769
  const targetDir = path.dirname(skillsDir);
824
770
  // Read before any write, so settings this install cannot merge leave the Claude target as it was.
825
771
  const settingsPath = path.join(targetDir, 'settings.json');
826
772
  const unmergeable = (reason) => new InstructionError(`Cannot merge .claude/settings.json: ${reason}. `
827
- + 'CLAUDE.md and .claude/ are unchanged; fix the file and rerun the same install command.');
773
+ + '.claude/ is unchanged; fix the file and rerun the same install command.');
828
774
  const settingsStat = fs.lstatSync(settingsPath, { throwIfNoEntry: false });
829
775
  if (settingsStat && !settingsStat.isFile()) throw unmergeable('not a regular file');
830
776
  let settings = {};
@@ -843,7 +789,6 @@ function installClaudeCore(installedSkills = new Map()) {
843
789
  && (!settings.env || typeof settings.env !== 'object' || Array.isArray(settings.env))) {
844
790
  throw unmergeable('env must be a JSON object');
845
791
  }
846
- installClaudeInstructions();
847
792
  installCoreSkills(skillsDir, installedSkills);
848
793
  updateProjectGitignore('.claude/skills/.devlyn-install.json');
849
794
 
@@ -1073,34 +1018,32 @@ function install(targets, global, optionalNames = [], plan, afterInstall = () =>
1073
1018
  const home = fs.statSync(os.homedir(), { bigint: true, throwIfNoEntry: false });
1074
1019
  const here = fs.statSync(projectDir(), { bigint: true });
1075
1020
  if (!global && home?.dev === here.dev && home?.ino === here.ino) {
1076
- throw new InstructionError('This project is your home folder, so its CLAUDE.md, AGENTS.md and .claude/settings.json '
1077
- + 'would apply to every project. Run from a project folder, or use --global for skills only.');
1021
+ throw new InstructionError('This project is your home folder, so its .claude/settings.json would apply to every project. '
1022
+ + 'Run from a project folder, or use --global for skills only.');
1078
1023
  }
1079
1024
  const roots = targets.flatMap((target) => skillRoots(target, global));
1080
1025
  return withUserSkillLocks(global ? roots : [], () => {
1081
1026
  const installedSkills = new Map();
1082
1027
  if (!global) readProjectGitignore();
1083
1028
  plan ??= preflightSkills(roots, optionalNames);
1084
- // AGENTS.md first, whatever order the menu gave: a new CLAUDE.md holds the block only when AGENTS.md does not.
1085
1029
  for (const target of ['agents', 'claude'].filter((name) => targets.includes(name))) {
1086
1030
  if (global) skillRoots(target, true).forEach((root) => installCoreSkills(root, installedSkills));
1087
- else if (target === 'agents') installAgentsProject(targets.includes('claude'), installedSkills);
1031
+ else if (target === 'agents') installAgentsProject(installedSkills);
1088
1032
  else installClaudeCore(installedSkills);
1089
1033
  }
1090
1034
  for (const name of optionalNames) installLocalSkill(name, roots, installedSkills);
1091
1035
  afterInstall();
1092
1036
  log(`\n✅ devlyn ${PKG.version} installed`, 'green');
1093
- if (global) log('The principles block installs per project: run npx devlyn-cli in the project without --global.');
1037
+ log('The Devlyn app gives every session the principles; in a terminal, start with: npx devlyn-cli run <claude|codex|omp>');
1094
1038
  noticeGlobalDrift(roots);
1095
- cleanupScope(global ? os.homedir() : projectDir(), global,
1096
- global ? [] : targets.map((target) => target === 'agents' ? 'AGENTS.md' : 'CLAUDE.md'));
1039
+ cleanupScope(global ? os.homedir() : projectDir(), global);
1097
1040
  if (!global) noticeIgnoredInstallFiles(installedSkills);
1098
1041
  return roots;
1099
1042
  });
1100
1043
  }
1101
1044
 
1102
1045
  // Cleanup visits existing paths in the command's scope; it never creates an install.
1103
- function cleanupScope(base, global, activeInstructions = []) {
1046
+ function cleanupScope(base, global) {
1104
1047
  const warned = new Set();
1105
1048
  let found = false;
1106
1049
  const warning = (file, error) => {
@@ -1206,15 +1149,15 @@ function cleanupScope(base, global, activeInstructions = []) {
1206
1149
  log(' ✕ .claude/settings.json retired entries (removed)', 'dim');
1207
1150
  }
1208
1151
  });
1209
- const instructions = global ? [] : ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md', '.cursorrules', '.windsurfrules', '.github/copilot-instructions.md'];
1210
- for (const name of instructions.filter((name) => !activeInstructions.includes(name))) {
1152
+ // CLAUDE.md and AGENTS.md stay as they are here: only a reviewed cleanup changes them.
1153
+ const instructions = global ? [] : ['GEMINI.md', '.cursorrules', '.windsurfrules', '.github/copilot-instructions.md'];
1154
+ for (const name of instructions) {
1211
1155
  const file = path.join(base, name);
1212
1156
  attempt(file, () => {
1213
1157
  if (!realParents(file) || !fs.lstatSync(file, { throwIfNoEntry: false })?.isFile()) return;
1214
- const evaluatorOnly = ['CLAUDE.md', 'AGENTS.md'].includes(name);
1215
- if (!updateInstructions(name, '', false, false, base, evaluatorOnly)) return;
1158
+ if (!removeInstructions(name, false, base)) return;
1216
1159
  found = true;
1217
- updateInstructions(name, '', false, true, base, evaluatorOnly);
1160
+ removeInstructions(name, true, base);
1218
1161
  });
1219
1162
  }
1220
1163
  return found;
@@ -1360,7 +1303,7 @@ async function init({ yes, claude, global }) {
1360
1303
  install(targets, global);
1361
1304
  log('\n💡 Add optional addons later: run `npx devlyn-cli` without -y', 'dim');
1362
1305
  const hints = [
1363
- ...(targets.includes('claude') ? [] : [`${global ? '~/.claude/skills' : 'CLAUDE.md + .claude/'} for Claude Code: add --claude`]),
1306
+ ...(targets.includes('claude') ? [] : [`${global ? '~/.claude/skills' : '.claude/'} for Claude Code: add --claude`]),
1364
1307
  ...(global ? [] : ['every project on this machine: add --global']),
1365
1308
  ];
1366
1309
  if (hints.length > 0) log(` ${hints.join(' · ')}`, 'dim');
@@ -1370,8 +1313,8 @@ async function init({ yes, claude, global }) {
1370
1313
  }
1371
1314
 
1372
1315
  const targetOptions = [
1373
- { key: 'agents', name: 'AGENTS.md — Codex · omp · Pi · Grok', desc: 'AGENTS.md + .agents/skills' },
1374
- { key: 'claude', name: 'CLAUDE.md — Claude Code', desc: 'CLAUDE.md + .claude/ (skills, settings)' },
1316
+ { key: 'agents', name: 'Codex · omp · Pi · Grok', desc: '.agents/skills' },
1317
+ { key: 'claude', name: 'Claude Code', desc: '.claude/ (skills, settings)' },
1375
1318
  ];
1376
1319
  const preselected = claude || hasDevlynClaude(false) || (global && hasDevlynClaude(true)) ? [0, 1] : [0];
1377
1320
  const targets = (await multiSelect('What to install', targetOptions, preselected)).map((option) => option.key);
@@ -1405,12 +1348,13 @@ async function init({ yes, claude, global }) {
1405
1348
  function showHelp() {
1406
1349
  showLogo();
1407
1350
  log('Usage:', 'green');
1408
- log(' npx devlyn-cli Install/update devlyn: choose what (AGENTS.md, CLAUDE.md) and where (this project or global)');
1351
+ log(' npx devlyn-cli Install/update devlyn skills: choose which agents and where (this project or global)');
1409
1352
  log(' npx devlyn-cli update Update every devlyn install already here (this project and your global skill folders) to this version; installs nothing new');
1410
- log(' npx devlyn-cli -y Without prompts: AGENTS.md + .agents/skills, plus CLAUDE.md + .claude/ if this project has them');
1411
- log(' npx devlyn-cli -y --claude Also install CLAUDE.md + .claude/ for Claude Code');
1353
+ log(' npx devlyn-cli -y Without prompts: .agents/skills, plus .claude/ if this project has it');
1354
+ log(' npx devlyn-cli -y --claude Also install .claude/ for Claude Code');
1412
1355
  log(' npx devlyn-cli -y --global Skills only, for every project on this machine (~/.agents/skills, ~/.codex/skills; ~/.claude/skills with --claude, or when it already has a devlyn install)');
1413
1356
  log(' npx devlyn-cli list List available skills');
1357
+ log(' npx devlyn-cli run <engine> Start claude, codex or omp in this terminal with the devlyn principles and skills');
1414
1358
  log(' npx devlyn-cli --help Show this help\n');
1415
1359
  log('Optional skills (select during install):', 'green');
1416
1360
  OPTIONAL_ADDONS.filter((a) => a.type === 'local').forEach((skill) => {
@@ -1704,10 +1648,10 @@ function cleanupProjectFiles(base, includedHooks = new Set(), callerBase = base)
1704
1648
  return items;
1705
1649
  }
1706
1650
 
1707
- // An intact managed block from an earlier release is brought up to date; owner text around it stays
1708
- // byte-for-byte (updateInstructions). A current block is the team's principles and stays.
1651
+ // The release gives each session the principles (docs/harness-run.md), so an intact copy in a project
1652
+ // file is removed, owner text around it kept byte-for-byte (removeInstructions). A copy someone
1653
+ // edited stays exactly as it is: only recognized content is ever removed.
1709
1654
  function cleanupInstructions(base) {
1710
- const { inspectInstruction } = require('./harness-status');
1711
1655
  const items = [];
1712
1656
  for (const name of ['CLAUDE.md', 'AGENTS.md']) {
1713
1657
  const file = path.join(base, name);
@@ -1716,10 +1660,10 @@ function cleanupInstructions(base) {
1716
1660
  let text;
1717
1661
  try { text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(fs.readFileSync(file)); }
1718
1662
  catch { continue; }
1719
- if (!/<!-- devlyn:instructions:begin sha256=/.test(text)) continue;
1720
- const template = fs.readFileSync(path.join(CONFIG_SOURCE, '..', name), 'utf8');
1721
- if (inspectInstruction(text, template, name).ownership === 'outdated') {
1722
- items.push({ kind: 'instructions', path: file, base, action: 'edit', reasons: [] });
1663
+ if (removeInstructions(name, false, base)) {
1664
+ items.push({ kind: 'instructions', path: file, base, action: 'remove', reasons: [] });
1665
+ } else if (holdsDevlynDefaults(name, text)) {
1666
+ items.push({ kind: 'instructions', path: file, base, action: 'skip', reasons: [cleanupReason('modified-copy')] });
1723
1667
  }
1724
1668
  }
1725
1669
  return items;
@@ -2071,6 +2015,11 @@ function cleanupValidateItem(item) {
2071
2015
  function cleanupApplyItem(item) {
2072
2016
  const invalid = cleanupValidateItem(item);
2073
2017
  if (invalid) return invalid;
2018
+ // Before the generic removal: only the proven copy goes, never the owner's file.
2019
+ if (item.kind === 'instructions') {
2020
+ removeInstructions(path.basename(item.path), true, path.dirname(item.path));
2021
+ return null;
2022
+ }
2074
2023
  if (cleanupAction(item) === 'remove') {
2075
2024
  if (item.kind === 'legacy-hook') {
2076
2025
  const { safe, settings } = cleanupReadSettings(item.base);
@@ -2087,10 +2036,6 @@ function cleanupApplyItem(item) {
2087
2036
  else if (after !== text) replaceFile(item.path, after);
2088
2037
  return null;
2089
2038
  }
2090
- if (item.kind === 'instructions') {
2091
- updateInstructions(path.basename(item.path), '', true, true, path.dirname(item.path));
2092
- return null;
2093
- }
2094
2039
  return cleanupReason('unknown-item');
2095
2040
  }
2096
2041
 
@@ -2169,7 +2114,7 @@ function cleanupSupport(item) {
2169
2114
  }
2170
2115
 
2171
2116
  function cleanupCanInclude(item) {
2172
- return !cleanupSupport(item) && item.action === 'skip'
2117
+ return !cleanupSupport(item) && item.kind !== 'instructions' && item.action === 'skip'
2173
2118
  && item.reasons.length === 1 && item.reasons[0].code === 'modified-copy';
2174
2119
  }
2175
2120
 
@@ -2391,7 +2336,7 @@ function cleanupDeliveryCommit(worktree, items, current, release, scope, base) {
2391
2336
  }
2392
2337
  const add = cleanupGit(worktree, ['add', '-u', '--', ...applied]);
2393
2338
  const commit = add.status === 0 && cleanupGit(worktree, ['-c', 'user.useConfigOnly=true', 'commit', '-q', '-m',
2394
- `chore(devlyn): remove legacy devlyn-cli copies\n\nDevlyn now delivers devlyn-cli ${PKG.version} to each session it starts, so the copies earlier\ninstalls committed here are no longer used. The managed principles block stays for teammates.`]);
2339
+ `chore(devlyn): remove legacy devlyn-cli copies\n\nDevlyn now delivers devlyn-cli ${PKG.version} to each session it starts, so the copies earlier\ninstalls committed here, the principles block included, are no longer used.`]);
2395
2340
  if (!commit || commit.status !== 0) return release(cleanupReason('delivery-failed', { detail: (commit || add).stderr.trim() }));
2396
2341
  // This check must observe the committed delivery worktree, not project the still-unclean remote.
2397
2342
  const inputs = { machine: false, projects: [worktree], accountHomes: [] };
@@ -2527,6 +2472,9 @@ async function main() {
2527
2472
  const command = args[0];
2528
2473
 
2529
2474
  switch (command) {
2475
+ case 'run':
2476
+ await require('./harness-run').runMain(args.slice(1));
2477
+ break;
2530
2478
  case 'cleanup':
2531
2479
  require('./release').runJson(() => cleanupCommand(args.slice(1)));
2532
2480
  break;