devlyn-cli 3.1.3 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -15
- package/bin/devlyn.js +36 -15
- package/bin/instruction-templates.json +36 -6
- package/bin/instructions.js +69 -19
- package/config/skills/_shared/archive_run.py +5 -1
- package/config/skills/_shared/codex-config.md +3 -14
- package/config/skills/_shared/engine-preflight.md +3 -3
- package/config/skills/_shared/role-config.py +14 -2
- package/config/skills/_shared/spec-verify-check.py +3 -4
- package/config/skills/_shared/state-phase-write.py +296 -18
- package/config/skills/_shared/task-complete.py +37 -4
- package/config/skills/_shared/terminal-claim-check.py +2 -2
- package/config/skills/devlyn:resolve/SKILL.md +25 -47
- package/config/skills/devlyn:resolve/references/phases/build-gate.md +5 -5
- package/config/skills/devlyn:resolve/references/phases/cleanup.md +5 -11
- package/config/skills/devlyn:resolve/references/phases/implement.md +1 -0
- package/config/skills/devlyn:resolve/references/phases/plan.md +3 -3
- package/config/skills/devlyn:resolve/references/state-schema.md +9 -8
- package/package.json +2 -1
- package/scripts/lint-skills.sh +12 -2
- package/scripts/test-owner-phases.py +193 -0
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ If devlyn-cli saved you time, [give it a star](https://github.com/fysoul17/devly
|
|
|
27
27
|
npx devlyn-cli
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
That's it. The installer opens with a single **agent selector** — pick any combination of **Claude Code**, **Codex CLI**, **oh-my-pi (omp)**, **Pi**, or **Grok Build CLI**, and devlyn installs into every one you choose in a single pass. Claude Code and any agent already present on your machine are pre-checked, so the common case is just Enter. Skill-capable agents receive the `devlyn:resolve`, `devlyn:ideate`, and `devlyn:design-ui` skills — plus the `devlyn:engines` and `devlyn:queue` utilities — in the directory each one loads from: Codex → `~/.codex/skills/`, Grok → `~/.grok/skills/`, while **omp and Pi share `~/.agents/skills/`** — the cross-agent standard both read — so the bundle is written there once, not duplicated per agent. In Codex / omp / Pi, invoke them as skills (`$devlyn:resolve`, `$devlyn:ideate`, `$devlyn:design-ui`); in Claude Code and Grok Build CLI they're slash commands (`/devlyn:resolve`). Rerunning refreshes skills
|
|
30
|
+
That's it. The installer opens with a single **agent selector** — pick any combination of **Claude Code**, **Codex CLI**, **oh-my-pi (omp)**, **Pi**, or **Grok Build CLI**, and devlyn installs into every one you choose in a single pass. Claude Code and any agent already present on your machine are pre-checked, so the common case is just Enter. Skill-capable agents receive the `devlyn:resolve`, `devlyn:ideate`, and `devlyn:design-ui` skills — plus the `devlyn:engines` and `devlyn:queue` utilities — in the directory each one loads from: Codex → `~/.codex/skills/`, Grok → `~/.grok/skills/`, while **omp and Pi share `~/.agents/skills/`** — the cross-agent standard both read — so the bundle is written there once, not duplicated per agent. In Codex / omp / Pi, invoke them as skills (`$devlyn:resolve`, `$devlyn:ideate`, `$devlyn:design-ui`); in Claude Code and Grok Build CLI they're slash commands (`/devlyn:resolve`). Rerunning refreshes skills and the managed instruction block for selected agents while preserving project rules outside it. See [Migration from earlier versions](#migration-from-earlier-versions) for legacy migration and merge recovery. (`npx devlyn-cli -y` installs the Claude core non-interactively; `npx devlyn-cli agents <cli>` adds one agent later.)
|
|
31
31
|
|
|
32
32
|
---
|
|
33
33
|
|
|
@@ -76,10 +76,10 @@ Internal phases run sequentially with file-based handoff via `.devlyn/pipeline.s
|
|
|
76
76
|
PLAN → IMPLEMENT → BUILD_GATE → CLEANUP → VERIFY (fresh subagent, findings-only)
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
- **PLAN**
|
|
80
|
-
- **BUILD_GATE** runs your project's real compilers, typecheckers, linters, and `python3 .claude/skills/_shared/spec-verify-check.py` (verification commands literal-match). Auto-detects Next.js, Rust, Go, Solidity, Expo, Swift, and Dockerfiles. Browser flows route through Chrome MCP → Playwright → curl tier.
|
|
79
|
+
- **PLAN** runs in the owner context and freezes requirements, verification and the authorized file surface before implementation.
|
|
80
|
+
- **BUILD_GATE** runs commands in the owner context without another model invocation, using your project's real compilers, typecheckers, linters, and `python3 .claude/skills/_shared/spec-verify-check.py` (verification commands literal-match). Auto-detects Next.js, Rust, Go, Solidity, Expo, Swift, and Dockerfiles. Browser flows route through Chrome MCP → Playwright → curl tier.
|
|
81
81
|
- **VERIFY** runs in a fresh subagent context with no code-mutation tools — findings only, structurally independent.
|
|
82
|
-
- Git checkpoints at every phase for safe rollback. Fix-loop budget shared across BUILD_GATE and VERIFY (`--max-rounds N`, default 4).
|
|
82
|
+
- Git checkpoints at every phase for safe rollback. Fix-loop budget shared across BUILD_GATE, CLEANUP and VERIFY (`--max-rounds N`, default 4).
|
|
83
83
|
|
|
84
84
|
Common flags: `--engine claude|codex|omp` (default: the orchestrator-supported default), `--role-config <path>` (one-run worker/judge profiles, see below), `--bypass build-gate,cleanup`, `--pair-verify` (force pair-mode JUDGE in VERIFY), `--no-pair` (intentional solo VERIFY), `--risk-probes` / `--no-risk-probes`, `--perf` (per-phase timing).
|
|
85
85
|
`--pair-verify` and `--no-pair` are mutually exclusive; using both stops with `BLOCKED:invalid-flags`.
|
|
@@ -118,10 +118,10 @@ Stack tasks to run back-to-back without supervision. `/devlyn:queue add "<intent
|
|
|
118
118
|
devlyn separates three engine roles:
|
|
119
119
|
|
|
120
120
|
- **Orchestrator** — the CLI you opened (Claude Code, Codex, or omp) that drives the conversation and loop. The contract is symmetric (`CLAUDE.md` ↔ `AGENTS.md`), so the same phase-gated pipeline runs whichever you launch; the file artifacts (spec, queue, state) carry over if you switch.
|
|
121
|
-
- **Executor** (legacy) — the worker for IMPLEMENT /
|
|
121
|
+
- **Executor** (legacy) — the worker for IMPLEMENT, its included code/doc cleanup, and their repair rounds, and the primary VERIFY judge, unless either is separately profiled (below). Defaults to the orchestrator-supported default. PLAN is orchestrator-fixed and never inherits `--engine` or an executor pin. An absent primary profile follows the legacy executor, not an opt-in worker profile.
|
|
122
122
|
- **Pair judge** — the first available *other* engine, default for VERIFY and conditional for risk probes.
|
|
123
123
|
|
|
124
|
-
`--engine <name>` sets the legacy executor for one run as an engine-only entry (it clears any lower-precedence profile's model/effort).
|
|
124
|
+
`--engine <name>` sets the legacy executor for one run as an engine-only entry (it clears any lower-precedence profile's model/effort). PLAN stays in the owner context; BUILD_GATE/CLEANUP run owner commands; risk-probe derivation keeps its existing route. VERIFY/JUDGE runs pair mode by default when the OTHER engine is available.
|
|
125
125
|
|
|
126
126
|
Pin roles durably with `/devlyn:engines` (no args shows the role table + detected engines; `executor <name>` / `pair <name>,...` / `role <worker|primary_judge|pair_judge> <JSON object>` / `role <name> clear` manage the pins, stored machine-local in `.devlyn/engines.json`; `clear` removes all legacy and role pins, preserves unrelated keys, and deletes an empty file). Pins fail closed: an unavailable pinned engine stops with `BLOCKED:<engine>-unavailable`, and a name with no `_shared/adapters/<name>.md` adapter stops with `BLOCKED:invalid-engine-config`. New engines plug in by shipping an adapter file — no skill changes.
|
|
127
127
|
|
|
@@ -144,12 +144,12 @@ Pin roles durably with `/devlyn:engines` (no args shows the role table + detecte
|
|
|
144
144
|
`--role-config <path>` supplies the same `{"roles": {...}}` object for one run only, with no other top-level keys; the flag may appear once.
|
|
145
145
|
|
|
146
146
|
- **Precedence** — an entry replaces the lower one as a whole; model/effort never merge across engines. Worker and primary judge: run `--role-config` entry > `--engine` (engine-only) > project role entry > legacy executor/default. Pair judge: run entry > project entry > `pair_judge_priority` / the OTHER-engine complement. A missing `model`/`effort` keeps that route's current default and is shown as inherited/unresolved.
|
|
147
|
-
- **Boundaries** — `worker` covers IMPLEMENT /
|
|
147
|
+
- **Boundaries** — `worker` covers IMPLEMENT, its included code/doc cleanup, and their repair rounds; `primary_judge` and `pair_judge` cover VERIFY only. PLAN stays orchestrator-fixed, BUILD_GATE/CLEANUP run owner commands, and risk-probe derivation stays outside these controls. OTHER must differ from the primary by engine: an explicit same-engine pair is `BLOCKED:invalid-engine-config`. `--no-pair` is unchanged — explicit solo VERIFY; an unused pair entry is neither dispatched nor availability-checked.
|
|
148
148
|
- **Initial explicit routes** — Codex worker/primary/pair accept exact `model` + `effort` (dispatched as `-m` / `-c model_reasoning_effort=`, validated against the installed Codex CLI's native model metadata for its version). Claude primary/pair judges pass an exact `model` as `--model` and validate native result identity; model-only selection needs no source update for a new ID. Explicit `effort` separately requires the adapter's version/model support declaration. The Claude worker stays the native `Agent` route and is engine-only: explicit `model`/`effort` on a Claude worker is `BLOCKED:unsupported-role-option` — a supported-route boundary, not a claim that Claude cannot implement. Other adapters (omp, grok) are engine-only for their eligible roles.
|
|
149
149
|
- **Errors** — `BLOCKED:unsupported-role-option` names the role, engine and field with guidance; there is no clamp, fallback, substituted model, or weaker retry. An explicitly selected engine (flag, pin, or profile) that is unavailable stops with `BLOCKED:<engine>-unavailable`. A native warning that a requested option was ignored is a failed promise even at exit 0.
|
|
150
150
|
- **Requested vs observed** — `/devlyn:engines` status prints each role's requested engine/model/effort, source and dispatch channel before dispatch; it describes a request and does not prove a native invocation happened. Effective model is reported only with native evidence; a Codex worker receipt binds requested dispatch and leaves effective model unknown. A reported worker model reroute blocks completion even at exit 0. SURFACE_CLOSE inherits native Claude model settings and records the model from its required JSON result. Effective effort stays unknown unless native evidence establishes it — invocation arguments prove dispatch, not provider-internal reasoning.
|
|
151
151
|
|
|
152
|
-
`--engine codex` routes IMPLEMENT /
|
|
152
|
+
`--engine codex` routes IMPLEMENT and its included code/doc cleanup to Codex as a supported engine-only route (exact model/effort via the profiles above). Historical record: iter-0020 closed Codex BUILD/IMPLEMENT below the quality floor on the 9-fixture suite (L2 vs L1 = −3.6, 3/8 gated fixtures cleared the +5 margin floor — release-readiness FAIL). Separately, PLAN-pair remains research-only: iter-0033g + iter-0034 closed it with explicit unblock conditions (container/sandbox infra OR production telemetry capturing positive evidence of subagent introspection). Install the Codex CLI (https://platform.openai.com/docs/codex) and pass the flag explicitly to opt in:
|
|
153
153
|
|
|
154
154
|
```
|
|
155
155
|
/devlyn:resolve "fix the auth bug" --engine codex
|
|
@@ -232,13 +232,19 @@ CLIs. Claude uses `CLAUDE.md`; Codex, Grok, omp and Pi share `AGENTS.md`.
|
|
|
232
232
|
Devlyn updates its checksum-marked instruction block and preserves project-specific
|
|
233
233
|
rules outside it. Keep custom rules outside the block; they take precedence over
|
|
234
234
|
its defaults. Exact pre-update backups are saved in `.devlyn/instructions/`.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
235
|
+
When recovery is needed, pre-existing symlinks or non-directory entries at `.devlyn/` or
|
|
236
|
+
`.devlyn/instructions/` stop installation without replacing the instruction file.
|
|
237
|
+
|
|
238
|
+
Templates from versions 3.0.0–3.1.2 migrate automatically, including custom
|
|
239
|
+
text before or after the template. Replaced or extended introductions under the
|
|
240
|
+
original project heading also migrate when the entire body from `## North Star`
|
|
241
|
+
onward matches a known template hash. Replacement project rules are preserved
|
|
242
|
+
outside the managed block; only an exact original introduction is removed.
|
|
243
|
+
Recognizable leftover Devlyn text, changed bodies and ambiguous templates stop
|
|
244
|
+
with a concise recovery message and leave the original unchanged. The message
|
|
245
|
+
links an exact `.backup`, new defaults in `.incoming`, and a `.merge.md` guide.
|
|
246
|
+
Compare the two files, preserve all project rules outside one new managed block,
|
|
247
|
+
and rerun the same install command. Earlier selected agents may already be updated.
|
|
242
248
|
Plain custom instruction files retain their contents and gain a managed block.
|
|
243
249
|
Use 3.1.3 or newer consistently: older installers still overwrite `CLAUDE.md`.
|
|
244
250
|
|
package/bin/devlyn.js
CHANGED
|
@@ -9,7 +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
|
+
const { updateInstructions, InstructionError } = require('./instructions');
|
|
13
13
|
|
|
14
14
|
// The devlyn skill bundle installed into every skill-capable agent's loader
|
|
15
15
|
// directory. Single source of truth so codex/omp/pi stay in lockstep — adding a
|
|
@@ -765,8 +765,30 @@ function installAgentsForAllDetected() {
|
|
|
765
765
|
// Install the Claude Code core config: project .claude/ (skills, templates,
|
|
766
766
|
// settings, CLAUDE.md, .gitignore) plus global ~/.claude/settings.json tweaks.
|
|
767
767
|
// Extracted so the unified target selector installs it only when "Claude Code"
|
|
768
|
-
// is chosen. Check instruction conflicts before
|
|
768
|
+
// is chosen. Check global settings and instruction conflicts before writes.
|
|
769
769
|
function installClaudeCore() {
|
|
770
|
+
const globalClaudeDir = path.join(os.homedir(), '.claude');
|
|
771
|
+
const globalSettingsPath = path.join(globalClaudeDir, 'settings.json');
|
|
772
|
+
function readGlobalSettings() {
|
|
773
|
+
let settings = {};
|
|
774
|
+
try {
|
|
775
|
+
if (fs.lstatSync(globalSettingsPath, { throwIfNoEntry: false })) {
|
|
776
|
+
settings = JSON.parse(fs.readFileSync(globalSettingsPath, 'utf8'));
|
|
777
|
+
}
|
|
778
|
+
} catch (error) {
|
|
779
|
+
const reason = error instanceof SyntaxError ? 'invalid JSON' : error.message;
|
|
780
|
+
throw new Error(`Cannot merge ${globalSettingsPath}: ${reason}. Original preserved; correct the file or its access and rerun installation.`);
|
|
781
|
+
}
|
|
782
|
+
if (!settings || typeof settings !== 'object' || Array.isArray(settings)) {
|
|
783
|
+
throw new Error(`Cannot merge ${globalSettingsPath}: root must be a JSON object. Original preserved; correct it and rerun installation.`);
|
|
784
|
+
}
|
|
785
|
+
if (!Object.prototype.hasOwnProperty.call(settings, 'env')) settings.env = {};
|
|
786
|
+
if (!settings.env || typeof settings.env !== 'object' || Array.isArray(settings.env)) {
|
|
787
|
+
throw new Error(`Cannot merge ${globalSettingsPath}: env must be a JSON object. Original preserved; correct it and rerun installation.`);
|
|
788
|
+
}
|
|
789
|
+
return settings;
|
|
790
|
+
}
|
|
791
|
+
readGlobalSettings();
|
|
770
792
|
updateInstructions('CLAUDE.md');
|
|
771
793
|
const targetDir = getTargetDir();
|
|
772
794
|
const skillsDir = path.join(targetDir, 'skills');
|
|
@@ -904,17 +926,8 @@ function installClaudeCore() {
|
|
|
904
926
|
}
|
|
905
927
|
|
|
906
928
|
// Configure global Claude Code settings (~/.claude/settings.json)
|
|
907
|
-
|
|
908
|
-
const
|
|
909
|
-
let globalSettings = {};
|
|
910
|
-
if (fs.existsSync(globalSettingsPath)) {
|
|
911
|
-
try {
|
|
912
|
-
globalSettings = JSON.parse(fs.readFileSync(globalSettingsPath, 'utf8'));
|
|
913
|
-
} catch {
|
|
914
|
-
globalSettings = {};
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
if (!globalSettings.env) globalSettings.env = {};
|
|
929
|
+
// Project settings may refer to this same file; merge the latest bytes.
|
|
930
|
+
const globalSettings = readGlobalSettings();
|
|
918
931
|
let globalSettingsChanged = false;
|
|
919
932
|
if (!globalSettings.env.CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING) {
|
|
920
933
|
globalSettings.env.CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING = '1';
|
|
@@ -1204,6 +1217,8 @@ function showBenchmarkModeHelp(mode) {
|
|
|
1204
1217
|
|
|
1205
1218
|
// Main
|
|
1206
1219
|
const args = process.argv.slice(2);
|
|
1220
|
+
|
|
1221
|
+
async function main() {
|
|
1207
1222
|
const command = args[0];
|
|
1208
1223
|
|
|
1209
1224
|
switch (command) {
|
|
@@ -1213,7 +1228,7 @@ switch (command) {
|
|
|
1213
1228
|
break;
|
|
1214
1229
|
case '-y':
|
|
1215
1230
|
case '--yes':
|
|
1216
|
-
init(true);
|
|
1231
|
+
await init(true);
|
|
1217
1232
|
break;
|
|
1218
1233
|
case 'list':
|
|
1219
1234
|
case 'ls':
|
|
@@ -1294,10 +1309,16 @@ switch (command) {
|
|
|
1294
1309
|
}
|
|
1295
1310
|
case 'init':
|
|
1296
1311
|
case undefined:
|
|
1297
|
-
init(false);
|
|
1312
|
+
await init(false);
|
|
1298
1313
|
break;
|
|
1299
1314
|
default:
|
|
1300
1315
|
log(`Unknown command: ${command}`, 'yellow');
|
|
1301
1316
|
showHelp();
|
|
1302
1317
|
process.exit(1);
|
|
1303
1318
|
}
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
main().catch((error) => {
|
|
1322
|
+
console.error(error instanceof InstructionError ? `\n${error.message}` : error);
|
|
1323
|
+
process.exitCode = 1;
|
|
1324
|
+
});
|
|
@@ -5,14 +5,24 @@
|
|
|
5
5
|
"3.0.0"
|
|
6
6
|
],
|
|
7
7
|
"length": 11091,
|
|
8
|
-
"sha256": "099e68f3e203fa735bb1ebcd780a0e18bde462ea7de5f74fb3d17cee5e64b63a"
|
|
8
|
+
"sha256": "099e68f3e203fa735bb1ebcd780a0e18bde462ea7de5f74fb3d17cee5e64b63a",
|
|
9
|
+
"preamble": "# Codex Project Instructions\n\ndevlyn-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.\n\n",
|
|
10
|
+
"body": {
|
|
11
|
+
"length": 10816,
|
|
12
|
+
"sha256": "8b972c2c379e5ff8411bf1214f926a3b9904bdf857ac6b30a8c79ce0e32182e3"
|
|
13
|
+
}
|
|
9
14
|
},
|
|
10
15
|
{
|
|
11
16
|
"versions": [
|
|
12
17
|
"3.0.1"
|
|
13
18
|
],
|
|
14
19
|
"length": 12166,
|
|
15
|
-
"sha256": "33fbd5b01c8e65f4c663f95423a45b16dcf5bb28f1fd0c6791079abd20c267ae"
|
|
20
|
+
"sha256": "33fbd5b01c8e65f4c663f95423a45b16dcf5bb28f1fd0c6791079abd20c267ae",
|
|
21
|
+
"preamble": "# Codex Project Instructions\n\ndevlyn-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.\n\n",
|
|
22
|
+
"body": {
|
|
23
|
+
"length": 11891,
|
|
24
|
+
"sha256": "52326a832df6d5ab13bddca5a1a01ad338a3db71d172c70d3eaed806668db4d8"
|
|
25
|
+
}
|
|
16
26
|
},
|
|
17
27
|
{
|
|
18
28
|
"versions": [
|
|
@@ -21,7 +31,12 @@
|
|
|
21
31
|
"3.1.2"
|
|
22
32
|
],
|
|
23
33
|
"length": 12581,
|
|
24
|
-
"sha256": "acd3672efaa33e75455f8530a7425e950c01c822c6f5d85d7dbc9d7ff1958028"
|
|
34
|
+
"sha256": "acd3672efaa33e75455f8530a7425e950c01c822c6f5d85d7dbc9d7ff1958028",
|
|
35
|
+
"preamble": "# Codex Project Instructions\n\ndevlyn-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.\n\n",
|
|
36
|
+
"body": {
|
|
37
|
+
"length": 12306,
|
|
38
|
+
"sha256": "0de6061f9dff8e88964e542bb8a5e113996e480bef597774fb15eaa766472129"
|
|
39
|
+
}
|
|
25
40
|
}
|
|
26
41
|
],
|
|
27
42
|
"CLAUDE.md": [
|
|
@@ -30,14 +45,24 @@
|
|
|
30
45
|
"3.0.0"
|
|
31
46
|
],
|
|
32
47
|
"length": 24434,
|
|
33
|
-
"sha256": "29ddcc5ef4d5b44b2ba6aae22bd3c0c033ceaa47b240bd647aaefbe00f2a6e4e"
|
|
48
|
+
"sha256": "29ddcc5ef4d5b44b2ba6aae22bd3c0c033ceaa47b240bd647aaefbe00f2a6e4e",
|
|
49
|
+
"preamble": "# Project Instructions\n\ndevlyn-cli installs `/devlyn:ideate` (optional) and `/devlyn:resolve` (required) into Claude Code, plus the contract below. These principles are non-negotiable on every change \u2014 yours and any sub-agent's.\n\n",
|
|
50
|
+
"body": {
|
|
51
|
+
"length": 24204,
|
|
52
|
+
"sha256": "555d8b9a4c154ec5225105919836a4d9831c76c1b1c7ee4c19fecac8fbd1c948"
|
|
53
|
+
}
|
|
34
54
|
},
|
|
35
55
|
{
|
|
36
56
|
"versions": [
|
|
37
57
|
"3.0.1"
|
|
38
58
|
],
|
|
39
59
|
"length": 25417,
|
|
40
|
-
"sha256": "50301bf98507c83da1576be7f443885236e7d421ff2e40fd077ed9652ea3a2e0"
|
|
60
|
+
"sha256": "50301bf98507c83da1576be7f443885236e7d421ff2e40fd077ed9652ea3a2e0",
|
|
61
|
+
"preamble": "# Project Instructions\n\ndevlyn-cli installs `/devlyn:ideate` (optional) and `/devlyn:resolve` (required) into Claude Code, plus the contract below. These principles are non-negotiable on every change \u2014 yours and any sub-agent's.\n\n",
|
|
62
|
+
"body": {
|
|
63
|
+
"length": 25187,
|
|
64
|
+
"sha256": "53a933e22369438a5d9dd7aa5f778909aeba5a38d5e2e68b8a36dab5c874d84d"
|
|
65
|
+
}
|
|
41
66
|
},
|
|
42
67
|
{
|
|
43
68
|
"versions": [
|
|
@@ -46,7 +71,12 @@
|
|
|
46
71
|
"3.1.2"
|
|
47
72
|
],
|
|
48
73
|
"length": 26152,
|
|
49
|
-
"sha256": "d11a863907dabdf827063df7133a8a46a14e018f608884f08c601bf36e0f3f0c"
|
|
74
|
+
"sha256": "d11a863907dabdf827063df7133a8a46a14e018f608884f08c601bf36e0f3f0c",
|
|
75
|
+
"preamble": "# Project Instructions\n\ndevlyn-cli installs `/devlyn:ideate` (optional) and `/devlyn:resolve` (required) into Claude Code, plus the contract below. These principles are non-negotiable on every change \u2014 yours and any sub-agent's.\n\n",
|
|
76
|
+
"body": {
|
|
77
|
+
"length": 25922,
|
|
78
|
+
"sha256": "dd9f0f63f559a3b9a6e2bbf102045b01f6b73bce684dd27124ee6d6af3641b91"
|
|
79
|
+
}
|
|
50
80
|
}
|
|
51
81
|
]
|
|
52
82
|
}
|
package/bin/instructions.js
CHANGED
|
@@ -5,29 +5,52 @@ const legacyTemplates = require('./instruction-templates.json');
|
|
|
5
5
|
|
|
6
6
|
const BEGIN = '<!-- devlyn:instructions:begin';
|
|
7
7
|
const END = '<!-- devlyn:instructions:end -->';
|
|
8
|
+
const LEGACY = /^devlyn-cli installs |^# Devlyn Agent Instructions\r?$|^This contract serves one goal: any capable engine|^Engine roles: orchestrator =/m;
|
|
8
9
|
const normalize = (text) => text.replace(/\r\n/g, '\n');
|
|
9
10
|
const digest = (text) => crypto.createHash('sha256').update(text).digest('hex');
|
|
10
11
|
|
|
12
|
+
class InstructionError extends Error {}
|
|
13
|
+
|
|
11
14
|
function managedBlock(template, eol) {
|
|
12
15
|
const body = 'Project-specific instructions outside this managed block take precedence over these defaults.\n\n'
|
|
13
16
|
+ normalize(template).replace(/\n?$/, '\n');
|
|
14
17
|
return `${BEGIN} sha256=${digest(body)} -->\n${body}${END}\n`.replace(/\n/g, eol);
|
|
15
18
|
}
|
|
16
19
|
|
|
20
|
+
function matchedEnd(text, start, template) {
|
|
21
|
+
let end = start;
|
|
22
|
+
for (let n = 0; n < template.length && end < text.length; n++, end++) {
|
|
23
|
+
if (text[end] === '\r' && text[end + 1] === '\n') end++;
|
|
24
|
+
}
|
|
25
|
+
return digest(normalize(text.slice(start, end))) === template.sha256 ? end : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
// A digest is ownership evidence; a familiar heading alone is not.
|
|
18
29
|
function legacyRanges(text, templates) {
|
|
19
|
-
const starts = new Set([0, ...
|
|
20
|
-
|
|
30
|
+
const starts = [...new Set([0, ...(text.startsWith('\uFEFF') ? [1] : []),
|
|
31
|
+
...Array.from(text.matchAll(/^# (?:Codex )?Project Instructions\r?$/gm), (m) => m.index)])].sort((a, b) => a - b);
|
|
21
32
|
const ranges = [];
|
|
22
33
|
for (const start of starts) {
|
|
23
34
|
for (const template of templates) {
|
|
24
|
-
let end = start;
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
let end = matchedEnd(text, start, template);
|
|
36
|
+
let custom = '';
|
|
37
|
+
if (end === null && template.body) {
|
|
38
|
+
const prefix = text.slice(start, starts.find((offset) => offset > start));
|
|
39
|
+
const body = /^## North Star\r?$/m.exec(prefix);
|
|
40
|
+
const header = template.preamble.slice(0, template.preamble.indexOf('\n\n') + 2);
|
|
41
|
+
if (body && normalize(prefix).startsWith(header)) {
|
|
42
|
+
const bodyStart = start + body.index;
|
|
43
|
+
end = matchedEnd(text, bodyStart, template.body);
|
|
44
|
+
// Remove only an exact original preamble; preserve an edited one in full.
|
|
45
|
+
const preambleEnd = matchedEnd(text, start, { length: template.preamble.length, sha256: digest(template.preamble) });
|
|
46
|
+
custom = text.slice(preambleEnd ?? start, bodyStart);
|
|
47
|
+
}
|
|
27
48
|
}
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
ranges.push({ start, end });
|
|
49
|
+
if (end !== null) {
|
|
50
|
+
const existing = ranges.find((range) => range.start === start && range.end === end);
|
|
51
|
+
if (!existing) ranges.push({ start, end, custom });
|
|
52
|
+
// Releases may share a body. Prefer the exact owned preamble, if found.
|
|
53
|
+
else if (custom.length < existing.custom.length) existing.custom = custom;
|
|
31
54
|
}
|
|
32
55
|
}
|
|
33
56
|
}
|
|
@@ -35,12 +58,18 @@ function legacyRanges(text, templates) {
|
|
|
35
58
|
}
|
|
36
59
|
|
|
37
60
|
function retainFile(file, bytes) {
|
|
38
|
-
|
|
61
|
+
for (const directory of [path.dirname(path.dirname(file)), path.dirname(file)]) {
|
|
62
|
+
const stat = fs.lstatSync(directory, { throwIfNoEntry: false });
|
|
63
|
+
if (stat && !stat.isDirectory()) {
|
|
64
|
+
throw new InstructionError(`Instruction recovery needs a real directory: ${directory}. Move it aside and rerun installation.`);
|
|
65
|
+
}
|
|
66
|
+
if (!stat) fs.mkdirSync(directory);
|
|
67
|
+
}
|
|
39
68
|
const ignore = path.join(path.dirname(file), '.gitignore');
|
|
40
69
|
if (!fs.existsSync(ignore)) fs.writeFileSync(ignore, '*\n', { flag: 'wx', mode: 0o600 });
|
|
41
70
|
if (fs.existsSync(file)) {
|
|
42
71
|
if (!fs.lstatSync(file).isFile() || !fs.readFileSync(file).equals(bytes)) {
|
|
43
|
-
throw new
|
|
72
|
+
throw new InstructionError(`Instruction recovery file differs; preserved it: ${file}. Move it aside before retrying.`);
|
|
44
73
|
}
|
|
45
74
|
} else {
|
|
46
75
|
fs.writeFileSync(file, bytes, { flag: 'wx', mode: 0o600 });
|
|
@@ -63,22 +92,36 @@ function updateInstructions(name) {
|
|
|
63
92
|
const exists = Boolean(stat);
|
|
64
93
|
// Do not replace a shared instruction symlink or follow it outside the project.
|
|
65
94
|
if (stat && !stat.isFile()) {
|
|
66
|
-
throw new
|
|
95
|
+
throw new InstructionError(`Instruction file must be a regular file; preserved: ${dest}. Move it aside and rerun, then merge your shared rules outside the managed block.`);
|
|
67
96
|
}
|
|
68
97
|
const before = exists ? fs.readFileSync(dest) : Buffer.alloc(0);
|
|
69
98
|
let current;
|
|
70
99
|
try {
|
|
71
100
|
current = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(before);
|
|
72
101
|
} catch (cause) {
|
|
73
|
-
throw new
|
|
102
|
+
throw new InstructionError(`${name} must be UTF-8; original preserved. Convert its encoding and rerun installation.`, { cause });
|
|
74
103
|
}
|
|
75
104
|
const eol = current.match(/\r?\n/)?.[0] || '\n';
|
|
76
105
|
const block = managedBlock(template, eol);
|
|
77
106
|
const recovery = path.join(process.cwd(), '.devlyn', 'instructions');
|
|
78
107
|
const conflict = (reason) => {
|
|
79
108
|
const incoming = path.join(recovery, `${name}.${digest(block)}.incoming`);
|
|
109
|
+
const backup = path.join(recovery, `${name}.${digest(before)}.backup`);
|
|
110
|
+
const guide = path.join(recovery, `${name}.${digest(before)}.${digest(block)}.merge.md`);
|
|
111
|
+
retainFile(backup, before);
|
|
80
112
|
retainFile(incoming, Buffer.from(block));
|
|
81
|
-
|
|
113
|
+
retainFile(guide, Buffer.from(`# Merge ${name}\n\n`
|
|
114
|
+
+ `Installation stopped: ${reason}. The original file is unchanged.\n\n`
|
|
115
|
+
+ `- Backup before this install attempt: ${backup}\n- New Devlyn defaults: ${incoming}\n- File to update: ${dest}\n\n`
|
|
116
|
+
+ `1. Compare the backup and new defaults side by side. Identify all project-specific additions and edits; do not discard them.\n`
|
|
117
|
+
+ `2. Update ${name} with exactly one complete managed block from the new defaults. Keep project-specific rules outside that block; they take precedence. Resolve conflicting rules explicitly. Do not copy the new defaults over the whole file.\n`
|
|
118
|
+
+ `3. Review the result, then rerun the same install command (for example, npx devlyn-cli). Other selected agents may still need installation.\n\n`
|
|
119
|
+
+ `To undo this manual merge, restore that backup to ${dest}. Earlier backups are retained too.\n`));
|
|
120
|
+
throw new InstructionError(`${name} needs merge (${reason}).\n`
|
|
121
|
+
+ `Original preserved: ${dest}\nBackup: ${backup}\nNew defaults: ${incoming}\n`
|
|
122
|
+
+ `Merge instructions: ${guide}\n`
|
|
123
|
+
+ `Keep custom rules outside the managed block, then rerun the same install command.\n`
|
|
124
|
+
+ `Installation is incomplete; earlier selected agents may already have been updated.`);
|
|
82
125
|
};
|
|
83
126
|
let content;
|
|
84
127
|
if (current.includes(BEGIN) || current.includes(END)) {
|
|
@@ -97,16 +140,23 @@ function updateInstructions(name) {
|
|
|
97
140
|
content = current.slice(0, start.index + (start[0].startsWith('\uFEFF') ? 1 : 0)) + block + current.slice(end.index + end[0].length);
|
|
98
141
|
} else {
|
|
99
142
|
const normalized = normalize(template);
|
|
143
|
+
const bodyStart = normalized.indexOf('## North Star\n');
|
|
144
|
+
const preamble = normalized.slice(0, bodyStart);
|
|
145
|
+
const body = normalized.slice(bodyStart);
|
|
100
146
|
const ranges = legacyRanges(current, [
|
|
101
|
-
{ length: normalized.length, sha256: digest(normalized)
|
|
147
|
+
{ length: normalized.length, sha256: digest(normalized),
|
|
148
|
+
...(bodyStart > 0 ? { preamble, body: { length: body.length, sha256: digest(body) } } : {}) },
|
|
102
149
|
...(legacyTemplates[name] || []),
|
|
103
150
|
]);
|
|
104
151
|
if (ranges.length > 1) return conflict('multiple legacy templates');
|
|
105
152
|
if (ranges.length === 1) {
|
|
106
|
-
const { start, end } = ranges[0];
|
|
107
|
-
|
|
153
|
+
const { start, end, custom } = ranges[0];
|
|
154
|
+
if (LEGACY.test(current.slice(0, start) + custom + current.slice(end))) {
|
|
155
|
+
return conflict('edited legacy introduction or extra template text');
|
|
156
|
+
}
|
|
157
|
+
content = current.slice(0, start) + custom + block + current.slice(end);
|
|
108
158
|
} else {
|
|
109
|
-
if (
|
|
159
|
+
if (LEGACY.test(current)) {
|
|
110
160
|
return conflict('unrecognized or edited legacy template');
|
|
111
161
|
}
|
|
112
162
|
content = current + (current ? eol + eol : '') + block;
|
|
@@ -124,7 +174,7 @@ function updateInstructions(name) {
|
|
|
124
174
|
fs.writeFileSync(temp, bytes, { flag: 'wx', mode: exists ? stat.mode & 0o777 : 0o644 });
|
|
125
175
|
if (exists) fs.chmodSync(temp, stat.mode & 0o777);
|
|
126
176
|
if (fs.existsSync(dest) !== exists || (exists && !fs.readFileSync(dest).equals(before))) {
|
|
127
|
-
throw new
|
|
177
|
+
throw new InstructionError(`${name} changed during installation; preserved it. Rerun installation.`);
|
|
128
178
|
}
|
|
129
179
|
fs.renameSync(temp, dest);
|
|
130
180
|
} finally {
|
|
@@ -134,4 +184,4 @@ function updateInstructions(name) {
|
|
|
134
184
|
return true;
|
|
135
185
|
}
|
|
136
186
|
|
|
137
|
-
module.exports = { updateInstructions };
|
|
187
|
+
module.exports = { updateInstructions, InstructionError };
|
|
@@ -464,7 +464,11 @@ def move_artifacts(devlyn: pathlib.Path, dest: pathlib.Path) -> int:
|
|
|
464
464
|
try:
|
|
465
465
|
for source, target in moves:
|
|
466
466
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
467
|
-
|
|
467
|
+
try:
|
|
468
|
+
shutil.move(str(source), str(target))
|
|
469
|
+
except OSError:
|
|
470
|
+
target.unlink(missing_ok=True)
|
|
471
|
+
raise
|
|
468
472
|
completed.append((source, target))
|
|
469
473
|
except BaseException:
|
|
470
474
|
for source, target in reversed(completed):
|
|
@@ -22,7 +22,7 @@ if [ ! -f "$CODEX_MONITORED_PATH" ]; then
|
|
|
22
22
|
fi
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
**Read-only critique / adversarial review / debate** (`/devlyn:resolve` VERIFY pair-mode, plus any future ideate read-only critique).
|
|
25
|
+
**Read-only critique / adversarial review / debate** (`/devlyn:resolve` VERIFY pair-mode, plus any future ideate read-only critique). Codex returns findings on stdout; the orchestrator writes files.
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
DEVLYN_CODEX_PROMPT_FILE="<prompt-file>" CODEX_MONITORED_ISOLATED=1 CODEX_MONITORED_TIMEOUT_SEC=600 bash "$CODEX_MONITORED_PATH" \
|
|
@@ -43,24 +43,13 @@ DEVLYN_CODEX_PROMPT_FILE="<prompt-file>" bash "$CODEX_MONITORED_PATH" \
|
|
|
43
43
|
-
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
outbound network access inside that sandbox, including the loopback servers and
|
|
48
|
-
network-backed test gates that CI may exercise:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
DEVLYN_CODEX_PROMPT_FILE="<prompt-file>" bash "$CODEX_MONITORED_PATH" \
|
|
52
|
-
-C <project-root> \
|
|
53
|
-
-s workspace-write \
|
|
54
|
-
-c sandbox_workspace_write.network_access=true \
|
|
55
|
-
-c model_reasoning_effort=xhigh \
|
|
56
|
-
-
|
|
57
|
-
```
|
|
46
|
+
PLAN uses owner reasoning; BUILD_GATE/CLEANUP run owner commands, without separate Codex invocations. Code/doc cleanup runs inside the selected IMPLEMENT invocation. An already-open historical BUILD span without `execution_kind` keeps its receipt-bound workspace-write/network-true worker route; do not reinterpret or rewrite that evidence.
|
|
58
47
|
|
|
59
48
|
Notes:
|
|
60
49
|
- `DEVLYN_CODEX_PROMPT_FILE` — use the same file as `DEVLYN_INVOCATION_PROMPT_FILE` for receipt-bound calls. Retain the generated transport carrier with the prompt/session/argv evidence; a path or claimed environment value alone does not prove delivery. Do not use shell command substitution for prompt bytes.
|
|
61
50
|
- `-C` — project root so Codex's working directory matches.
|
|
62
51
|
- `-s read-only` / `-s workspace-write` — sandbox policy. Use workspace-write for implementation/probe phases that write tracked files or `.devlyn` artifacts.
|
|
63
|
-
- `-c sandbox_workspace_write.network_access=<true|false>` — required and receipt-bound for mutation phases: `true` only for BUILD_GATE and, per call, PROBE-DERIVE when the probe's visible Verification command requires a localhost service; `false` for
|
|
52
|
+
- `-c sandbox_workspace_write.network_access=<true|false>` — required and receipt-bound for mutation phases: `true` only for historical receipt-bound BUILD_GATE and, per call, PROBE-DERIVE when the probe's visible Verification command requires a localhost service; `false` for IMPLEMENT, historical worker PLAN/CLEANUP, and PROBE-DERIVE by default. This allows CI-equivalent loopback/network tests without widening to `danger-full-access` and prevents user configuration from silently changing other phases.
|
|
64
53
|
- `-c model_reasoning_effort=xhigh` — config override for reasoning depth. Required for deep critique; skills may choose `high` or `medium` when thoroughness doesn't warrant xhigh.
|
|
65
54
|
- **For every receipt-bound Codex call, add `--json -m <model_requested>` to the recipes above**, matching the model recorded at spawn; keep the skill’s existing model-selection rules. Other routes keep their omission rules unless an explicit role profile supplies a model: normal workers may inherit user config, while unconfigured isolated VERIFY uses its CLI default. Explicit profiles use role-config.py options and the separate judge-role-evidence.py contract; they do not relax mutation receipts. Omission proves neither model identity nor role fitness. Re-certify seats after model/version changes before re-pinning them.
|
|
66
55
|
- `CODEX_MONITORED_ISOLATED=1` — required for bounded read-only critique/probe/judge calls. The wrapper adds `--ignore-user-config --ignore-rules --ephemeral --disable codex_hooks --disable hooks` so user config, AGENTS.md, hooks, and project rules cannot add hidden context, tool calls, or transcript side effects. Do not set it for workspace-write implementation phases.
|
|
@@ -13,11 +13,11 @@ Engine requirements have two classes:
|
|
|
13
13
|
|
|
14
14
|
## Role resolution
|
|
15
15
|
|
|
16
|
-
The orchestrator is the CLI the user opened. PLAN remains orchestrator-fixed. Resolve the legacy executor as explicit `--engine` > `cwd/.devlyn/engines.json` `executor` > the invoking skill's supported default. BUILD_GATE
|
|
16
|
+
The orchestrator is the CLI the user opened. PLAN remains orchestrator-fixed. Resolve the legacy executor as explicit `--engine` > `cwd/.devlyn/engines.json` `executor` > the invoking skill's supported default. BUILD_GATE runs commands in the orchestrator without a separate model; risk-probe derivation keeps its existing route.
|
|
17
17
|
|
|
18
18
|
The role profiles and `--role-config` below apply only to `/devlyn:resolve`; `/devlyn:ideate` keeps its existing `--engine` / legacy executor selection and never inherits worker/judge profiles. `role-config.py` is resolve’s deterministic resolver. Optional project `roles.worker`, `roles.primary_judge`, and `roles.pair_judge` each require `engine` and accept exact `model`/`effort`; `/devlyn:resolve --role-config <path>` supplies the same `{"roles": {...}}` object for one run. Each entry replaces the entire lower entry. Worker/primary precedence is run role > `--engine` engine-only > project role > legacy executor. Pair precedence is run role > project role > existing `pair_judge_priority`/binary complement. An absent primary profile follows the legacy executor, not the worker profile. No parent/global devlyn config lookup.
|
|
19
19
|
|
|
20
|
-
Worker controls IMPLEMENT/
|
|
20
|
+
Worker controls IMPLEMENT, its included code/doc cleanup, and their repair rounds only; owner CLEANUP performs artifact/invariant checks without a worker dispatch; primary/pair control VERIFY only. OTHER must differ from the resolved primary engine. Explicit same-engine pair is invalid; a legacy priority list skips the primary. `--no-pair` does not dispatch or check availability of an unused pair. Configured pins remain explicit promises, while an unconfigured unavailable automatic OTHER records its solo reason.
|
|
21
21
|
|
|
22
22
|
Before the first phase, `/devlyn:resolve` freezes resolution with `state-phase-write.py --freeze-roles`; subsequent status/dispatch reads that state instead of mutable config. `state.engine` stays the legacy executor. The resolver rejects statically unsupported selected channel fields before PLAN, and validates adapter names and the existing ASCII `executor: yes|no` / `pair_judge: yes|no` eligibility markers; absent eligibility section means both. Malformed configuration fails with `BLOCKED:invalid-engine-config`.
|
|
23
23
|
|
|
@@ -33,7 +33,7 @@ When a run or phase requires engine `<name>` (claude, codex, omp, or any adapter
|
|
|
33
33
|
|
|
34
34
|
Never prompt the user mid-pipeline. Missing engines for explicit routes are BLOCKED states, not silent fallbacks. Missing OTHER engines for automatic escalations are reported solo-skips, not fallbacks — the auto route was never selected.
|
|
35
35
|
|
|
36
|
-
Per-skill defaults: `/devlyn:resolve` uses Claude for
|
|
36
|
+
Per-skill defaults: `/devlyn:resolve` uses Claude for IMPLEMENT when the orchestrator has Claude Code's native Agent primitive; Codex CLI and oh-my-pi orchestrators use their own fresh worker as the default route (Codex child process, omp native `task`) so implementation and independent verification retain structural isolation. PLAN uses the owner context; BUILD_GATE/CLEANUP use owner commands. VERIFY invokes the OTHER engine by default when available unless `--no-pair` was passed. `/devlyn:ideate` defaults to Claude; `--engine` selects the elicitation/normalization adapter, not an automatic cross-model challenge phase. Any future ideate read-only critique must follow `_shared/codex-config.md` isolation rules. Each SKILL.md flag block is source of truth for that skill's default.
|
|
37
37
|
|
|
38
38
|
## What a skill must report after a BLOCKED engine check
|
|
39
39
|
|
|
@@ -10,6 +10,7 @@ import os
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
import re
|
|
12
12
|
import shutil
|
|
13
|
+
import stat
|
|
13
14
|
import subprocess
|
|
14
15
|
import sys
|
|
15
16
|
import tempfile
|
|
@@ -95,9 +96,20 @@ def validate(config, *, run=False, shared=SHARED):
|
|
|
95
96
|
|
|
96
97
|
def read_config(path, *, run=False, optional=False, shared=SHARED):
|
|
97
98
|
path = Path(path)
|
|
98
|
-
if optional and not path.exists():
|
|
99
|
-
return {}, {"path": str(path.absolute()), "sha256": None}
|
|
100
99
|
try:
|
|
100
|
+
if optional:
|
|
101
|
+
try:
|
|
102
|
+
path.lstat()
|
|
103
|
+
except FileNotFoundError:
|
|
104
|
+
for parent in path.parents:
|
|
105
|
+
try:
|
|
106
|
+
mode = parent.stat().st_mode
|
|
107
|
+
except FileNotFoundError:
|
|
108
|
+
continue
|
|
109
|
+
if not stat.S_ISDIR(mode):
|
|
110
|
+
fail(f"cannot read role configuration {path}: {parent} is not a directory")
|
|
111
|
+
return {}, {"path": str(path.absolute()), "sha256": None}
|
|
112
|
+
raise
|
|
101
113
|
raw = path.read_bytes()
|
|
102
114
|
value = validate(loads(raw), run=run, shared=shared)
|
|
103
115
|
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
|
@@ -1754,10 +1754,9 @@ def authorized_surface_findings(
|
|
|
1754
1754
|
to catch. A persistent finding exhausts the existing BUILD_GATE
|
|
1755
1755
|
fix-loop budget and halts for user/orchestrator review instead.
|
|
1756
1756
|
|
|
1757
|
-
Not re-run at VERIFY MECHANICAL time
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
CLEANUP's own sanctioned changes.
|
|
1757
|
+
Not re-run at VERIFY MECHANICAL time: historical worker CLEANUP can
|
|
1758
|
+
license paths outside PLAN. Owner CLEANUP instead enforces an unchanged
|
|
1759
|
+
source checkpoint and the final untracked baseline before VERIFY.
|
|
1761
1760
|
"""
|
|
1762
1761
|
surface, surface_error = load_authorized_surface(devlyn_dir)
|
|
1763
1762
|
if surface_error is not None:
|