devlyn-cli 3.1.2 → 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/AGENTS.md +6 -6
- package/CLAUDE.md +7 -7
- package/README.md +34 -13
- package/bin/devlyn.js +44 -65
- package/bin/instruction-templates.json +82 -0
- package/bin/instructions.js +187 -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/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/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/role-config.py +14 -2
- package/config/skills/_shared/spec-verify-check.py +427 -101
- package/config/skills/_shared/state-phase-write.py +497 -49
- package/config/skills/_shared/task-complete.py +46 -5
- package/config/skills/_shared/terminal-claim-check.py +2 -2
- 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 +29 -51
- package/config/skills/devlyn:resolve/references/free-form-mode.md +2 -2
- package/config/skills/devlyn:resolve/references/phases/build-gate.md +8 -7
- 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 +10 -9
- package/package.json +2 -1
- package/scripts/lint-skills.sh +14 -4
- package/scripts/test-owner-phases.py +193 -0
|
@@ -36,7 +36,7 @@ def require(condition, message):
|
|
|
36
36
|
def command(argv, *, cwd=None, ok=(0,)):
|
|
37
37
|
result = subprocess.run(argv, cwd=cwd, capture_output=True, text=True, encoding="utf-8")
|
|
38
38
|
require(result.returncode in ok, f"{shlex.join(argv[:4])}: {result.stderr.strip() or result.stdout.strip()}")
|
|
39
|
-
return result.stdout.
|
|
39
|
+
return result.stdout.removesuffix("\n")
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def git(work, *args):
|
|
@@ -190,7 +190,7 @@ def allocate(args):
|
|
|
190
190
|
receipt["remote_url"] = remote_url(receipt)
|
|
191
191
|
require("\n" not in receipt["remote_url"]["push"] and receipt["remote_url"]["push"] == receipt["remote_url"]["fetch"], "split/multiple remote URLs are unsupported")
|
|
192
192
|
require(not ref_sha(receipt, "refs/heads/"+args.branch), "existing branch cannot be adopted")
|
|
193
|
-
require(git(work, "symbolic-ref", "
|
|
193
|
+
require(git(work, "symbolic-ref", "HEAD") == "refs/heads/" + args.base, "allocate from the retained base checkout")
|
|
194
194
|
require(not git(work, "status", "--porcelain", "--untracked-files=all"), "allocation requires clean tracked/untracked contents")
|
|
195
195
|
receipt["baseline"] = git(work, "rev-parse", "HEAD")
|
|
196
196
|
target = Path(args.worktree).absolute() if args.worktree else work
|
|
@@ -779,6 +779,14 @@ else:
|
|
|
779
779
|
raise SystemExit('unexpected gh arguments: '+str(a))
|
|
780
780
|
'''
|
|
781
781
|
|
|
782
|
+
GIT_DISPATCH = r'''#!/bin/sh
|
|
783
|
+
for arg do
|
|
784
|
+
case "$arg" in push|fetch|ls-remote|remove) exec python3 "$0.py" "$@";; esac
|
|
785
|
+
done
|
|
786
|
+
exec "$REAL_GIT" "$@"
|
|
787
|
+
'''
|
|
788
|
+
|
|
789
|
+
|
|
782
790
|
GIT_WRAPPER = r'''#!/usr/bin/env python3
|
|
783
791
|
import json, os, pathlib, subprocess, sys
|
|
784
792
|
a = sys.argv[1:]
|
|
@@ -822,7 +830,7 @@ class CompletionTests(unittest.TestCase):
|
|
|
822
830
|
for key in list(self.env):
|
|
823
831
|
if key in {"GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR"}:
|
|
824
832
|
del self.env[key]
|
|
825
|
-
for name, body in (("gh", FAKE_GH), ("git", GIT_WRAPPER)):
|
|
833
|
+
for name, body in (("gh", FAKE_GH), ("git.py", GIT_WRAPPER), ("git", GIT_DISPATCH)):
|
|
826
834
|
path = self.bin / name
|
|
827
835
|
path.write_text(body, encoding="utf-8")
|
|
828
836
|
path.chmod(0o755)
|
|
@@ -854,8 +862,8 @@ class CompletionTests(unittest.TestCase):
|
|
|
854
862
|
r = self.run_cmd([sys.executable, str(Path(__file__).resolve()), *map(str, args)], success=success, cwd=cwd)
|
|
855
863
|
return json.loads(r.stdout), r
|
|
856
864
|
|
|
857
|
-
def allocate(self, linked=False):
|
|
858
|
-
args = ["allocate", "--repo", self.work, "--task", "fixture", "--branch", "task/fixture", "--repository", "test/project", "--base",
|
|
865
|
+
def allocate(self, linked=False, base="main"):
|
|
866
|
+
args = ["allocate", "--repo", self.work, "--task", "fixture", "--branch", "task/fixture", "--repository", "test/project", "--base", base]
|
|
859
867
|
if linked:
|
|
860
868
|
args += ["--worktree", self.root / "linked"]
|
|
861
869
|
result, _ = self.cli(*args)
|
|
@@ -863,6 +871,39 @@ class CompletionTests(unittest.TestCase):
|
|
|
863
871
|
self.task = Path(result["worktree"])
|
|
864
872
|
return result
|
|
865
873
|
|
|
874
|
+
def test_allocate_base_with_same_named_tag(self):
|
|
875
|
+
self.g("tag", "main")
|
|
876
|
+
result = self.allocate(linked=True)
|
|
877
|
+
self.assertEqual(result["status"], "ALLOCATED")
|
|
878
|
+
self.assertEqual(json.loads(self.receipt.read_text(encoding="utf-8"))["base"], "main")
|
|
879
|
+
|
|
880
|
+
def test_allocate_base_preserves_unicode_whitespace(self):
|
|
881
|
+
base = "main\u00a0"
|
|
882
|
+
self.g("switch", "-c", base)
|
|
883
|
+
result = self.allocate(base=base)
|
|
884
|
+
self.assertEqual(result["status"], "ALLOCATED")
|
|
885
|
+
self.assertEqual(json.loads(self.receipt.read_text(encoding="utf-8"))["base"], base)
|
|
886
|
+
|
|
887
|
+
def assert_base_identity_rejected(self, actual, base):
|
|
888
|
+
result, process = self.cli("allocate", "--repo", self.work, "--task", "fixture",
|
|
889
|
+
"--branch", "task/fixture", "--repository", "test/project", "--base", base, success=False)
|
|
890
|
+
self.assertNotEqual(process.returncode, 0)
|
|
891
|
+
self.assertIn("retained base checkout", result["reason"])
|
|
892
|
+
head = self.run_cmd(["git", "-C", str(self.work), "symbolic-ref", "HEAD"]).stdout
|
|
893
|
+
self.assertEqual(head, "refs/heads/" + actual + "\n")
|
|
894
|
+
self.assertFalse((self.work / ".git/devlyn-completion").exists())
|
|
895
|
+
self.assertNotEqual(self.run_cmd(["git", "-C", str(self.work), "show-ref", "--verify",
|
|
896
|
+
"refs/heads/task/fixture"], success=False).returncode, 0)
|
|
897
|
+
|
|
898
|
+
def test_allocate_rejects_trimmed_base_alias(self):
|
|
899
|
+
self.g("switch", "-c", "main\u00a0")
|
|
900
|
+
self.assert_base_identity_rejected("main\u00a0", "main")
|
|
901
|
+
|
|
902
|
+
def test_allocate_rejects_short_base_alias(self):
|
|
903
|
+
self.g("branch", "heads/main")
|
|
904
|
+
self.g("tag", "main")
|
|
905
|
+
self.assert_base_identity_rejected("main", "heads/main")
|
|
906
|
+
|
|
866
907
|
@unittest.skipUnless(sys.platform == "darwin" or sys.platform.startswith("linux"), "writer observation requires POSIX")
|
|
867
908
|
def test_disposable_scratch_cleans_for_local_only_and_preserves_source(self):
|
|
868
909
|
result = self.allocate()
|
|
@@ -149,7 +149,7 @@ def classify_state_bytes(
|
|
|
149
149
|
return malformed("run state must be a JSON object"), None
|
|
150
150
|
|
|
151
151
|
run_id = state.get("run_id")
|
|
152
|
-
if not isinstance(run_id, str) or not SAFE_RUN_ID_RE.fullmatch(run_id):
|
|
152
|
+
if not isinstance(run_id, str) or run_id in (".", "..") or not SAFE_RUN_ID_RE.fullmatch(run_id):
|
|
153
153
|
return malformed("run state has invalid run_id"), state
|
|
154
154
|
phases = state.get("phases")
|
|
155
155
|
if not isinstance(phases, dict):
|
|
@@ -197,7 +197,7 @@ def classify_state_bytes(
|
|
|
197
197
|
verdict = verify.get("verdict")
|
|
198
198
|
if verdict is None:
|
|
199
199
|
return incomplete("verify", "verify completed without verdict", run_id), state
|
|
200
|
-
if verdict not in VALID_VERIFY_VERDICTS:
|
|
200
|
+
if not isinstance(verdict, str) or verdict not in VALID_VERIFY_VERDICTS:
|
|
201
201
|
return malformed("verify has invalid verdict", run_id), state
|
|
202
202
|
else:
|
|
203
203
|
verdict = None
|
|
@@ -110,7 +110,7 @@ Structural lint (inline check, no script needed):
|
|
|
110
110
|
After lint passes:
|
|
111
111
|
1. Write `<spec-dir>/<id>-<slug>/spec.md` (the spec).
|
|
112
112
|
2. Generate `<spec-dir>/<id>-<slug>/spec.expected.json` following `references/spec-template.md` § "Sibling file: `spec.expected.json`" for constraint coverage, diff scope and guard controls.
|
|
113
|
-
3. Run `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --check <spec-path>` to validate the
|
|
113
|
+
3. Run `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --check <spec-path>` to validate the actual spec's carrier shape and supported `complexity` frontmatter. Sibling `spec.expected.json` takes precedence over the legacy inline `## Verification` JSON carrier; any present actionable solo-headroom hypothesis command must match that carrier's `verification_commands[].cmd`. If exit 2, fix the file named in the error and re-run.
|
|
114
114
|
4. Run `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --check-expected <expected-path>` to validate sibling `spec.expected.json` against `_shared/expected.schema.json` plus sibling spec `complexity` frontmatter and any present actionable solo-headroom hypothesis; if the spec has a solo-headroom hypothesis, its observable command must match `spec.expected.json.verification_commands[].cmd`. If exit 2, fix the JSON/frontmatter/hypothesis and re-run.
|
|
115
115
|
5. Print: `spec ready — /devlyn:resolve --spec <spec-path>`.
|
|
116
116
|
|
|
@@ -37,7 +37,7 @@ You must NOT:
|
|
|
37
37
|
3. For each missing/malformed piece: apply the smallest allowed fix.
|
|
38
38
|
4. Write the normalized spec. Default location: `<spec-dir>/<id>-<slug>/spec.md`. With `--in-place` flag: write to `<path>` directly (overwrites the original).
|
|
39
39
|
5. Generate or fix `spec.expected.json` per the rules above. Same dir as the spec.
|
|
40
|
-
6. Run `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --check <spec-path>` to validate the spec carrier and supported `complexity` frontmatter
|
|
40
|
+
6. Run `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --check <spec-path>` to validate the actual spec's carrier shape and supported `complexity` frontmatter. Sibling `spec.expected.json` takes precedence over the legacy inline `## Verification` JSON carrier; any present actionable solo-headroom hypothesis command must match that carrier's `verification_commands[].cmd`. If exit 2, fix the file named in the error and re-run.
|
|
41
41
|
7. Run `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --check-expected <expected-path>` to validate sibling `spec.expected.json` plus sibling spec `complexity` frontmatter and any present solo-headroom hypothesis command against `spec.expected.json.verification_commands[].cmd`.
|
|
42
42
|
8. If lint still fails after allowed fixes (e.g. Requirements section is empty in the source), surface the issue and exit non-zero — do NOT invent Requirements.
|
|
43
43
|
9. If the preserved Requirements combine state mutation with ordering/priority,
|
|
@@ -95,10 +95,10 @@ Schema: `_shared/expected.schema.json`. Required when Requirements have observab
|
|
|
95
95
|
python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --check-expected <expected-path>
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
Map mechanically decidable rules from both Requirements and Constraints into the existing carriers. Validate each new guard with a violating control that fails and an allowed control that passes, preserving permitted pre-existing occurrences:
|
|
98
|
+
Map mechanically decidable rules from both Requirements and Constraints into the existing carriers. Preserve specific file constraints even when narrower than PLAN's scope. During BUILD_GATE, with `state.base_ref.sha` recorded, the existing `authorized_surface` check enforces PLAN's scope over the run diff and newly created untracked paths (`.devlyn/` exempt). Do not duplicate this general scope check with a whole-checkout scan in `verification_commands`. Validate each new guard with a violating control that fails and an allowed control that passes, preserving permitted pre-existing occurrences:
|
|
99
99
|
- `verification_commands` ← `## Verification` + commands the conversation surfaced. While drafting both Verification and its carrier, omit a standalone check only when another command in that contract runs the same assertions under the required execution conditions and propagates failure; describe that shared coverage in Verification and keep both carriers aligned. Preserve distinct scenarios and required independent verification; execute approved commands unchanged without reusing results across phases. For semantic annotation or exception-handling rules, use a suitable syntax-aware/executable check, preferring existing checks; explicitly retain uncovered semantics as source-review obligations.
|
|
100
100
|
- `forbidden_patterns` ← literal syntax constraints only; regex does not prove semantics. It scans the whole file-filtered diff, including context, deletions and headers; additions-only guards must exclude those lines.
|
|
101
|
-
- `required_files` / `forbidden_files` ← file
|
|
101
|
+
- `required_files` / `forbidden_files` ← literal file paths from Requirements, Constraints and Out of Scope; `forbidden_files` matches exact Git-relative paths (forward slashes, no leading `./`), including both names of a rename. Globs and directory-wide exclusions need a suitable `verification_commands` guard, not wildcard-looking entries in these lists.
|
|
102
102
|
- `max_deps_added` ← dependency limits in Requirements or Constraints (default 0 unless the spec licenses a new dep).
|
|
103
103
|
- `required_risk_probe_requirements` ← for specs where a Requirement combines ordering/priority, rollback, idempotency, auth/signature, concurrency, atomic-batch, or exact output shape, declare the risk categories PHASE 1.5 risk-probe derivation must cover directly, e.g. `[{"tag": "rollback_state", "derived_from": "<exact ## Verification bullet>"}]`. This replaces guessing the required categories from prose — declare them explicitly so the check works in any language. `tag` must be one of the risk-probe tags in `_shared/spec-verify-check.py:RISK_PROBE_TAGS`; `derived_from` must be an exact substring of `## Verification`. Omit entirely when the spec has no ordering/rollback/idempotency/auth/concurrency/atomic-batch/exact-shape risk.
|
|
104
104
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: devlyn:resolve
|
|
3
|
-
description:
|
|
3
|
+
description: Default to direct work after inspecting scope and verification, including bounded multi-file changes. Use full resolve for explicit resolve/spec-mode requests, queue drains, or concrete interacting requirements or verification too complex for the current context. Domain labels, file count, spec documents and quoted skill paths alone do not trigger it. Investigate or clarify missing intent/access first. Explicit small resolve retains all phases and independent verification. Verify dual-judge is default-when-available.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
The current CLI owns PHASE 0, state transitions, Git checkpoints and final report/archive. Spawn the canonical phase workers from this process; never delegate the whole run to another parent orchestrator. VERIFY uses a fresh, findings-only worker.
|
|
7
7
|
|
|
8
8
|
<pipeline_config>
|
|
9
9
|
$ARGUMENTS
|
|
@@ -48,13 +48,13 @@ fi
|
|
|
48
48
|
</runtime_paths>
|
|
49
49
|
|
|
50
50
|
<engine_routing>
|
|
51
|
-
Each phase routes to an engine and prepends the per-engine adapter header from `_shared/adapters/<engine>.md` (e.g. `claude.md`, `codex.md`) to the canonical phase body. Adapter is the per-model delta (Anthropic's prompt-engineering guide for Claude, OpenAI's prompt guidance for Codex). Canonical body is engine-agnostic.
|
|
51
|
+
Each model-invoked phase routes to an engine and prepends the per-engine adapter header from `_shared/adapters/<engine>.md` (e.g. `claude.md`, `codex.md`) to the canonical phase body. Adapter is the per-model delta (Anthropic's prompt-engineering guide for Claude, OpenAI's prompt guidance for Codex). Canonical body is engine-agnostic.
|
|
52
52
|
|
|
53
|
-
-
|
|
54
|
-
- Claude Code phases
|
|
55
|
-
- Codex CLI phases: shell out via `bash "$CODEX_MONITORED_PATH"` with the same compounded prompt. Each `codex exec` child is a new session/fresh context. Write the compounded prompt to a file and set `DEVLYN_CODEX_PROMPT_FILE` with sole prompt argument `-`; the wrapper snapshots exact stdin bytes, seals transport evidence and emits a heartbeat. Without file transport stdin remains DEVNULL. No MCP. The wrapper call is foreground-blocking — never a background shell (`run_in_background`, `&`, `nohup`), never end your message while it runs: headless print-mode wind-down kills backgrounded children (0-byte delivery); the heartbeat is the observability channel.
|
|
56
|
-
- oh-my-pi phases: spawn the native `task` tool with a fresh `context` containing adapter-header + canonical-body + task-context. Capture the task result into `.devlyn/<phase>.stdout` and any tool error into `.devlyn/<phase>.stderr` before updating state. If the `task` tool is unavailable for an omp-routed phase, write the current phase verdict as `"BLOCKED"` and report `BLOCKED:fresh-context-unavailable`; do not fall back to same-context execution or a nested `omp -p` subprocess.
|
|
57
|
-
- Default engine: Claude when the orchestrator has Claude Code’s native `Agent`; otherwise its own fresh worker. PLAN is orchestrator-fixed and never inherits `--engine`, an executor pin, or `state.engine`. BUILD_GATE
|
|
53
|
+
- PLAN runs in the owner context; BUILD_GATE and CLEANUP run orchestrator commands with no separate model. The selected IMPLEMENT worker also owns code/doc cleanup before its final checkpoint. IMPLEMENT and VERIFY still require fresh workers. If the current CLI cannot spawn a required worker, write the current phase verdict as `"BLOCKED"` and report `BLOCKED:fresh-context-unavailable` with the failed spawn command; do not continue with ad-hoc same-context execution.
|
|
54
|
+
- Claude Code model-invoked phases: spawn `Agent` (`mode: "bypassPermissions"`); prompt = adapter-header + canonical-body + task-context.
|
|
55
|
+
- Codex CLI model-invoked phases: shell out via `bash "$CODEX_MONITORED_PATH"` with the same compounded prompt. Each `codex exec` child is a new session/fresh context. Write the compounded prompt to a file and set `DEVLYN_CODEX_PROMPT_FILE` with sole prompt argument `-`; the wrapper snapshots exact stdin bytes, seals transport evidence and emits a heartbeat. Without file transport stdin remains DEVNULL. No MCP. The wrapper call is foreground-blocking — never a background shell (`run_in_background`, `&`, `nohup`), never end your message while it runs: headless print-mode wind-down kills backgrounded children (0-byte delivery); the heartbeat is the observability channel.
|
|
56
|
+
- oh-my-pi model-invoked phases: spawn the native `task` tool with a fresh `context` containing adapter-header + canonical-body + task-context. Capture the task result into `.devlyn/<phase>.stdout` and any tool error into `.devlyn/<phase>.stderr` before updating state. If the `task` tool is unavailable for an omp-routed phase, write the current phase verdict as `"BLOCKED"` and report `BLOCKED:fresh-context-unavailable`; do not fall back to same-context execution or a nested `omp -p` subprocess.
|
|
57
|
+
- Default engine: Claude when the orchestrator has Claude Code’s native `Agent`; otherwise its own fresh worker. PLAN is orchestrator-fixed and never inherits `--engine`, an executor pin, or `state.engine`. BUILD_GATE runs orchestrator commands; probes retain their existing routes. `_shared/engine-preflight.md#role-resolution` defines the single resolver and explicit `--role-config` / project role precedence. Absent profiles preserve legacy `--engine` / executor / default behavior; VERIFY primary may be independently selected. Configured role/priority pins and flags fail closed on unavailable engine or failed authentication, including failures discovered at dispatch. Unconfigured automatic VERIFY selects an available OTHER engine.
|
|
58
58
|
- The `--engine` flag does not disable default pairing: the second judge uses the OTHER engine by default when available.
|
|
59
59
|
- Multi-LLM evolution: when a new model adapter ships in `_shared/adapters/`, that engine becomes selectable for the roles its adapter declares eligible without further skill changes (NORTH-STAR.md "Multi-LLM evolution direction").
|
|
60
60
|
</engine_routing>
|
|
@@ -72,7 +72,7 @@ After `state.implement_passed_sha` is set: SURFACE_CLOSE and CLEANUP are limited
|
|
|
72
72
|
</post_implement_invariant>
|
|
73
73
|
|
|
74
74
|
<transition_protocol>
|
|
75
|
-
For every direct complete→spawn handoff, call `state-phase-write.py ... --phase <current> transition` with the current phase's normal completion/attestation arguments plus caller-specified `--next-phase`, `--next-round`, `--next-triggered-by`, `--next-engine`, `--next-model`, and any next-phase metadata. The verb validates a legal edge and commits both lifecycle writes atomically; it returns JSON state facts only. It never selects a phase/engine, renders a prompt, or spawns an agent. Use standalone `spawn` only for the initial post-bootstrap dispatch; use standalone `complete` when no next phase opens (including a halt).
|
|
75
|
+
For every direct complete→spawn handoff, call `state-phase-write.py ... --phase <current> transition` with the current phase's normal completion/attestation arguments plus caller-specified `--next-phase`, `--next-round`, `--next-triggered-by`, `--next-engine`, `--next-model`, and any next-phase metadata. For owner PLAN/BUILD_GATE/CLEANUP, omit engine/model/prompt/session arguments. PLAN records `execution_kind: "orchestrator_context"`; BUILD_GATE/CLEANUP record `execution_kind: "orchestrator_commands"`, all with null worker identity. Existing metadata-bearing PLAN/CLEANUP and already-open absent-kind BUILD spans retain historical worker validation; never relabel their evidence. The verb validates a legal edge and commits both lifecycle writes atomically; it returns JSON state facts only. It never selects a phase/engine, renders a prompt, or spawns an agent. Use standalone `spawn` only for the initial post-bootstrap dispatch; use standalone `complete` when no next phase opens (including a halt).
|
|
76
76
|
</transition_protocol>
|
|
77
77
|
|
|
78
78
|
## PHASE 0: PARSE + CLASSIFY + ROUTE
|
|
@@ -97,7 +97,7 @@ Outer-owner boundary: before normal task writes, follow `references/task-complet
|
|
|
97
97
|
|
|
98
98
|
5. Announce one line: `resolve starting — run <run_id> — engine <engine> — mode <mode> — complexity <complexity-or-na> — pair <on|solo:auto_pair_other_engine_unavailable|disabled> — risk_probes <on|off>`.
|
|
99
99
|
|
|
100
|
-
6. Open the initial post-bootstrap span with standalone `state-phase-write.py ... spawn`: `plan` before
|
|
100
|
+
6. Open the initial post-bootstrap span with standalone `state-phase-write.py ... spawn`: `plan` before owner planning (normal run), or `verify` before MECHANICAL (verify-only). The bootstrap never chooses a phase, engine, branch, prompt, or agent.
|
|
101
101
|
|
|
102
102
|
### Explicit role dispatch
|
|
103
103
|
|
|
@@ -111,17 +111,13 @@ After the actual process exits, run `python3 "$DEVLYN_SHARED_DIR/judge-role-evid
|
|
|
111
111
|
|
|
112
112
|
## PHASE 1: PLAN
|
|
113
113
|
|
|
114
|
-
Skip in verify-only mode. The
|
|
114
|
+
Skip in verify-only mode. PLAN is orchestrator-fixed and never inherits `--engine`, an executor pin, or `state.engine`. The owner reads `references/phases/plan.md` and the original request/spec, then writes `.devlyn/plan.md` before product edits. Open with `state-phase-write.py ... --phase plan spawn --round 0`, without worker identity or a prompt dispatch. This is owner reasoning, not zero-cost mechanical work.
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
Subagent output (writes `.devlyn/plan.md`): file list to touch, risk list (out-of-scope expansions, ambiguous spec sections), acceptance restatement (what `## Verification` actually requires verbatim). For large work only, PLAN may add a fourth section `## Execution phases` under the conditions in `references/phases/plan.md`; a phase block missing a runnable `gate:` line disqualifies the whole section — treat the run as single-phase and log the disqualification.
|
|
119
|
-
|
|
120
|
-
State write: each PLAN receipt carries spawn-known `round`, `started_at`, `triggered_by`, `engine`, `model_requested`, and `prompt_sha256`; completion adds `completed_at`, `duration_ms`, `verdict`, nullable `model_effective`, and `output_sha256` under `references/state-schema.md`. Later mutations rehash bound output. Never edit PLAN to widen scope after completion; only the bounded PLAN re-spawn may replace its receipt.
|
|
116
|
+
Keep the file list/authorized surface, risks and verbatim verification requirements. Large work may include the existing execution-phase section only when every boundary has a runnable gate. Completing PLAN binds the exact output digest; all later mutations rehash it. Never widen it after implementation starts.
|
|
121
117
|
|
|
122
118
|
After return:
|
|
123
119
|
1. If `.devlyn/plan.md` lists zero files → halt with verdict `BLOCKED:plan-empty`.
|
|
124
|
-
2. If
|
|
120
|
+
2. If the plan exceeds the authorized scope, the sole correction is owner round 1 with `--triggered-by plan`, before implementation starts. Narrow to the original authorization, complete and bind the new output; a second failed plan halts. No worker dispatch or new permission is implied.
|
|
125
121
|
3. After any re-spawn above, if `state.risk_profile.risk_probes_enabled == true` and `state.risk_profile.risk_probes_explicit == false`, parse the `authorized_surface` array from the JSON block under `<!-- devlyn:authorized-surface -->`. For a well-formed string array, compute `probe_scale_small := len(authorized_surface) <= 2 AND no entry ends in "/**"`. If true, set `risk_probes_enabled = false`, leave `high_risk` unchanged, and append `auto-risk-probes demoted: plan surface small (<n> paths)` to `reasons` using the actual length. A missing or malformed block leaves probe state unchanged; BUILD_GATE owns its malformed-block failure.
|
|
126
122
|
|
|
127
123
|
## PHASE 1.5: RISK_PROBES
|
|
@@ -228,9 +224,9 @@ After return:
|
|
|
228
224
|
|
|
229
225
|
Skip in verify-only mode. Constrained design judgment within PLAN's invariants. Writes code, tests, and inline doc-comments. No standalone DOCS phase — what the spec licenses is updated here, what it does not is out of scope.
|
|
230
226
|
|
|
231
|
-
Engine/model/effort: frozen `role_resolution.roles.worker
|
|
227
|
+
Engine/model/effort: frozen `role_resolution.roles.worker`, including code/doc cleanup within this same invocation before its final checks; obtain validated argv additions using `role-config.py --state .devlyn/pipeline.state.json --role worker --resolved-model <existing-exact-phase-model>`. Prompt body: `references/phases/implement.md`.
|
|
232
228
|
|
|
233
|
-
For every Codex-routed IMPLEMENT
|
|
229
|
+
For every Codex-routed IMPLEMENT spawn, render the exact
|
|
234
230
|
prompt to `.devlyn/<phase>.prompt.<round>`, pass its SHA-256 to `state-phase-write.py
|
|
235
231
|
spawn --prompt-sha256`, and invoke only through `codex-monitored.sh` with these
|
|
236
232
|
seven variables set to the active state identity:
|
|
@@ -240,13 +236,14 @@ The session and receipt paths are `.devlyn/<phase>.worker-session.<round>.jsonl`
|
|
|
240
236
|
and `.devlyn/<phase>.invocation.<round>.json`. These three paths are round-scoped
|
|
241
237
|
so a retry cannot overwrite earlier prompt/session evidence. Every invocation
|
|
242
238
|
must include `--json -m <model_requested>` and `-c sandbox_workspace_write.network_access=<true|false>`: exactly
|
|
243
|
-
`
|
|
239
|
+
`false` for IMPLEMENT, so
|
|
244
240
|
user configuration cannot silently change the phase capability. Redirect wrapper stdout directly
|
|
245
241
|
to that session path. The wrapper rejects bypass/yolo flags and any sandbox other
|
|
246
242
|
than `workspace-write`. The receipt seals the requested model, sandbox, phase-scoped
|
|
247
243
|
network capability, prompt, terminal exit, and session digest; completion passes the
|
|
248
244
|
same canonical session via `--engine-session-log`. A missing/mismatched receipt
|
|
249
245
|
or same-round retry blocks. Respawn with a new round instead of replacing it.
|
|
246
|
+
Historical metadata-bearing PLAN/CLEANUP and absent-kind BUILD spans retain their original receipt-bound worker routes (network false for PLAN/CLEANUP, true for BUILD). New owner spans never create worker receipts.
|
|
250
247
|
|
|
251
248
|
State write: `phases.implement.{started_at, verdict, completed_at, duration_ms}`.
|
|
252
249
|
|
|
@@ -276,56 +273,37 @@ Run once iff `state.source.type == "generated"` and complexity is trivial/medium
|
|
|
276
273
|
|
|
277
274
|
Freeze `.devlyn/surface-close.input.patch`; assemble Claude adapter + `references/phases/surface-close.md` canonical body VERBATIM + data-only Goal/patch/hashes/surface/commands; write the exact bytes to `.devlyn/surface-close.prompt.<round>`, hash them and spawn with `--tools "Read,Grep,Glob,Edit,Write" --dangerously-skip-permissions --output-format json --strict-mcp-config --mcp-config '{"mcpServers":{}}'`, omitting `--model` at SPW spawn to inherit native Claude model settings. Record the effective model from the native JSON result. Use no positional prompt on the CLI route and retain its generated transport carrier. Bound at 600s (native or `run-bounded.py 600 --stdin-file .devlyn/surface-close.prompt.<round> --record-transport -- claude -p`), else block pre-spawn. Save raw stdout as `.devlyn/surface-close.output.json`, extract its `result` string to `.devlyn/surface-close.stdout`, retain `.devlyn/surface-close.worker-session.<round>.jsonl`, and complete with `.devlyn/surface-close.output.json` as `--engine-session-log`; non-JSON/missing result follows the existing failure path. Workers execute nothing; `surface-check` gates rows, citations, scope, and execution. Empty PASS completes; authorized delta is scoped-staged and committed. Only `BLOCKED:surface-close-adjudication-malformed` runs `surface-adjudication-recover --authorized-surface-json <array>`, whose successful rollback and transcript Edit/Write audit complete the nonterminal skip carrier and continue to BUILD_GATE. Timeout, input/prompt mismatch, attestation failure, execution-audit violation, and out-of-surface delta run `surface-rollback`, complete bare `BLOCKED`, and halt; rollback failure also halts. One shot; no `max_rounds`.
|
|
278
275
|
|
|
279
|
-
**Common post-fix checkpoint (BUILD_GATE and VERIFY):** After fix IMPLEMENT returns, increment `state.rounds.global`; scoped-stage the authorized surface and commit `chore(pipeline): implement fix round <n>`; run `python3 "$DEVLYN_SHARED_DIR/state-phase-write.py" --devlyn-dir .devlyn --phase implement durability-enforce --round <n> --origin-phase <build_gate|verify>`. Its receipt must PASS before re-entry, which enforces it again before VERIFY artifact clearing or phase spawn.
|
|
276
|
+
**Common post-fix checkpoint (BUILD_GATE and VERIFY):** CLEANUP findings use the same checkpoint with origin `cleanup`. After fix IMPLEMENT returns, increment `state.rounds.global`; scoped-stage the authorized surface and commit `chore(pipeline): implement fix round <n>`; run `python3 "$DEVLYN_SHARED_DIR/state-phase-write.py" --devlyn-dir .devlyn --phase implement durability-enforce --round <n> --origin-phase <build_gate|cleanup|verify>`. Its receipt must PASS before re-entry, which enforces it again before VERIFY artifact clearing or phase spawn.
|
|
280
277
|
|
|
281
278
|
## PHASE 3: BUILD_GATE
|
|
282
279
|
|
|
283
280
|
Skip in verify-only mode OR when `build-gate` in `state.bypasses`. Deterministic — same commands CI / Docker / production run.
|
|
284
281
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
must not add isolation, narrow the sandbox, or widen permissions inside the
|
|
289
|
-
worker. Invoke a Codex BUILD_GATE with
|
|
290
|
-
`-s workspace-write -c sandbox_workspace_write.network_access=true`; the
|
|
291
|
-
receipt rejects a missing, false, duplicate, or non-BUILD_GATE network override
|
|
292
|
-
before the worker starts. This retains the write sandbox while allowing the
|
|
293
|
-
loopback and network operations exercised by CI-equivalent gates. If an
|
|
294
|
-
authoritative route/tool response denies a required filesystem,
|
|
295
|
-
subprocess, loopback, PTY, or network operation, preserve that denial through
|
|
296
|
-
the BUILD_GATE process-evidence manifest and complete the phase as
|
|
297
|
-
`BLOCKED:build-env-underprovisioned`, not as a product finding or substitute
|
|
298
|
-
command. The worker:
|
|
282
|
+
The orchestrator reads `references/phases/build-gate.md` and runs the gates directly: no adapter header, rendered worker prompt or separate model. Open its span through the predecessor transition with no BUILD engine/model/prompt/session arguments. New spans record `execution_kind: "orchestrator_commands"`; historical already-open spans with that field absent retain their worker attestation contract.
|
|
283
|
+
|
|
284
|
+
Follow the BUILD body's capability contract on the parent's actual command route and log that basis; no child receipt attests it. Authoritative denial yields report-level `BLOCKED:build-env-underprovisioned` (bare `BLOCKED` in state). Keep spec/probe capture and its conditional post-spec denial carrier refresh; ordinary gates retain findings and raw logs. The orchestrator:
|
|
299
285
|
1. Detects language/framework via project files (`package.json`, `pyproject.toml`, etc.).
|
|
300
|
-
2. Runs language-specific gates (tsc / lint / test).
|
|
286
|
+
2. Runs language-specific gates (tsc / lint / test), deferring exact literal overlaps under `references/phases/build-gate.md`'s current-contract/current-round rule; task-context prompts must preserve that rule.
|
|
301
287
|
3. Always runs `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --include-risk-probes` (verification_commands literal-match plus `.devlyn/risk-probes.jsonl` when present). If `state.risk_profile.risk_probes_enabled == true`, the script requires `.devlyn/risk-probes.jsonl`; a missing file is a CRITICAL mechanical blocker, not a silent solo run. The script routes each command through `process-evidence.py`; preserve `.devlyn/spec-verify.results.json` and its validated BUILD_GATE carrier for the state-bound archive flow.
|
|
302
288
|
4. If diff touches web-surface files: run the browser tier with the repo's available toolchain (for example Playwright or curl).
|
|
303
289
|
5. Emits `.devlyn/build_gate.findings.jsonl` + `.devlyn/build_gate.log.md`.
|
|
304
290
|
|
|
305
|
-
State write: `phases.build_gate.{started_at, verdict, completed_at, duration_ms, artifacts}
|
|
291
|
+
State write: `phases.build_gate.{execution_kind, started_at, verdict, completed_at, duration_ms, artifacts}`; engine/model fields are null, with no worker prompt or receipt. Completion keeps PLAN integrity and process-evidence floors.
|
|
306
292
|
|
|
307
293
|
Branch:
|
|
308
294
|
- `PASS` → PHASE 4.
|
|
309
|
-
- `FAIL` → fix loop.
|
|
295
|
+
- `FAIL` → before product repair, halt with report-level `BLOCKED:required-tools-unavailable` only if a parent probe confirms the failed invocation's external gate tool is absent, unchanged `state.base_ref.sha` configuration requires it, and cited explicit task/spec constraints prohibit supplying it through any authorized route. Derive the tool from that configuration and invocation, never diagnostic text; bind the probe's interpreter, work root and normalized child environment to the runner call sites and actual gate invocation, not parent PATH or a manifest command string alone. An available authorized tool route, product-module/configuration failure, changed tool configuration, permitted or unspecified supply, or uncertain probe/identity/environment retains the fix loop. A confirmed prohibited-tool blocker wins even with other product findings: leave them unrepaired and preserve phase `FAIL`, findings, raw streams and sealed manifests unchanged. Record the probe and constraint citation separately in parent halt evidence; no repair means no round increment. Otherwise spawn IMPLEMENT-engine agent with the build_gate findings as input, then run the common post-fix checkpoint with origin `build_gate`. On second FAIL with `state.rounds.global >= state.rounds.max_rounds` → halt with verdict `BLOCKED:build-gate-exhausted`.
|
|
310
296
|
- `BLOCKED:build-env-underprovisioned` → halt immediately with the denied operation, original command, and process-evidence manifest/id; do not enter the product fix loop.
|
|
311
297
|
|
|
312
298
|
## PHASE 4: CLEANUP
|
|
313
299
|
|
|
314
|
-
Skip if `cleanup` in `state.bypasses`.
|
|
300
|
+
Skip if `cleanup` in `state.bypasses`. The owner reads `references/phases/cleanup.md`; no separate model invocation. Code/doc cleanup already belongs to the selected IMPLEMENT worker, with its original engine/model/effort and receipt.
|
|
315
301
|
|
|
316
|
-
|
|
317
|
-
- Tooling artifacts the spec did not list as deliverables (`test-results/`, `playwright-report/`, `.last-run.json`, coverage HTML).
|
|
318
|
-
- Dead code added by this diff (not pre-existing dead code).
|
|
319
|
-
- Doc references whose target this diff renamed or removed.
|
|
302
|
+
Open through the predecessor transition with `--next-pre-sha "$(git rev-parse HEAD)"`, omitting worker identity. Remove only observed generated, untracked/ignored artifacts owned by this run and not required deliverables; preserve source, user data, evidence and restart records. For PASS, tracked source and HEAD must equal `pre_sha`; the state writer checks them against `--post-sha` and rejects new untracked files relative to `.devlyn/untracked.baseline` (`.devlyn/` exempt). FAIL/BLOCKED closure preserves the observed delta and cannot enter VERIFY.
|
|
320
303
|
|
|
321
|
-
|
|
304
|
+
If a new code/doc cleanup issue appears after BUILD, emit a concrete finding and complete CLEANUP as FAIL into the existing selected IMPLEMENT repair route with `--next-triggered-by cleanup`, within the shared round budget. After repair, checkpoint and rerun BUILD_GATE before CLEANUP and fresh VERIFY; never reuse the earlier green checks. Keep the existing authorized surface, allowlist and durability checks. Do not broaden scope for cleanup.
|
|
322
305
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
After return:
|
|
326
|
-
1. Run `git diff --name-only <pre_sha>` and compare new untracked files against `.devlyn/untracked.baseline` (`.devlyn/` exempt) — any path outside the cleanup allowlist (including a cleanup-created untracked file) → revert to `pre_sha`, delete the unauthorized untracked file, and emit `invariant.cleanup-out-of-scope` into `.devlyn/cleanup.findings.jsonl`.
|
|
327
|
-
2. If allowlist honored and diff non-empty: `git add -u && git commit -m "chore(pipeline): cleanup"`.
|
|
328
|
-
3. Transition CLEANUP→VERIFY through `<transition_protocol>` with `--post-sha "$(git rev-parse HEAD)"` (also when the diff was empty), preserving completion/session arguments and the next-phase identity below.
|
|
306
|
+
On success transition CLEANUP→VERIFY with `--post-sha "$(git rev-parse HEAD)"` and the next judge identity. VERIFY MECHANICAL runs independently on the final source.
|
|
329
307
|
|
|
330
308
|
## PHASE 5: VERIFY (fresh subagent, findings-only)
|
|
331
309
|
|
|
@@ -378,7 +356,7 @@ Open the `final_report` span through the predecessor's `state-phase-write.py --d
|
|
|
378
356
|
|
|
379
357
|
3. **Terminal verdict** — derive from `state.phases.{plan, implement, surface_close, build_gate, cleanup, verify}.verdict` per the precedence rules in `references/state-schema.md#terminal-verdict`. Verify-only mode short-circuits to `state.phases.verify.verdict`.
|
|
380
358
|
|
|
381
|
-
4. **Render report to `.devlyn/final-report.md` before completion** — first line exactly `<!-- devlyn:final-report run_id=<current state.run_id> -->`, once only, followed by a nonempty report body. Sections: header (run_id, engine, mode, verdict, wall-time), per-phase summary (including SURFACE_CLOSE run or skip), pair/risk-probe status, findings table (verify + finish-gate findings), follow-up notes (the explicit line `pipeline continued to BUILD_GATE — surface_close_rolled_back_adjudication_malformed` when `continued_after_block` is set, any large-mode `## Assumptions` block, any pair-judge TIMEOUT (headline: solo verdict after pair TIMEOUT), any `--no-pair` / `--no-risk-probes` opt-out, any engine setup guidance after BLOCKED, `/devlyn:ideate` guidance after `BLOCKED:solo-headroom-hypothesis-required` that asks for the visible behavior `solo_claude` is expected to miss, and `/devlyn:ideate` guidance after `BLOCKED:solo-ceiling-avoidance-required` that asks for the concrete difference from rejected or solo-saturated controls such as `S2`-`S6`). User-facing text may follow archive; it cannot substitute for this file.
|
|
359
|
+
4. **Render report to `.devlyn/final-report.md` before completion** — first line exactly `<!-- devlyn:final-report run_id=<current state.run_id> -->`, once only, followed by a nonempty report body. Sections: header (run_id, engine, mode, verdict, wall-time), per-phase summary (including SURFACE_CLOSE run or skip, owner PLAN reasoning, and BUILD_GATE/CLEANUP as orchestrator commands with no separate model; code cleanup is included in IMPLEMENT cost), pair/risk-probe status, findings table (verify + finish-gate findings), follow-up notes (the explicit line `pipeline continued to BUILD_GATE — surface_close_rolled_back_adjudication_malformed` when `continued_after_block` is set, any large-mode `## Assumptions` block, any pair-judge TIMEOUT (headline: solo verdict after pair TIMEOUT), any `--no-pair` / `--no-risk-probes` opt-out, any engine setup guidance after BLOCKED, `/devlyn:ideate` guidance after `BLOCKED:solo-headroom-hypothesis-required` that asks for the visible behavior `solo_claude` is expected to miss, and `/devlyn:ideate` guidance after `BLOCKED:solo-ceiling-avoidance-required` that asks for the concrete difference from rejected or solo-saturated controls such as `S2`-`S6`). User-facing text may follow archive; it cannot substitute for this file.
|
|
382
360
|
|
|
383
361
|
5. Complete the span with `state-phase-write.py --devlyn-dir .devlyn --phase final_report complete --verdict <bare enum> --log-file .devlyn/final-report.md` — the enum class of the terminal verdict (`BLOCKED:<reason>` → `BLOCKED`; `NEEDS_WORK` / `PASS_WITH_ISSUES` / `PASS` unchanged) — BEFORE archive runs (archive prune skips runs whose `final_report.verdict` is null). The writer validates the canonical nonsymlink regular file, current run marker and nonempty body, then binds its exact bytes; validation failure leaves the phase open. Never hand-edit lifecycle fields in `pipeline.state.json` (`references/state-schema.md` § Write protocol).
|
|
384
362
|
|
|
@@ -75,8 +75,8 @@ When the rules are silent (rare — pathological goal text), default to `medium`
|
|
|
75
75
|
The internal mini-spec written for trivial / medium / large-assumptions paths must satisfy:
|
|
76
76
|
|
|
77
77
|
- `## Requirements` non-empty, each bullet testable (CLI command, test command, observable file change).
|
|
78
|
-
- For executable generated verification, put one fenced `json` object with
|
|
79
|
-
- `## Verification` is preceded by a `<!-- devlyn:verification -->` sentinel on its own line directly above the heading — the machine locator `spec-verify-check.py` uses; the heading text itself is decorative and may be any language.
|
|
78
|
+
- For executable generated verification, put one fenced `json` object with `verification_commands` inside the sentinel-marked `## Verification` section. Generated mode reads this inline carrier, not a sibling file. Follow `../../devlyn:ideate/references/spec-template.md` § "Sibling file: `spec.expected.json`" for constraint coverage, diff scope and violating/allowed controls, but express those checks as executable `verification_commands`; sibling-only fields such as `required_files` and `forbidden_patterns` are rejected inline. Preserve uncovered semantics as source-review obligations.
|
|
79
|
+
- `## Verification` is preceded by a `<!-- devlyn:verification -->` sentinel on its own line directly above the heading — the machine locator `spec-verify-check.py` uses; the heading text itself is decorative and may be any language. When all Requirements are pure-design with no runnable acceptance check, put `{"pure_design": true, "verification_commands": []}` in the fenced `json` block and retain semantic source-review obligations. This explicit declaration is required; a missing block, an unmarked empty list, or `pure_design: true` with commands is invalid.
|
|
80
80
|
- If a free-form goal includes pair-evidence intent and already includes an actionable solo-headroom hypothesis, preserve that literal hypothesis in `.devlyn/criteria.generated.md` unchanged enough for VERIFY to detect `solo-headroom hypothesis`, `solo_claude`, `miss`, and the backticked observable command line that itself contains `miss`, emit the canonical `spec.solo_headroom_hypothesis` pair trigger reason, and satisfy regenerated-evidence checks such as `benchmark audit --require-hypothesis-trigger`.
|
|
81
81
|
- If a free-form goal includes unmeasured pair-candidate intent and already includes solo ceiling avoidance, preserve that literal note in `.devlyn/criteria.generated.md` unchanged enough for reviewers to see `solo ceiling avoidance`, `solo_claude`, and the concrete difference from rejected or solo-saturated controls such as `S2`-`S6`.
|
|
82
82
|
- Free-form mode mini-specs are written to `.devlyn/criteria.generated.md` (not to a roadmap path) — this is run-scoped artifact, not a documented spec.
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
# PHASE 3 — BUILD_GATE (canonical body)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The orchestrator reads this body directly, without an adapter header or worker prompt. BUILD_GATE runs the same commands CI / Docker / production run; gate selection and diagnosis remain orchestrator work.
|
|
4
4
|
|
|
5
5
|
<role>
|
|
6
|
-
Run language-specific gates and the spec literal-match verification. Emit findings; the orchestrator's fix loop consumes them.
|
|
6
|
+
Run language-specific gates and the spec literal-match verification. Emit findings; the orchestrator's fix loop consumes them. Git staging and commits belong to the parent IMPLEMENT checkpoint; verify any staging prerequisite with read-only Git inspection, never repeat `git add` or forward task staging instructions as worker actions.
|
|
7
7
|
</role>
|
|
8
8
|
|
|
9
9
|
<capability_contract>
|
|
10
|
-
|
|
11
|
-
filesystem, subprocess, loopback, PTY, and network capabilities are part of the
|
|
12
|
-
gate input, not permissions this worker may widen.
|
|
10
|
+
Before executing gates, inspect the orchestrator command route's effective filesystem, subprocess, loopback, PTY and network capabilities and record the actual basis in `.devlyn/build_gate.log.md`. Use an already authorized CI-equivalent route; do not infer capabilities from an engine name or unrelated command success, narrow the route, or widen permissions. No child receipt attests these parent capabilities.
|
|
13
11
|
|
|
14
12
|
If the parent route or an authoritative tool response explicitly denies a
|
|
15
13
|
required operation before the command can produce product output:
|
|
@@ -24,6 +22,8 @@ required operation before the command can produce product output:
|
|
|
24
22
|
3. Do not emit a product finding, run a substitute command, narrow the command,
|
|
25
23
|
retry it on another route, or treat the denial as test/lint output.
|
|
26
24
|
|
|
25
|
+
If a denial is appended after `.devlyn/spec-verify.results.json` was sealed, refresh only that existing object's `process_evidence` and `commands` before completion. Load `$DEVLYN_SHARED_DIR/process-evidence.py` with Python's `runpy.run_path`; using the current work root, `state.run_id`, `"build_gate"`, and its round, call `validate_manifest(work, manifest_relative_path(state, "build_gate"), run_id, "build_gate", round_, require_expectations=False)`, then `bound_carrier_summary_commands(work, carrier)` and `validate_summary_commands(work, commands, carrier)`. Replace only those two result fields with the returned carrier/commands. Preserve all other fields, genuine findings, failed expectations and raw streams; do not rewrite the manifest or replay commands. This is conditional on an actual append, not routine browser resealing. If no results file exists, retain the existing BLOCKED completion path.
|
|
26
|
+
|
|
27
27
|
The state writer derives the phase verdict floor from this sealed manifest. A
|
|
28
28
|
capability denial can complete only as `BLOCKED`; a failed product expectation
|
|
29
29
|
cannot complete as `PASS` or `PASS_WITH_ISSUES` even if a mutable results or
|
|
@@ -46,12 +46,12 @@ Detect the project shape from files in `state.base_ref.sha`:
|
|
|
46
46
|
</detection>
|
|
47
47
|
|
|
48
48
|
<gates>
|
|
49
|
-
Run in
|
|
49
|
+
Resolve the concrete commands for steps 1–3; detection examples are not command identities. Run them in order unless step 4's current valid literal contract, using its source precedence and checker defaults, schedules the exact command with the same source/test inputs, working directory and effective environment. Establish the work root and normalized child environment from the checker/runner call sites and the current invocation; a manifest's command string alone does not establish them. The literal must expect exit 0, cover every gate assertion and use the same effective timeout as the planned gate invocation; uncertain matches run normally. Defer a matching gate to step 4 and derive its usual per-error findings and severity from that invocation's validated current BUILD_GATE run/round raw streams, citing the evidence id. A completed nonzero exit still supplies the gate's usual failure findings. If step 4 leaves no validated exit-kind result for a deferred gate, run that gate normally and append its findings, preserving step 4's failures; capability denials retain the stop contract above. Keep every literal invocation and independent post-CLEANUP VERIFY. Each gate emits findings into `.devlyn/build_gate.findings.jsonl`:
|
|
50
50
|
|
|
51
51
|
1. **Type check** (TypeScript / mypy / etc.). Each error → one finding, severity `HIGH`, rule `correctness.type-check`.
|
|
52
52
|
2. **Lint**. Run a recognized configured language linter (for example, ESLint with a config, `ruff`, or `clippy`) and/or the exact `package.json` `scripts.lint` command. A declared specialized `lint:*` script (for example, `lint:json`) is not the general language lint gate on the strength of its declaration alone. If the spec's verification commands explicitly name such a specialized command, step 4 executes it and preserves its real failure severity. If no linter is applicable, log an explicit SKIP in `.devlyn/build_gate.log.md` and emit zero lint findings. Each error → finding, severity `MEDIUM`, rule `quality.lint`. Warnings stay LOW unless the spec elevates them.
|
|
53
53
|
3. **Test suite** (npm test / pytest / go test / cargo test). Each failing test → finding, severity `HIGH`, rule `correctness.test-failure`. Include the failing test's file:line and the assertion.
|
|
54
|
-
4. **Spec literal verification + risk probes**: `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --include-risk-probes`. The script routes every literal command and risk probe through `process-evidence.py`, preserving exact command, outcome, raw stdout/stderr, and digests in the BUILD_GATE run/round manifest. It self-stages from sibling `spec.expected.json` next to `state.source.spec_path`, or the legacy inline carrier when the sibling is absent; benchmark-prestaged `.devlyn/spec-verify.json` still wins. It appends `.devlyn/risk-probes.jsonl` when present, and requires that file when `state.risk_profile.risk_probes_enabled == true`. Malformed `state.risk_profile` is also CRITICAL because it can hide enabled risk probes. When probes are enabled or `.devlyn/risk-probes.jsonl` exists, missing or mismatched top-level `state.risk_probes_digest` emits `correctness.risk-probe-integrity` CRITICAL; the digest binds `.devlyn/risk-probes.jsonl` plus referenced `.devlyn/probes/` script bytes. Command or risk-probe mismatch → CRITICAL finding. Missing required risk probes, missing/malformed generated carrier, or malformed sibling expected file → `correctness.spec-verify-malformed` CRITICAL. The same invocation also enforces PLAN's declared `authorized_surface` (the json block under `.devlyn/plan.md`'s `<!-- devlyn:authorized-surface -->` section, located by that sentinel regardless of the heading's language) against this run's diff and untracked delta: any changed path or created-during-run untracked path outside the declared surface → `scope.out-of-scope-file` CRITICAL (`.devlyn/` exempt) — the only sanctioned fix is removing the file, never widening the declared surface, since that would let the same worker that leaked scope self-authorize the leak; missing sentinel, malformed `plan.md`/surface block, or missing `.devlyn/untracked.baseline` → `scope.authorized-surface-malformed` CRITICAL. This scope check runs only here (BUILD_GATE), not when VERIFY MECHANICAL reuses this script post-CLEANUP,
|
|
54
|
+
4. **Spec literal verification + risk probes**: `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --include-risk-probes`. The script routes every literal command and risk probe through `process-evidence.py`, preserving exact command, outcome, raw stdout/stderr, and digests in the BUILD_GATE run/round manifest. It self-stages from sibling `spec.expected.json` next to `state.source.spec_path`, or the legacy inline carrier when the sibling is absent; benchmark-prestaged `.devlyn/spec-verify.json` still wins. It appends `.devlyn/risk-probes.jsonl` when present, and requires that file when `state.risk_profile.risk_probes_enabled == true`. Malformed `state.risk_profile` is also CRITICAL because it can hide enabled risk probes. When probes are enabled or `.devlyn/risk-probes.jsonl` exists, missing or mismatched top-level `state.risk_probes_digest` emits `correctness.risk-probe-integrity` CRITICAL; the digest binds `.devlyn/risk-probes.jsonl` plus referenced `.devlyn/probes/` script bytes. Command or risk-probe mismatch → CRITICAL finding. Missing required risk probes, missing/malformed generated carrier, or malformed sibling expected file → `correctness.spec-verify-malformed` CRITICAL. The same invocation also enforces PLAN's declared `authorized_surface` (the json block under `.devlyn/plan.md`'s `<!-- devlyn:authorized-surface -->` section, located by that sentinel regardless of the heading's language) against this run's diff and untracked delta: any changed path or created-during-run untracked path outside the declared surface → `scope.out-of-scope-file` CRITICAL (`.devlyn/` exempt) — the only sanctioned fix is removing the file, never widening the declared surface, since that would let the same worker that leaked scope self-authorize the leak; missing sentinel, malformed `plan.md`/surface block, or missing `.devlyn/untracked.baseline` → `scope.authorized-surface-malformed` CRITICAL. This scope check runs only here (BUILD_GATE), not when VERIFY MECHANICAL reuses this script post-CLEANUP, while owner CLEANUP separately enforces unchanged tracked source and the final untracked baseline. Historical worker CLEANUP retains its existing allowlist.
|
|
55
55
|
5. **Browser** (only when diff touches `*.tsx`, `*.jsx`, `*.vue`, `*.svelte`, `page.*`, `layout.*`, `route.*`, `*.css`, `*.html`): start the dev server and run the repo's existing browser checks, or a minimal curl/HTML check when no browser test harness exists. Each failed check → finding, severity `HIGH`, rule `correctness.browser-flow-failed`.
|
|
56
56
|
|
|
57
57
|
Append all findings; do not stop on the first failure.
|
|
@@ -59,6 +59,7 @@ Append all findings; do not stop on the first failure.
|
|
|
59
59
|
|
|
60
60
|
<output>
|
|
61
61
|
- `.devlyn/build_gate.findings.jsonl` — JSONL stream, one finding per line. Schema: `{id, rule_id, severity, file, line, message, fix_hint, criterion_ref}`.
|
|
62
|
+
- A completed checker preflight rejection writes `spec-verify.results.json` with `preflight_failure` and the original CRITICAL finding's path/digest. Retain that source (default `.devlyn/spec-verify-findings.jsonl`) and append its finding to `.devlyn/build_gate.findings.jsonl`. Earlier gate observations remain bound, but no spec command is claimed to have run. An absent results file still follows the interrupted-observation `BLOCKED` path.
|
|
62
63
|
- `.devlyn/build_gate.log.md` — human-readable gate results with raw output or links to complete raw-output files, using sealed artifacts where available. Retain complete inventories/hash maps in `.devlyn/` files; report their paths, digests and relevant deltas instead of dumping them into the conversation.
|
|
63
64
|
- Report `PASS` if zero CRITICAL/HIGH findings, `FAIL` for genuine product findings, or `BLOCKED:build-env-underprovisioned` only for the evidence-backed capability path above. A capability blocker leaves no product finding. Do not edit `pipeline.state.json` yourself — the orchestrator records it via `state-phase-write.py` from these artifacts.
|
|
64
65
|
</output>
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
# PHASE 4 — CLEANUP (canonical body)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
<role>
|
|
6
|
-
Remove tooling artifacts, dead code added by this diff, and doc references invalidated by this diff. The cleanup is bounded by an allowlist enforced post-spawn.
|
|
7
|
-
</role>
|
|
3
|
+
The selected IMPLEMENT worker performs code/doc cleanup in its existing invocation before its final checks. The owner then uses the CLEANUP span for generated-artifact removal and invariant checks only; no new model call.
|
|
8
4
|
|
|
9
5
|
<input>
|
|
10
|
-
- Cumulative diff
|
|
11
|
-
-
|
|
12
|
-
- `state.phases.cleanup.pre_sha` (the orchestrator captured this before spawn — your post-cleanup diff against this SHA must stay within the allowlist).
|
|
6
|
+
- Cumulative diff, original spec and immutable PLAN authorized surface.
|
|
7
|
+
- CLEANUP `pre_sha`: tracked source and HEAD must remain unchanged during owner checks.
|
|
13
8
|
</input>
|
|
14
9
|
|
|
15
10
|
<allowlist>
|
|
16
|
-
|
|
11
|
+
Within IMPLEMENT and PLAN authorization, you may modify or delete:
|
|
17
12
|
|
|
18
13
|
1. **Tooling artifacts** the spec did not list as deliverables: `test-results/`, `playwright-report/`, `.last-run.json`, coverage HTML output, build artifacts, runtime caches (`__pycache__/`, `*.pyc`, `.cache/`).
|
|
19
14
|
2. **Dead code added by this diff** — symbols (functions, classes, types, exports) introduced by this diff that no other code added by this diff references AND that are not part of the spec's required surface. Pre-existing dead code is out of scope.
|
|
@@ -24,8 +19,7 @@ Files outside this allowlist must not change. Pre-existing tooling leaks (alread
|
|
|
24
19
|
</allowlist>
|
|
25
20
|
|
|
26
21
|
<output>
|
|
27
|
-
-
|
|
28
|
-
- Report your verdict in this reply: `PASS` if changes within allowlist (or no changes needed); `FAIL` if you cannot complete within the allowlist (the orchestrator will revert). Do not edit `pipeline.state.json` yourself — the orchestrator records it via `state-phase-write.py`.
|
|
22
|
+
The owner removes only run-owned untracked/ignored generated artifacts from item 1, preserving deliverables, evidence and recovery inputs. It records PASS when the source checkpoint is unchanged and no cleanup finding remains. A code/doc finding returns through the selected IMPLEMENT repair route and reruns BUILD before fresh VERIFY. Never modify source inside owner CLEANUP or reuse stale checks. Record lifecycle via `state-phase-write.py`.
|
|
29
23
|
</output>
|
|
30
24
|
|
|
31
25
|
<quality_bar>
|
|
@@ -14,6 +14,7 @@ You execute the plan. Constrained design judgment within PLAN's invariants — w
|
|
|
14
14
|
|
|
15
15
|
<output>
|
|
16
16
|
- Code changes implementing every Requirement. Verify with `git diff`.
|
|
17
|
+
- In this same selected worker invocation, remove dead code/comments introduced by this diff and repair references it invalidated, within PLAN authorization and `references/phases/cleanup.md`. Finish before the final checks/checkpoint; no separate cleanup model is dispatched.
|
|
17
18
|
- Tests added or updated for changed behavior. Run the focused development tests needed to establish that behavior; BUILD_GATE and VERIFY own the post-implementation full suite.
|
|
18
19
|
- For every sibling `spec.expected.json.process_evidence[]` item whose `phase` is `implement`, run `python3 "$DEVLYN_SHARED_DIR/process-evidence.py" --devlyn-dir .devlyn run --phase implement --id '<id>'`. The runner must report `expectation_met: true`; cite its manifest path in the phase reply.
|
|
19
20
|
- For each criterion satisfied, set `state.criteria[i].status: "implemented"` with an `evidence` record `{"file": "...", "line": N, "note": "brief"}`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# PHASE 1 — PLAN (canonical body)
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The orchestrator reads this body in its existing context; no PLAN worker is dispatched.
|
|
4
4
|
|
|
5
5
|
<role>
|
|
6
6
|
You translate a spec or generated criteria into a concrete plan: the file list to touch, the risks the implementation must navigate, and a verbatim restatement of what acceptance requires. The plan is the contract IMPLEMENT executes against.
|
|
@@ -20,14 +20,14 @@ Write `.devlyn/plan.md` with three sections:
|
|
|
20
20
|
3. **Acceptance restatement** — verbatim copy of the spec's `## Verification` block (or generated criteria's equivalent). The plan is wrong if any verification command later fails because of a planning oversight.
|
|
21
21
|
4. **Execution phases** — conditional; large work only. Emit this section ONLY when ALL hold: (a) spec frontmatter `complexity: high` (legacy `large`) or `state.complexity == "large"`; (b) the work spans multiple subsystems or more than ~8 files; (c) every phase boundary has at least one runnable gate command. Otherwise emit exactly the three sections above — when in doubt, do not decompose. When emitted: 2-5 `### Phase <k> — <title>` blocks, each with task checkboxes (`- [ ]`), a `gate:` line (1-2 commands, exit-code truth, runnable at that boundary), and the files it owns (a subset of section 1). Definitions are written once here and are the contract; the orchestrator later updates checkboxes as a display mirror only — routing truth lives in `pipeline.state.json`.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Record via `state-phase-write.py`: `PASS` if plan is shippable; `BLOCKED` if spec is internally contradictory or cannot be planned without violating constraints. Never hand-edit lifecycle fields.
|
|
24
24
|
</output>
|
|
25
25
|
|
|
26
26
|
<quality_bar>
|
|
27
27
|
- Scope first, then implementation. Decide what files to touch before deciding how to implement. Files not in the list are off-limits to IMPLEMENT.
|
|
28
28
|
- Tooling artifacts and reporter output are not deliverables unless the spec lists them. Plan to configure tools to emit to gitignored paths.
|
|
29
29
|
- Existing tests are contract. Plan to extend them; do not plan to remove or weaken them.
|
|
30
|
-
- Spec frontmatter is read-only
|
|
30
|
+
- Spec frontmatter is read-only; cleanup cannot broaden the task contract.
|
|
31
31
|
- If a Requirement says "match the literal output X", restate the literal in the plan. Paraphrasing the contract here propagates into IMPLEMENT.
|
|
32
32
|
</quality_bar>
|
|
33
33
|
|