devlyn-cli 3.0.1 → 3.1.1
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 +1 -1
- package/CLAUDE.md +3 -3
- package/README.md +20 -3
- package/bin/devlyn.js +69 -23
- package/config/skills/_shared/adapters/claude.md +3 -2
- package/config/skills/_shared/adapters/codex.md +1 -1
- package/config/skills/_shared/archive_run.py +5 -3
- package/config/skills/_shared/codex-config.md +13 -8
- package/config/skills/_shared/codex-monitored.sh +12 -19
- package/config/skills/_shared/collect-codex-findings.py +1 -0
- package/config/skills/_shared/engine-preflight.md +2 -0
- package/config/skills/_shared/finish-gate.py +4 -1
- package/config/skills/_shared/invocation-receipt.py +194 -5
- package/config/skills/_shared/judge-role-evidence.py +33 -6
- package/config/skills/_shared/phase-prompt-render.py +2 -0
- package/config/skills/_shared/platform-support.py +370 -0
- package/config/skills/_shared/process-evidence.py +4 -2
- package/config/skills/_shared/resolve-bootstrap.py +102 -59
- package/config/skills/_shared/resolve-stop-hook.py +3 -1
- package/config/skills/_shared/role-config.py +7 -5
- package/config/skills/_shared/run-bounded.py +35 -21
- package/config/skills/_shared/spec-verify-check.py +274 -157
- package/config/skills/_shared/state-phase-write.py +26 -9
- package/config/skills/_shared/task-complete.py +1173 -0
- package/config/skills/_shared/verify-merge-findings.py +9 -8
- package/config/skills/devlyn:queue/SKILL.md +3 -3
- package/config/skills/devlyn:resolve/SKILL.md +14 -7
- package/config/skills/devlyn:resolve/references/outer-loop.md +10 -2
- package/config/skills/devlyn:resolve/references/phases/verify.md +5 -3
- package/config/skills/devlyn:resolve/references/task-completion.md +117 -0
- package/package.json +1 -1
- package/scripts/lint-skills.sh +10 -6
package/AGENTS.md
CHANGED
|
@@ -38,7 +38,7 @@ Each skill's `SKILL.md` is the source of truth for flags and workflow. Do not du
|
|
|
38
38
|
|
|
39
39
|
Engine roles: orchestrator = whichever CLI the user opened (this contract is symmetric with CLAUDE.md. If you are Codex orchestrating `/devlyn:resolve`, the phase machinery is mandatory regardless of task size; if you will not run it, say so explicitly and stop — do not silently degrade to ad-hoc execution); legacy executor (with optional separate worker/primary/pair profiles) uses the canonical skill's orchestrator-supported default, overridable per run with `--engine <name>` or durably via machine-local `.devlyn/engines.json` `{"executor": "<name>", "pair_judge_priority": ["<name>", ...]}`; PLAN is orchestrator-fixed and never inherits `--engine` or the executor pin; pair judge = first available OTHER engine, default for VERIFY whenever available; risk probes remain conditional and --no-pair opts out. Pins fail closed (`BLOCKED:<engine>-unavailable` / `BLOCKED:invalid-engine-config`); new engines plug in by shipping `_shared/adapters/<name>.md`. `/devlyn:engines` (no args) shows the role table + detected engines; `executor <name>` / `pair <name>,...` / `clear` manage the pins. The executor pin binds the orchestrator in plain conversation too, not only inside a skill run: when you would implement directly and executor is pinned to a non-default engine, route that work through the pin — via `/devlyn:resolve` (reads it at PHASE 0) or by delegating to that engine; no pin / no file → use the canonical skill's orchestrator-supported default. The pair-judge pin stays pipeline-scoped. `/devlyn:engines role` configures explicit engine/model/effort for supported worker and judge routes; `/devlyn:resolve --role-config <path>` overrides roles for one run. Canonical skills define precedence, evidence and unsupported-route errors; absent profiles preserve existing defaults.
|
|
40
40
|
|
|
41
|
-
Conversational entry: Before writes, inspect requested files and relevant callers/tests. Default to direct execution only for clear, local, reversible, low-risk work with a decisive existing or small task-specific acceptance check. Preserve requested behavior, scope, explicit constraints and the executor pin; make scoped edits, run required checks, review the final diff, and report changes with evidence. One concise route/check explanation suffices; ordinary direct work needs no spec, pipeline state, phase workers or extra approval.
|
|
41
|
+
Conversational entry: Before writes, inspect requested files and relevant callers/tests. Default to direct execution only for clear, local, reversible, low-risk work with a decisive existing or small task-specific acceptance check. Preserve requested behavior, scope, explicit constraints and the executor pin; make scoped edits, run required checks, review the final diff, and report changes with evidence. One concise route/check explanation suffices; ordinary direct work needs no spec, pipeline state, phase workers or extra approval. For direct/full/queue work, the outer owner reads `references/task-completion.md` relative to the installed `devlyn:resolve` skill's directory: prospective task-branch ownership (linked worktree optional), scoped commit acceptance, then PR/merge and recoverable owned-resource cleanup after required archive/queue commits. Local-only/no-push wins; delivery pending/failure stays separate from product verification.
|
|
42
42
|
|
|
43
43
|
Use full `/devlyn:resolve` for material ambiguity, subsystem/design work, security/auth/payment/persistence/concurrency/public-API-contract changes, or inconclusive inspection/checks. Counts of lines, files or words alone do not establish low risk. Explicit resolve (even small), formal spec workflows and queue drains retain all canonical phases, independent verification, pins and failure handling. If new risk appears mid-edit, preserve the delta in the full run's scope/evidence; do not hide it in a new baseline or reconfirm existing authorization.
|
|
44
44
|
|
package/CLAUDE.md
CHANGED
|
@@ -51,7 +51,7 @@ Each skill's `SKILL.md` is the source of truth for its flags and workflow — do
|
|
|
51
51
|
|
|
52
52
|
### Conversational entry + full-route handoff
|
|
53
53
|
|
|
54
|
-
Before writes, inspect requested files and relevant callers/tests. Default to direct execution only for clear, local, reversible, low-risk work with a decisive existing or small task-specific acceptance check. Preserve requested behavior, scope, explicit constraints and the executor pin; make scoped edits, run required checks, review the final diff, and report changes with evidence. One concise route/check explanation suffices; ordinary direct work needs no spec, pipeline state, phase workers or extra approval.
|
|
54
|
+
Before writes, inspect requested files and relevant callers/tests. Default to direct execution only for clear, local, reversible, low-risk work with a decisive existing or small task-specific acceptance check. Preserve requested behavior, scope, explicit constraints and the executor pin; make scoped edits, run required checks, review the final diff, and report changes with evidence. One concise route/check explanation suffices; ordinary direct work needs no spec, pipeline state, phase workers or extra approval. For direct/full/queue work, the outer owner reads `references/task-completion.md` relative to the installed `devlyn:resolve` skill's directory: prospective task-branch ownership (linked worktree optional), scoped commit acceptance, then PR/merge and recoverable owned-resource cleanup after required archive/queue commits. Local-only/no-push wins; delivery pending/failure stays separate from product verification.
|
|
55
55
|
|
|
56
56
|
Use full `/devlyn:resolve` for material ambiguity, subsystem/design work, security/auth/payment/persistence/concurrency/public-API-contract changes, or inconclusive inspection/checks. Counts of lines, files or words alone do not establish low risk. Explicit resolve (even small), formal spec workflows and queue drains retain all canonical phases, independent verification, pins and failure handling. If new risk appears mid-edit, preserve the delta in the full run's scope/evidence; do not hide it in a new baseline or reconfirm existing authorization.
|
|
57
57
|
|
|
@@ -60,9 +60,9 @@ The user does not invoke skills manually; the orchestrating model does. Large ta
|
|
|
60
60
|
1. Write the agreed contract to `docs/specs/<id>/spec.md` (+ `spec.expected.json` when mechanical verifications exist). Always a spec file for large work — a written spec is the user's reviewed contract; free-form large now assumes-and-logs instead of halting (zero-scope-signal goals still halt to ideate).
|
|
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
|
-
4. **Per-task outer loop**: read the terminal verdict.
|
|
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.
|
|
64
64
|
|
|
65
|
-
**Intent queue (unattended drain)**: the user stacks intents in `docs/specs/queue.md` (ordered checklist; `/devlyn:queue` is the front-end — no args shows status, `add <intent>` appends, `drain` starts the serial drain). Drain strictly serially — per item: spec it, commit the scoped owner baseline, run the outer loop, mark `[x]` done or `[F]` blocked with reason, commit that terminal transition, then
|
|
65
|
+
**Intent queue (unattended drain)**: the user stacks intents in `docs/specs/queue.md` (ordered checklist; `/devlyn:queue` is the front-end — no args shows status, `add <intent>` appends, `drain` starts the serial drain). Drain strictly serially — per item: spec it, commit the scoped owner baseline, run the outer loop, mark `[x]` done or `[F]` blocked with reason, commit that terminal transition, then complete eligible delivery once. Retain PR/pending workspaces; unavailable separate safe placement stops the drain with remaining items pending. A product-blocked item otherwise never halts the queue. The queue entry is the user's go-ahead, so assume-and-log replaces the interactive checkpoint — but unattended assumptions may only take scope-narrowing, reversible, non-user-visible defaults. Material ambiguity (user-visible behavior, data/state semantics, new files/scripts/flags, implementation surface) → mark `[F] needs-review`, commit it, and move on. End the drain with a per-item product verdict, separate delivery status/URL/resume and assumptions report. The canonical commit order is `config/skills/devlyn:resolve/references/outer-loop.md`.
|
|
66
66
|
|
|
67
67
|
## Subtractive-first editing — perfection = nothing left to remove
|
|
68
68
|
<!-- runtime-principles:section=subtractive-first:begin -->
|
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ Skip ideate entirely if you have a spec or just want to describe the work — `/
|
|
|
62
62
|
|
|
63
63
|
### Step 2 — Resolve with `/devlyn:resolve`
|
|
64
64
|
|
|
65
|
-
Before writes, inspect requested files and relevant callers/tests. Clear, local, reversible, low-risk conversational edits with a decisive acceptance check run directly, honoring scope, constraints and executor pins; finish required checks and diff review, then
|
|
65
|
+
Before writes, inspect requested files and relevant callers/tests. Clear, local, reversible, low-risk conversational edits with a decisive acceptance check run directly, honoring scope, constraints and executor pins; finish required checks and diff review, then accept the scoped commit for owner completion below. Material ambiguity, subsystem/design work, security/auth/payment/persistence/concurrency/public-API-contract changes or no decisive check use full `/devlyn:resolve`. Explicit resolve (including small tasks), formal specs and queue drains keep the full workflow below. Pass a spec, a free-form goal, or a diff to verify.
|
|
66
66
|
|
|
67
67
|
```
|
|
68
68
|
/devlyn:resolve "fix the login bug" # free-form
|
|
@@ -97,9 +97,21 @@ golden fixture, risk-probe, or pair-evidence candidate must also include
|
|
|
97
97
|
difference from rejected or solo-saturated controls such as `S2`-`S6`; without
|
|
98
98
|
that, `/devlyn:resolve` stops with `BLOCKED:solo-ceiling-avoidance-required`.
|
|
99
99
|
|
|
100
|
+
Accepted tasks default to owner completion: scoped commit → push → PR → normal
|
|
101
|
+
protected merge → recoverable cleanup of prospectively owned resources. Set
|
|
102
|
+
`git config --local devlyn.completionMode pr` to stop at a PR, or `auto` to restore
|
|
103
|
+
the default; `task-complete.py complete --mode auto|pr` overrides one task.
|
|
104
|
+
Local-only/no-push instructions take precedence. Linked worktrees are optional;
|
|
105
|
+
existing branches cannot be adopted. Full runs require successful archive before
|
|
106
|
+
delivery; direct tasks use their actual checks and root acceptance. Verify-only
|
|
107
|
+
never publishes. Pending checks or unsupported merge policy retain resources and
|
|
108
|
+
report a receipt-based resume command separately from product verification.
|
|
109
|
+
See [task completion](config/skills/devlyn:resolve/references/task-completion.md)
|
|
110
|
+
for allocation, acceptance, writer cessation and recovery.
|
|
111
|
+
|
|
100
112
|
### Queue multiple intents for unattended drain — `/devlyn:queue`
|
|
101
113
|
|
|
102
|
-
Stack tasks to run back-to-back without supervision. `/devlyn:queue add "<intent>"` appends to `docs/specs/queue.md` (an ordered checklist); `/devlyn:queue drain` runs each item serially — spec it, run the resolve outer loop,
|
|
114
|
+
Stack tasks to run back-to-back without supervision. `/devlyn:queue add "<intent>"` appends to `docs/specs/queue.md` (an ordered checklist); `/devlyn:queue drain` runs each item serially — spec it, run the resolve outer loop, commit `[x]` done or `[F]` blocked with a reason, then complete eligible delivery once. Product verdict and delivery status are separate. A blocked product otherwise never halts the queue; pending delivery retains its branch, and the next item requires separate safe placement. Unattended runs only take scope-narrowing, reversible defaults — anything user-visible or ambiguous is marked `[F] needs-review` for you to adjudicate afterward. `/devlyn:queue` with no args shows status.
|
|
103
115
|
|
|
104
116
|
### Engine roles — auto-detected, pinnable with `/devlyn:engines`
|
|
105
117
|
|
|
@@ -293,9 +305,14 @@ Selected during install. Run `npx devlyn-cli` again to add more.
|
|
|
293
305
|
|
|
294
306
|
## Requirements
|
|
295
307
|
|
|
296
|
-
- **Node.js 18+**
|
|
308
|
+
- **Node.js 18+** and npm
|
|
309
|
+
- **Python 3.11+** available as `python3`, and Git for the resolve harness
|
|
297
310
|
- **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** installed and configured
|
|
298
311
|
|
|
312
|
+
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 Codex with `npx devlyn-cli agents codex`. npm extraction and installed directories use the legal U+F03A colon alias (for example `devlyn\uf03aresolve`); logical skill names remain `devlyn:resolve`. Do not rename them by hand. Harness text is UTF-8 without requiring `PYTHONUTF8`.
|
|
313
|
+
|
|
314
|
+
Windows completion preserves the workspace, owned refs and recovery receipt when writer cessation cannot be proved, even after merge; resume guidance and delivery status remain separate from product verification. The portability workflow tests a POSIX-packed npm artifact on native Windows without checking out colon paths. A POSIX pass alone does not establish Windows support: acceptance requires the passing Windows job for the exact source SHA/artifact hashes.
|
|
315
|
+
|
|
299
316
|
## Contributing
|
|
300
317
|
|
|
301
318
|
- **Add a skill** — directory in `config/skills/` with `SKILL.md`
|
package/bin/devlyn.js
CHANGED
|
@@ -18,6 +18,33 @@ const DEVLYN_CORE_SKILLS = ['devlyn:resolve', 'devlyn:ideate', 'devlyn:design-ui
|
|
|
18
18
|
const DEVLYN_SKILL_DIR_STAMP = '__DEVLYN_SKILL_DIR__';
|
|
19
19
|
const DEVLYN_INSTALL_MARKER = '.devlyn-install.json';
|
|
20
20
|
|
|
21
|
+
function logicalSkillName(name) {
|
|
22
|
+
return name.replace(/\uF03A/g, ':');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function physicalSkillName(name) {
|
|
26
|
+
return process.platform === 'win32' ? name.replace(/:/g, '\uF03A') : name;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function skillPath(root, name) {
|
|
30
|
+
name = logicalSkillName(name);
|
|
31
|
+
const aliases = [...new Set([name, name.replace(/:/g, '\uF03A')])];
|
|
32
|
+
const entries = fs.existsSync(root) ? fs.readdirSync(root) : [];
|
|
33
|
+
const found = aliases.filter((alias) => entries.includes(alias));
|
|
34
|
+
if (found.length > 1) throw new Error(`Ambiguous skill aliases in ${root}: ${found.join(', ')}`);
|
|
35
|
+
return path.join(root, found[0] || physicalSkillName(name));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function preflightSkillDirs(source, target, names) {
|
|
39
|
+
for (const name of names) {
|
|
40
|
+
skillPath(source, name);
|
|
41
|
+
skillPath(target, name);
|
|
42
|
+
}
|
|
43
|
+
for (const relative of DEPRECATED_DIRS) {
|
|
44
|
+
skillPath(target, path.basename(relative));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
21
48
|
// Cross-agent shared skills directory read by BOTH oh-my-pi and Pi. Verified
|
|
22
49
|
// from the omp binary's skill-provider strings ("skills from .agents/skills —
|
|
23
50
|
// project walk-up + user home") and Pi's docs ("~/.agents/skills/"). Installing
|
|
@@ -313,6 +340,8 @@ function listContents() {
|
|
|
313
340
|
|
|
314
341
|
function cleanupDeprecated(targetDir) {
|
|
315
342
|
let removed = 0;
|
|
343
|
+
const deprecated = DEPRECATED_DIRS.map((relPath) =>
|
|
344
|
+
[relPath, skillPath(path.join(targetDir, 'skills'), path.basename(relPath))]);
|
|
316
345
|
for (const relPath of DEPRECATED_FILES) {
|
|
317
346
|
const fullPath = path.join(targetDir, relPath);
|
|
318
347
|
if (fs.existsSync(fullPath)) {
|
|
@@ -321,8 +350,7 @@ function cleanupDeprecated(targetDir) {
|
|
|
321
350
|
removed++;
|
|
322
351
|
}
|
|
323
352
|
}
|
|
324
|
-
for (const relPath of
|
|
325
|
-
const fullPath = path.join(targetDir, relPath);
|
|
353
|
+
for (const [relPath, fullPath] of deprecated) {
|
|
326
354
|
if (fs.existsSync(fullPath)) {
|
|
327
355
|
fs.rmSync(fullPath, { recursive: true });
|
|
328
356
|
log(` ✕ ${relPath}/ (removed)`, 'dim');
|
|
@@ -337,13 +365,14 @@ function copyRecursive(src, dest, baseDir) {
|
|
|
337
365
|
|
|
338
366
|
if (stats.isDirectory()) {
|
|
339
367
|
// Never install dev workspaces, even when running from source repo.
|
|
340
|
-
if (UNSHIPPED_SKILL_DIRS.has(path.basename(src))) return;
|
|
368
|
+
if (UNSHIPPED_SKILL_DIRS.has(logicalSkillName(path.basename(src)))) return;
|
|
341
369
|
if (!fs.existsSync(dest)) {
|
|
342
370
|
fs.mkdirSync(dest, { recursive: true });
|
|
343
371
|
}
|
|
344
372
|
|
|
345
373
|
for (const item of fs.readdirSync(src)) {
|
|
346
|
-
|
|
374
|
+
const name = src === path.join(CONFIG_SOURCE, 'skills') ? physicalSkillName(logicalSkillName(item)) : item;
|
|
375
|
+
copyRecursive(path.join(src, item), path.join(dest, name), baseDir);
|
|
347
376
|
}
|
|
348
377
|
} else {
|
|
349
378
|
const destDir = path.dirname(dest);
|
|
@@ -360,9 +389,19 @@ function clearInstallMarker(skillsDir) {
|
|
|
360
389
|
}
|
|
361
390
|
|
|
362
391
|
function assertCompleteSkillInstall(sourceSkillsDir, skillsDir, skillNames) {
|
|
363
|
-
|
|
364
|
-
!fs.existsSync(
|
|
365
|
-
|
|
392
|
+
function complete(src, dest) {
|
|
393
|
+
if (!fs.existsSync(src) || !fs.existsSync(dest)) return false;
|
|
394
|
+
if (fs.statSync(src).isDirectory()) {
|
|
395
|
+
return fs.statSync(dest).isDirectory() && fs.readdirSync(src).every((name) =>
|
|
396
|
+
complete(path.join(src, name), path.join(dest, name)));
|
|
397
|
+
}
|
|
398
|
+
return fs.statSync(dest).isFile();
|
|
399
|
+
}
|
|
400
|
+
const missing = skillNames.filter((name) => {
|
|
401
|
+
const src = skillPath(sourceSkillsDir, name);
|
|
402
|
+
const dest = skillPath(skillsDir, name);
|
|
403
|
+
return !complete(src, dest) || (name !== '_shared' && !fs.existsSync(path.join(src, 'SKILL.md')));
|
|
404
|
+
});
|
|
366
405
|
if (missing.length > 0) {
|
|
367
406
|
throw new Error(`Incomplete devlyn skill install; missing: ${missing.join(', ')}`);
|
|
368
407
|
}
|
|
@@ -440,11 +479,12 @@ const UNSHIPPED_SKILL_DIRS = new Set([
|
|
|
440
479
|
// no counterpart in source. Dev workspaces are skipped entirely.
|
|
441
480
|
function cleanManagedSkillDirs(sourceSkillsDir, targetSkillsDir) {
|
|
442
481
|
if (!fs.existsSync(sourceSkillsDir) || !fs.existsSync(targetSkillsDir)) return 0;
|
|
482
|
+
preflightSkillDirs(sourceSkillsDir, targetSkillsDir, fs.readdirSync(sourceSkillsDir));
|
|
443
483
|
let cleaned = 0;
|
|
444
484
|
for (const entry of fs.readdirSync(sourceSkillsDir, { withFileTypes: true })) {
|
|
445
485
|
if (!entry.isDirectory()) continue;
|
|
446
|
-
if (UNSHIPPED_SKILL_DIRS.has(entry.name)) continue;
|
|
447
|
-
const targetPath =
|
|
486
|
+
if (UNSHIPPED_SKILL_DIRS.has(logicalSkillName(entry.name))) continue;
|
|
487
|
+
const targetPath = skillPath(targetSkillsDir, entry.name);
|
|
448
488
|
if (fs.existsSync(targetPath)) {
|
|
449
489
|
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
450
490
|
cleaned++;
|
|
@@ -547,9 +587,11 @@ function multiSelect(items, preselectedIndices = []) {
|
|
|
547
587
|
}
|
|
548
588
|
|
|
549
589
|
function installLocalSkill(skillName) {
|
|
550
|
-
const src =
|
|
590
|
+
const src = skillPath(OPTIONAL_SKILLS_SOURCE, skillName);
|
|
551
591
|
const targetDir = getTargetDir();
|
|
552
|
-
const
|
|
592
|
+
const targets = new Set([path.join(targetDir, 'skills'), ...Object.values(CLI_TARGETS)
|
|
593
|
+
.map((cli) => cli.skillsDir).filter((dir) => dir && fs.existsSync(dir))]);
|
|
594
|
+
for (const target of targets) skillPath(target, skillName);
|
|
553
595
|
|
|
554
596
|
if (!fs.existsSync(src)) {
|
|
555
597
|
log(` ⚠️ Skill "${skillName}" not found`, 'yellow');
|
|
@@ -557,7 +599,6 @@ function installLocalSkill(skillName) {
|
|
|
557
599
|
}
|
|
558
600
|
|
|
559
601
|
log(`\n🛠️ Installing ${skillName}...`, 'cyan');
|
|
560
|
-
copyRecursive(src, dest, targetDir);
|
|
561
602
|
|
|
562
603
|
// Mirror to every CLI skill-loader directory that already exists so optional
|
|
563
604
|
// skills are picked up by Codex/omp/Pi (and any future CLI with a skillsDir)
|
|
@@ -565,13 +606,12 @@ function installLocalSkill(skillName) {
|
|
|
565
606
|
// create an agent install just because someone opted into a Claude-side skill.
|
|
566
607
|
// De-dup by directory: omp and Pi share ~/.agents/skills, so the mirror runs
|
|
567
608
|
// once per unique destination.
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
copyRecursive(src, cliDest, cli.skillsDir);
|
|
609
|
+
for (const target of targets) {
|
|
610
|
+
fs.rmSync(skillPath(target, skillName), { recursive: true, force: true });
|
|
611
|
+
const dest = path.join(target, physicalSkillName(skillName));
|
|
612
|
+
copyRecursive(src, dest, target);
|
|
613
|
+
stampInstalledSkillDir(dest, dest);
|
|
614
|
+
assertCompleteSkillInstall(OPTIONAL_SKILLS_SOURCE, target, [skillName]);
|
|
575
615
|
}
|
|
576
616
|
return true;
|
|
577
617
|
}
|
|
@@ -632,6 +672,7 @@ function installSkillsForCLI(cliKey) {
|
|
|
632
672
|
fs.mkdirSync(cli.skillsDir, { recursive: true });
|
|
633
673
|
}
|
|
634
674
|
clearInstallMarker(cli.skillsDir);
|
|
675
|
+
preflightSkillDirs(sourceSkillsDir, cli.skillsDir, cli.skillsToInstall);
|
|
635
676
|
|
|
636
677
|
const removed = cleanupDeprecated(path.dirname(cli.skillsDir));
|
|
637
678
|
if (removed > 0) {
|
|
@@ -640,13 +681,14 @@ function installSkillsForCLI(cliKey) {
|
|
|
640
681
|
|
|
641
682
|
let copied = 0;
|
|
642
683
|
for (const skillName of cli.skillsToInstall) {
|
|
643
|
-
const src =
|
|
644
|
-
const
|
|
684
|
+
const src = skillPath(sourceSkillsDir, skillName);
|
|
685
|
+
const previous = skillPath(cli.skillsDir, skillName);
|
|
686
|
+
const dest = path.join(cli.skillsDir, physicalSkillName(skillName));
|
|
645
687
|
if (!fs.existsSync(src)) continue;
|
|
646
688
|
// Full replace per cleanManagedSkillDirs semantics: stale files in the
|
|
647
689
|
// installed mirror would otherwise persist forever.
|
|
648
|
-
if (fs.existsSync(
|
|
649
|
-
fs.rmSync(
|
|
690
|
+
if (fs.existsSync(previous)) {
|
|
691
|
+
fs.rmSync(previous, { recursive: true, force: true });
|
|
650
692
|
}
|
|
651
693
|
copyRecursive(src, dest, cli.skillsDir);
|
|
652
694
|
stampInstalledSkillDir(dest, dest);
|
|
@@ -777,6 +819,10 @@ function installClaudeCore() {
|
|
|
777
819
|
}
|
|
778
820
|
copyRecursive(CONFIG_SOURCE, targetDir, targetDir);
|
|
779
821
|
assertCompleteSkillInstall(path.join(CONFIG_SOURCE, 'skills'), skillsDir, DEVLYN_CORE_SKILLS);
|
|
822
|
+
for (const name of DEVLYN_CORE_SKILLS) {
|
|
823
|
+
const dest = skillPath(skillsDir, name);
|
|
824
|
+
stampInstalledSkillDir(dest, dest);
|
|
825
|
+
}
|
|
780
826
|
writeInstallMarker(skillsDir);
|
|
781
827
|
|
|
782
828
|
// Remove deprecated files from previous versions
|
|
@@ -22,7 +22,7 @@ two targeted probes; the invoking phase sets `--effort`, pair-JUDGE uses
|
|
|
22
22
|
`medium`):
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
python3 "$DEVLYN_SHARED_DIR/run-bounded.py" 600 --
|
|
25
|
+
python3 "$DEVLYN_SHARED_DIR/run-bounded.py" 600 --stdin-file "<judge-prompt-file>" --record-transport -- claude -p \
|
|
26
26
|
--permission-mode dontAsk \
|
|
27
27
|
--tools "Read,Grep,Glob" --allowedTools "Read,Grep,Glob" \
|
|
28
28
|
--setting-sources project --strict-mcp-config --mcp-config '{"mcpServers":{}}' \
|
|
@@ -30,6 +30,7 @@ python3 "$DEVLYN_SHARED_DIR/run-bounded.py" 600 -- claude -p "<judge prompt>" \
|
|
|
30
30
|
> .devlyn/claude-judge.stdout 2> .devlyn/claude-judge.stderr
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
- Write the complete prompt as exact UTF-8 file bytes; no positional prompt or shell substitution. With `--record-transport`, the runner snapshots stdin and exclusively creates `<judge-prompt-file>.transport.json` to bind actual native argv and delivered bytes; an existing carrier rejects dispatch. Preserve it for explicit judge-role authentication. Ordinary `--stdin-file` reads require no output or writable input directory; without it, stdin remains DEVNULL.
|
|
33
34
|
- Omit `--model` — the CLI's configured default is used (zero-touch, same
|
|
34
35
|
rule as codex-config.md's "omit `-m`").
|
|
35
36
|
- `dontAsk` denies anything not allowlisted (official guide: "denies
|
|
@@ -46,7 +47,7 @@ python3 "$DEVLYN_SHARED_DIR/run-bounded.py" 600 -- claude -p "<judge prompt>" \
|
|
|
46
47
|
`.devlyn/verify.pair.findings.jsonl`. Raw stdout stays diagnostic at
|
|
47
48
|
`.devlyn/claude-judge.stdout`; `verify-merge-findings.py` blocks the run
|
|
48
49
|
if stdout contains findings the canonical file lacks.
|
|
49
|
-
- Exit 124 is a wall-budget abort (
|
|
50
|
+
- Exit 124 is a wall-budget abort (terminates the native child process tree; POSIX process group) → the orchestrator
|
|
50
51
|
writes `.devlyn/verify.pair.timeout.json`; budget abort ≠ availability failure
|
|
51
52
|
and the fail-closed availability rules above are unchanged.
|
|
52
53
|
- Do not pipe stdout (`| tail`, `| grep`); capture to file. Non-zero exit other
|
|
@@ -14,7 +14,7 @@ Your default is efficient, direct, task-oriented. The canonical body specifies t
|
|
|
14
14
|
|
|
15
15
|
Resolve the request in the fewest useful tool loops without sacrificing correctness. For retrieval tasks: start with one broad search using short discriminative keywords; make another retrieval call only when the top results don't answer the core question or a required fact / parameter / source is missing. For tool-heavy tasks, start with a brief preamble: a one-line acknowledgment of the request and the first step you'll take.
|
|
16
16
|
|
|
17
|
-
For VERIFY JUDGE retrieval, batch related reads. After sealed parity proves a
|
|
17
|
+
For a constrained-read VERIFY packet, judge the supplied material without tools; missing or unbound inputs require a verdict-binding BLOCKED finding. Keep the read-only sandbox and bounded review. Otherwise, for VERIFY JUDGE retrieval, batch related reads. After sealed parity proves a
|
|
18
18
|
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.
|
|
@@ -535,7 +535,7 @@ def completion_self_test() -> None:
|
|
|
535
535
|
for i, final in enumerate(invalid):
|
|
536
536
|
path = runs / f"indeterminate-{i:02d}" / "pipeline.state.json"
|
|
537
537
|
path.parent.mkdir()
|
|
538
|
-
path.write_text(json.dumps({"run_id": path.parent.name, "phases": {"final_report": final}}))
|
|
538
|
+
path.write_text(json.dumps({"run_id": path.parent.name, "phases": {"final_report": final}}), encoding="utf-8")
|
|
539
539
|
preserved.append(path)
|
|
540
540
|
for i, raw in enumerate((b"{", b"\xff", b"null", b'{"phases":{}}', b'{"run_id":[]}')):
|
|
541
541
|
path = runs / f"malformed-{i}" / "pipeline.state.json"
|
|
@@ -545,14 +545,14 @@ def completion_self_test() -> None:
|
|
|
545
545
|
for i, pattern in enumerate(BOOTSTRAP_TEMP_PATTERNS):
|
|
546
546
|
path = runs / f"residue-{i}" / "pipeline.state.json"
|
|
547
547
|
path.parent.mkdir()
|
|
548
|
-
path.write_text(json.dumps({"run_id": path.parent.name, "phases": {"final_report": valid[0]}}))
|
|
548
|
+
path.write_text(json.dumps({"run_id": path.parent.name, "phases": {"final_report": valid[0]}}), encoding="utf-8")
|
|
549
549
|
residue = path.with_name(pattern.replace("*", "interrupted"))
|
|
550
550
|
residue.write_bytes(b"interrupted initialization")
|
|
551
551
|
preserved.extend((path, residue))
|
|
552
552
|
for i in range(12):
|
|
553
553
|
path = runs / f"complete-{i:02d}" / "pipeline.state.json"
|
|
554
554
|
path.parent.mkdir()
|
|
555
|
-
path.write_text(json.dumps({"run_id": path.parent.name, "phases": {"final_report": valid[i % 8]}}))
|
|
555
|
+
path.write_text(json.dumps({"run_id": path.parent.name, "phases": {"final_report": valid[i % 8]}}), encoding="utf-8")
|
|
556
556
|
before = {p: p.read_bytes() for p in preserved}
|
|
557
557
|
assert prune(runs) == 2
|
|
558
558
|
assert {p: p.read_bytes() for p in preserved} == before
|
|
@@ -901,6 +901,7 @@ def self_test() -> int:
|
|
|
901
901
|
[sys.executable, str(pathlib.Path(__file__).resolve()), "--devlyn-dir", str(bad)],
|
|
902
902
|
capture_output=True,
|
|
903
903
|
text=True,
|
|
904
|
+
encoding="utf-8",
|
|
904
905
|
)
|
|
905
906
|
assert blocked_cli.returncode == 1
|
|
906
907
|
assert "error: archive blocked:" in blocked_cli.stderr
|
|
@@ -948,4 +949,5 @@ def main() -> int:
|
|
|
948
949
|
|
|
949
950
|
|
|
950
951
|
if __name__ == "__main__":
|
|
952
|
+
runpy.run_path(str(pathlib.Path(__file__).with_name("platform-support.py")))["configure_utf8"]()
|
|
951
953
|
raise SystemExit(main())
|
|
@@ -4,7 +4,7 @@ Single source of truth for how every skill calls Codex. **MCP is not used.** Ski
|
|
|
4
4
|
|
|
5
5
|
## Canonical invocations
|
|
6
6
|
|
|
7
|
-
All long-running Codex calls go through `codex-monitored.sh
|
|
7
|
+
All long-running Codex calls go through `codex-monitored.sh`. It streams full stdout and emits a `[codex-monitored] heartbeat` every 30s on stderr. Write multiline prompts as exact UTF-8 bytes to a task-local file; set `DEVLYN_CODEX_PROMPT_FILE` and pass the sole prompt argument `-`. The wrapper snapshots those bytes before dispatch and seals actual argv, prompt digest and completion in `<prompt-file>.transport.json`. Missing/unreadable files, competing prompt arguments and receipt/prompt mismatches fail before launch. Without file transport, stdin remains DEVNULL (avoiding the open-stdin plus argument-prompt hang).
|
|
8
8
|
|
|
9
9
|
Before the first Codex call, resolve the wrapper from the invoked skill directory:
|
|
10
10
|
|
|
@@ -25,22 +25,22 @@ fi
|
|
|
25
25
|
**Read-only critique / adversarial review / debate** (`/devlyn:resolve` VERIFY pair-mode, plus any future ideate read-only critique). Security review stays native to Claude Code BUILD_GATE. Codex returns findings on stdout; the orchestrator writes files.
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
CODEX_MONITORED_ISOLATED=1 bash "$CODEX_MONITORED_PATH" \
|
|
28
|
+
DEVLYN_CODEX_PROMPT_FILE="<prompt-file>" CODEX_MONITORED_ISOLATED=1 CODEX_MONITORED_TIMEOUT_SEC=600 bash "$CODEX_MONITORED_PATH" \
|
|
29
29
|
-C <project-root> \
|
|
30
30
|
-s read-only \
|
|
31
31
|
-c model_reasoning_effort=xhigh \
|
|
32
|
-
|
|
32
|
+
-
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
**Workspace-write implementation** (`/devlyn:resolve` IMPLEMENT phase when `--engine codex` or `--engine auto` routes to Codex, plus codex-routed `/devlyn:ideate` phases):
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
bash "$CODEX_MONITORED_PATH" \
|
|
38
|
+
DEVLYN_CODEX_PROMPT_FILE="<prompt-file>" bash "$CODEX_MONITORED_PATH" \
|
|
39
39
|
-C <project-root> \
|
|
40
40
|
-s workspace-write \
|
|
41
41
|
-c sandbox_workspace_write.network_access=false \
|
|
42
42
|
-c model_reasoning_effort=xhigh \
|
|
43
|
-
|
|
43
|
+
-
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
**CI-equivalent BUILD_GATE** keeps the same write sandbox and enables general
|
|
@@ -48,15 +48,16 @@ outbound network access inside that sandbox, including the loopback servers and
|
|
|
48
48
|
network-backed test gates that CI may exercise:
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
bash "$CODEX_MONITORED_PATH" \
|
|
51
|
+
DEVLYN_CODEX_PROMPT_FILE="<prompt-file>" bash "$CODEX_MONITORED_PATH" \
|
|
52
52
|
-C <project-root> \
|
|
53
53
|
-s workspace-write \
|
|
54
54
|
-c sandbox_workspace_write.network_access=true \
|
|
55
55
|
-c model_reasoning_effort=xhigh \
|
|
56
|
-
|
|
56
|
+
-
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
Notes:
|
|
60
|
+
- `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.
|
|
60
61
|
- `-C` — project root so Codex's working directory matches.
|
|
61
62
|
- `-s read-only` / `-s workspace-write` — sandbox policy. Use workspace-write for implementation/probe phases that write tracked files or `.devlyn` artifacts.
|
|
62
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 PLAN, IMPLEMENT, 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.
|
|
@@ -66,6 +67,10 @@ Notes:
|
|
|
66
67
|
- Wrapper calls are **foreground-blocking**. Never launch them via a backgrounded shell (`run_in_background`, `&`, `nohup`) and never end the orchestrator message while one runs: a headless print-mode session kills backgrounded children at wind-down (observed 2026-07-07: an FS1 A-arm IMPLEMENT codex call was killed at turn end → 0-byte delivery). The heartbeat stream is the observability channel; block on the call. Interactive Claude Code caps a foreground Bash call at `BASH_MAX_TIMEOUT_MS` (the installer sets at least 3600000 ms, IMPLEMENT's effective outer ceiling), so every foreground Codex wrapper call must pass the Bash `timeout` explicitly at that ceiling or the phase's own budget (for example, 600s judges) — never rely on the 120 s default.
|
|
67
68
|
- Raw `codex exec ...` invocations are **forbidden** in skill prompts. The benchmark variant arm runs a PATH shim (`scripts/codex-shim/codex`) that transparently re-routes any raw `codex exec` to the wrapper as a safety net, but skills should always emit the wrapper form directly so the orchestrator's first-attempt has the right shape. Two prior iterations (iter-0006 universal foreground ban, iter-0008 prompt-level kill-shape contract) failed because the orchestrator picked starvation-prone shapes (`codex exec ... 2>&1 | tail -200`) from its own pattern prior — the wrapper plus the shim is the runtime binding layer those iters lacked. See `autoresearch/iterations/0009-wrapper-and-hook.md`.
|
|
68
69
|
|
|
70
|
+
## Constrained Windows judge reads
|
|
71
|
+
|
|
72
|
+
Codex supports [native Windows sandboxing](https://learn.chatgpt.com/docs/windows/windows-sandbox). A policy-denied read selects a constrained-read route for that judge; it does not establish that Windows lacks sandbox support or denies every read. The orchestrator supplies the complete spec, sibling expected contract, accepted source SHA and cumulative diff, relevant source/tests with file:line locators, and validated sealed MECHANICAL results, manifests and required raw streams inline in the prompt file. Tell the fresh judge: "Judge only the supplied evidence; run no tools. Missing, truncated or unbound inputs require a verdict-binding BLOCKED finding." Keep `-s read-only`, isolation, freshness, selected model/effort, the 600s budget and normal findings/timeout handling. Never widen sandbox permissions or invent evidence to complete the packet.
|
|
73
|
+
|
|
69
74
|
## Availability check
|
|
70
75
|
|
|
71
76
|
Before the first Codex call in a run, verify the CLI is on PATH:
|
|
@@ -84,4 +89,4 @@ The local Codex CLI, fronted by `codex-monitored.sh`, is the integration boundar
|
|
|
84
89
|
|
|
85
90
|
Skills write the invocation as a Bash command the runtime executes. Example shape from `/devlyn:resolve` PHASE 2 IMPLEMENT when routed to Codex:
|
|
86
91
|
|
|
87
|
-
> Run `bash "$CODEX_MONITORED_PATH" --json -C <state.base_ref.repo_root> -s workspace-write -m <model_requested> -c sandbox_workspace_write.network_access=false -c model_reasoning_effort=xhigh
|
|
92
|
+
> Run `DEVLYN_CODEX_PROMPT_FILE="<IMPLEMENT-prompt-file>" bash "$CODEX_MONITORED_PATH" --json -C <state.base_ref.repo_root> -s workspace-write -m <model_requested> -c sandbox_workspace_write.network_access=false -c model_reasoning_effort=xhigh -`. Capture stdout as the IMPLEMENT reply; non-zero exit → treat as subagent failure. The wrapper emits `[codex-monitored]` heartbeat and lifecycle lines on **stderr** — stdout stays clean for Codex output, so the orchestrator can parse the reply without filtering. Heartbeat-on-stderr keeps the orchestrator's combined-output stream non-silent (defeats the iter-0008 byte-watchdog kill) without polluting the codex-reply view of stdout. Do not pipe the wrapper; direct capture or file redirection preserves streaming and avoids the pipe-refusal exit.
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
# clear message so the orchestrator can self-correct on retry.
|
|
22
22
|
# (Round 2 finding #1 fix: shim alone does not defeat `| tail`; the
|
|
23
23
|
# wrapper must reject the pipe shape directly.)
|
|
24
|
-
# 2. Closes stdin
|
|
24
|
+
# 2. Closes stdin unless DEVLYN_CODEX_PROMPT_FILE supplies the sole prompt.
|
|
25
25
|
# 3. Streams codex stdout to OUR stdout line-by-line — the orchestrator reads
|
|
26
26
|
# stdout as the subagent reply (per `_shared/codex-config.md`) so we MUST
|
|
27
27
|
# NOT swallow it (e.g. `tail -n 200`). codex stderr forwards to OUR stderr.
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
# CODEX_MONITORED_ALLOW_PIPED — set non-empty to skip the pipe-stdout
|
|
55
55
|
# refusal. Reserved for tests; don't use
|
|
56
56
|
# in skill prompts.
|
|
57
|
+
# DEVLYN_CODEX_PROMPT_FILE — exact binary stdin prompt; requires sole `-`.
|
|
57
58
|
# DEVLYN_INVOCATION_* — run/phase/round/workdir/prompt/session/
|
|
58
59
|
# receipt identity. When any is set, all
|
|
59
60
|
# are required and the wrapper seals a
|
|
@@ -269,22 +270,17 @@ if [ -n "${CODEX_MONITORED_ISOLATED:-}" ]; then
|
|
|
269
270
|
printf '[codex-monitored] isolated=1\n' >&2
|
|
270
271
|
fi
|
|
271
272
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
--
|
|
277
|
-
--run-id "$DEVLYN_INVOCATION_RUN_ID" \
|
|
278
|
-
--phase "$DEVLYN_INVOCATION_PHASE" \
|
|
279
|
-
--round "$DEVLYN_INVOCATION_ROUND" \
|
|
280
|
-
--prompt-file "$DEVLYN_INVOCATION_PROMPT_FILE" \
|
|
281
|
-
--session-file "$DEVLYN_INVOCATION_SESSION_FILE" \
|
|
282
|
-
-- "${CODEX_ARGS[@]}" || exit 64
|
|
273
|
+
RECEIPT_HELPER="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/invocation-receipt.py"
|
|
274
|
+
# Native Python owns Windows PIDs and the timeout tree; MSYS PIDs are different.
|
|
275
|
+
if [ "${OS:-}" = Windows_NT ]; then
|
|
276
|
+
exec python3 "$RECEIPT_HELPER" dispatch --binary "$CODEX_BIN" \
|
|
277
|
+
--timeout "$TIMEOUT_SEC" --heartbeat "$HEARTBEAT_SEC" -- "${CODEX_ARGS[@]}" < /dev/null
|
|
283
278
|
fi
|
|
284
279
|
|
|
285
|
-
#
|
|
280
|
+
# The dispatcher snapshots file input, seals actual argv, then execs in this group.
|
|
286
281
|
set -m
|
|
287
|
-
"$
|
|
282
|
+
python3 "$RECEIPT_HELPER" dispatch --binary "$CODEX_BIN" \
|
|
283
|
+
--timeout "$TIMEOUT_SEC" --heartbeat "$HEARTBEAT_SEC" -- "${CODEX_ARGS[@]}" < /dev/null &
|
|
288
284
|
CODEX_PID=$!
|
|
289
285
|
printf '[codex-monitored] codex pid=%d\n' "$CODEX_PID" >&2
|
|
290
286
|
|
|
@@ -308,11 +304,8 @@ if [ -n "$TIMEOUT_FLAG" ] && [ -f "$TIMEOUT_FLAG" ]; then
|
|
|
308
304
|
EXIT=124
|
|
309
305
|
fi
|
|
310
306
|
|
|
311
|
-
if [ -n "$RECEIPT_ENABLED" ]; then
|
|
312
|
-
python3 "$RECEIPT_HELPER"
|
|
313
|
-
--workdir "$DEVLYN_INVOCATION_WORKDIR" \
|
|
314
|
-
--receipt "$DEVLYN_INVOCATION_RECEIPT" \
|
|
315
|
-
--exit-code "$EXIT" || exit 64
|
|
307
|
+
if [ -n "$RECEIPT_ENABLED" ] || [ -n "${DEVLYN_CODEX_PROMPT_FILE:-}" ]; then
|
|
308
|
+
python3 "$RECEIPT_HELPER" complete-dispatch --exit-code "$EXIT" || exit 64
|
|
316
309
|
fi
|
|
317
310
|
|
|
318
311
|
printf '[codex-monitored] codex exited: code=%d elapsed=%ds\n' \
|
|
@@ -49,6 +49,8 @@ Do not report a downgraded successful run when a required engine is missing.
|
|
|
49
49
|
|
|
50
50
|
## Canonical cross-engine invocations
|
|
51
51
|
|
|
52
|
+
For native Windows, use native Node/npm and Python (`python3` on PATH), with Git for Windows Bash for the shipped shell wrapper. Supported npm engine shims resolve to native argv; unsupported/malformed shims fail visibly. Deliver multiline prompts from files: Codex uses `DEVLYN_CODEX_PROMPT_FILE` with explicit `-`; Claude uses `run-bounded.py 600 --stdin-file <prompt-file> --record-transport -- claude -p`. Retain generated transport evidence. A policy-denied Codex judge read uses the complete inline no-tools packet in `_shared/codex-config.md#constrained-windows-judge-reads`, keeping read-only isolation and the selected role.
|
|
53
|
+
|
|
52
54
|
After the availability check passes, Codex keeps the monitored `codex-monitored.sh`
|
|
53
55
|
path; every other resolved OTHER engine follows `_shared/adapters/<name>.md`
|
|
54
56
|
`## Invocation`. Capture `.devlyn/<name>-judge.stdout`, then normalize it with
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"""Deterministic PHASE 6 final-diff gate for /devlyn:resolve."""
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import runpy
|
|
5
6
|
import argparse
|
|
6
7
|
import importlib.util
|
|
7
8
|
import json
|
|
@@ -43,6 +44,7 @@ def git(work: pathlib.Path, *args: str) -> subprocess.CompletedProcess[str]:
|
|
|
43
44
|
cwd=str(work),
|
|
44
45
|
capture_output=True,
|
|
45
46
|
text=True,
|
|
47
|
+
encoding="utf-8",
|
|
46
48
|
)
|
|
47
49
|
|
|
48
50
|
|
|
@@ -329,7 +331,7 @@ def write_state(devlyn: pathlib.Path, state: dict) -> None:
|
|
|
329
331
|
|
|
330
332
|
def read_findings(devlyn: pathlib.Path) -> list[dict]:
|
|
331
333
|
path = devlyn / FINDINGS_NAME
|
|
332
|
-
return [SPEC_VERIFY.loads_strict_json(line) for line in path.read_text().splitlines() if line.strip()]
|
|
334
|
+
return [SPEC_VERIFY.loads_strict_json(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
|
|
333
335
|
|
|
334
336
|
|
|
335
337
|
def read_summary(devlyn: pathlib.Path) -> dict:
|
|
@@ -515,4 +517,5 @@ def main() -> int:
|
|
|
515
517
|
|
|
516
518
|
|
|
517
519
|
if __name__ == "__main__":
|
|
520
|
+
runpy.run_path(str(pathlib.Path(__file__).with_name("platform-support.py")))["configure_utf8"]()
|
|
518
521
|
raise SystemExit(main())
|