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 +6 -6
- package/CLAUDE.md +7 -7
- package/README.md +20 -5
- package/bin/devlyn.js +10 -52
- package/bin/instruction-templates.json +52 -0
- package/bin/instructions.js +137 -0
- package/config/skills/_shared/adapters/claude.md +1 -5
- package/config/skills/_shared/adapters/codex.md +1 -5
- package/config/skills/_shared/adapters/omp.md +0 -4
- package/config/skills/_shared/expected.schema.json +2 -2
- package/config/skills/_shared/platform-support.py +4 -1
- package/config/skills/_shared/resolve-bootstrap.py +22 -2
- package/config/skills/_shared/spec-verify-check.py +424 -97
- package/config/skills/_shared/state-phase-write.py +202 -32
- package/config/skills/_shared/task-complete.py +9 -1
- package/config/skills/devlyn:ideate/SKILL.md +1 -1
- package/config/skills/devlyn:ideate/references/from-spec-mode.md +1 -1
- package/config/skills/devlyn:ideate/references/spec-template.md +2 -2
- package/config/skills/devlyn:resolve/SKILL.md +4 -4
- package/config/skills/devlyn:resolve/references/free-form-mode.md +2 -2
- package/config/skills/devlyn:resolve/references/phases/build-gate.md +3 -2
- package/config/skills/devlyn:resolve/references/state-schema.md +1 -1
- package/package.json +1 -1
- package/scripts/lint-skills.sh +2 -2
package/AGENTS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Codex Project Instructions
|
|
2
2
|
|
|
3
|
-
devlyn-cli installs
|
|
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
|
-
|
|
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` —
|
|
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
|
|
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`
|
|
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.
|
|
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
|
|
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
|
-
|
|
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` —
|
|
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
|
|
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`
|
|
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.
|
|
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).
|
|
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 —
|
|
34
|
+
## How It Works — Direct Work or Full Pipeline
|
|
35
35
|
|
|
36
|
-
devlyn-cli
|
|
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
|
-
|
|
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
|
-
###
|
|
63
|
+
### Choose direct execution or `/devlyn:resolve`
|
|
64
64
|
|
|
65
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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.
|
|
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 (
|
|
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.
|
|
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.
|
|
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": "
|
|
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
|
-
|
|
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):
|