devlyn-cli 4.3.0 → 4.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/skill-history.json +3 -0
- package/config/skills/_shared/harness-store.py +2 -1
- package/config/skills/_shared/task-completion.md +6 -3
- package/config/skills/_shared/warm-build-dirs.py +97 -2
- package/config/skills/devlyn-ideate/scripts/queue.py +10 -1
- package/harness/AGENTS.md +37 -0
- package/harness/CLAUDE.md +37 -0
- package/native/claude/.claude-plugin/plugin.json +6 -0
- package/native/claude/skills/_shared/.ruff.toml +2 -0
- package/native/claude/skills/_shared/adapters/claude.md +15 -0
- package/native/claude/skills/_shared/adapters/codex.md +14 -0
- package/native/claude/skills/_shared/adapters/omp.md +6 -0
- package/native/claude/skills/_shared/codex-monitored.sh +263 -0
- package/native/claude/skills/_shared/engine-doctor.sh +70 -0
- package/native/claude/skills/_shared/engine-preflight.md +29 -0
- package/native/claude/skills/_shared/expected-contract.py +242 -0
- package/native/claude/skills/_shared/expected.schema.json +120 -0
- package/native/claude/skills/_shared/harness-store.py +202 -0
- package/native/claude/skills/_shared/invocation-receipt.py +499 -0
- package/native/claude/skills/_shared/platform-support.py +395 -0
- package/native/claude/skills/_shared/role-config.py +247 -0
- package/native/claude/skills/_shared/runtime-principles.md +35 -0
- package/native/claude/skills/_shared/task-complete.py +2865 -0
- package/native/claude/skills/_shared/task-completion.md +249 -0
- package/native/claude/skills/_shared/warm-build-dirs.py +1065 -0
- package/native/claude/skills/devlyn-engines/SKILL.md +63 -0
- package/native/claude/skills/devlyn-ideate/SKILL.md +132 -0
- package/native/claude/skills/devlyn-ideate/references/elicitation.md +29 -0
- package/native/claude/skills/devlyn-ideate/references/loop.md +123 -0
- package/native/claude/skills/devlyn-ideate/references/package-format.md +128 -0
- package/native/claude/skills/devlyn-ideate/references/plan-add.md +12 -0
- package/native/claude/skills/devlyn-ideate/scripts/acceptance.py +882 -0
- package/native/claude/skills/devlyn-ideate/scripts/queue.py +2191 -0
- package/native/codex/.agents/plugins/marketplace.json +12 -0
- package/native/codex/plugins/devlyn/.codex-plugin/plugin.json +6 -0
- package/native/codex/plugins/devlyn/skills/_shared/.ruff.toml +2 -0
- package/native/codex/plugins/devlyn/skills/_shared/adapters/claude.md +15 -0
- package/native/codex/plugins/devlyn/skills/_shared/adapters/codex.md +14 -0
- package/native/codex/plugins/devlyn/skills/_shared/adapters/omp.md +6 -0
- package/native/codex/plugins/devlyn/skills/_shared/codex-monitored.sh +263 -0
- package/native/codex/plugins/devlyn/skills/_shared/engine-doctor.sh +70 -0
- package/native/codex/plugins/devlyn/skills/_shared/engine-preflight.md +29 -0
- package/native/codex/plugins/devlyn/skills/_shared/expected-contract.py +242 -0
- package/native/codex/plugins/devlyn/skills/_shared/expected.schema.json +120 -0
- package/native/codex/plugins/devlyn/skills/_shared/harness-store.py +202 -0
- package/native/codex/plugins/devlyn/skills/_shared/invocation-receipt.py +499 -0
- package/native/codex/plugins/devlyn/skills/_shared/platform-support.py +395 -0
- package/native/codex/plugins/devlyn/skills/_shared/role-config.py +247 -0
- package/native/codex/plugins/devlyn/skills/_shared/runtime-principles.md +35 -0
- package/native/codex/plugins/devlyn/skills/_shared/task-complete.py +2865 -0
- package/native/codex/plugins/devlyn/skills/_shared/task-completion.md +249 -0
- package/native/codex/plugins/devlyn/skills/_shared/warm-build-dirs.py +1065 -0
- package/native/codex/plugins/devlyn/skills/devlyn-engines/SKILL.md +63 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/SKILL.md +132 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/elicitation.md +29 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/loop.md +123 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/package-format.md +128 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/plan-add.md +12 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/scripts/acceptance.py +882 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/scripts/queue.py +2191 -0
- package/package.json +1 -1
- package/release.json +1630 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/fysoul17/devlyn-cli/config/skills/_shared/expected.schema.json",
|
|
4
|
+
"title": "spec.expected.json — mechanical acceptance contract",
|
|
5
|
+
"description": "Load-bearing LLM-agnostic decoupler for the devlyn-cli harness. Defines the machine-readable acceptance criteria a spec ships alongside spec.md. Stable across model upgrades — when Opus 5 / GPT-6 / Qwen / Gemini land, this schema does not move; only the per-model adapter files in _shared/adapters/ do.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"verification_commands": {
|
|
10
|
+
"type": "array",
|
|
11
|
+
"description": "Each command must pass on the task's committed source. At least one entry is required when the spec has any observable runtime check (CLI, test command, HTTP request).",
|
|
12
|
+
"items": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"additionalProperties": false,
|
|
15
|
+
"oneOf": [
|
|
16
|
+
{ "required": ["cmd"], "not": { "required": ["argv"] } },
|
|
17
|
+
{ "required": ["argv"], "not": { "required": ["cmd"] } }
|
|
18
|
+
],
|
|
19
|
+
"properties": {
|
|
20
|
+
"cmd": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Shell command, executed via `subprocess.run(..., shell=True)` from the build's working directory.",
|
|
23
|
+
"minLength": 1
|
|
24
|
+
},
|
|
25
|
+
"argv": {
|
|
26
|
+
"type": "array",
|
|
27
|
+
"minItems": 1,
|
|
28
|
+
"items": { "type": "string" },
|
|
29
|
+
"description": "Exact argv executed without a shell; argv[0] must be non-empty. Preferred for generated and Windows contracts."
|
|
30
|
+
},
|
|
31
|
+
"exit_code": {
|
|
32
|
+
"type": "integer",
|
|
33
|
+
"description": "Required exit code. Default 0 if omitted.",
|
|
34
|
+
"default": 0
|
|
35
|
+
},
|
|
36
|
+
"timeout_sec": {
|
|
37
|
+
"type": "integer",
|
|
38
|
+
"minimum": 1,
|
|
39
|
+
"maximum": 600,
|
|
40
|
+
"description": "Per-command timeout in seconds. Default 60 if omitted; maximum 600.",
|
|
41
|
+
"default": 60
|
|
42
|
+
},
|
|
43
|
+
"stdout_contains": {
|
|
44
|
+
"type": "array",
|
|
45
|
+
"description": "Each substring must appear verbatim in (stdout + stderr) for pass.",
|
|
46
|
+
"items": { "type": "string", "minLength": 1 },
|
|
47
|
+
"default": []
|
|
48
|
+
},
|
|
49
|
+
"stdout_not_contains": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"description": "None of these substrings may appear in (stdout + stderr) for pass.",
|
|
52
|
+
"items": { "type": "string", "minLength": 1 },
|
|
53
|
+
"default": []
|
|
54
|
+
},
|
|
55
|
+
"contract_refs": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"description": "Requirements this command verifies. Ideate loop tasks name requirement IDs (R1, R2, ...) from the task Requirements and require at least one.",
|
|
58
|
+
"items": { "type": "string", "minLength": 1 },
|
|
59
|
+
"default": []
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"forbidden_patterns": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"description": "Regex patterns scanned across the task's diff.",
|
|
67
|
+
"items": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"additionalProperties": false,
|
|
70
|
+
"required": ["pattern", "description", "severity"],
|
|
71
|
+
"properties": {
|
|
72
|
+
"pattern": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"description": "Python re.search-compatible regex. Anchored implicitly by the surrounding regex, NOT by ^ / $ unless intended.",
|
|
75
|
+
"minLength": 1
|
|
76
|
+
},
|
|
77
|
+
"description": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"description": "Human-readable explanation of what the pattern catches and why it's forbidden.",
|
|
80
|
+
"minLength": 1
|
|
81
|
+
},
|
|
82
|
+
"files": {
|
|
83
|
+
"type": "array",
|
|
84
|
+
"description": "Optional allow-list of files (substrings of diff --git lines). When present, scan is sliced to hunks touching only these files.",
|
|
85
|
+
"items": { "type": "string", "minLength": 1 },
|
|
86
|
+
"default": []
|
|
87
|
+
},
|
|
88
|
+
"severity": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"enum": ["disqualifier", "warning"],
|
|
91
|
+
"description": "disqualifier blocks acceptance; warning is recorded."
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"required_files": {
|
|
97
|
+
"type": "array",
|
|
98
|
+
"description": "Files specified by literal paths that must exist in the committed source; globs are not expanded.",
|
|
99
|
+
"items": { "type": "string", "minLength": 1 },
|
|
100
|
+
"default": []
|
|
101
|
+
},
|
|
102
|
+
"forbidden_files": {
|
|
103
|
+
"type": "array",
|
|
104
|
+
"description": "Exact Git-relative paths (forward slashes, no leading ./) that must NOT appear in the diff, including both rename names; globs and directory prefixes are not expanded.",
|
|
105
|
+
"items": { "type": "string", "minLength": 1 },
|
|
106
|
+
"default": []
|
|
107
|
+
},
|
|
108
|
+
"max_deps_added": {
|
|
109
|
+
"type": "integer",
|
|
110
|
+
"description": "Hard cap on package names the root package.json's dependencies and devDependencies gain over the task inputs; a move between them adds none.",
|
|
111
|
+
"minimum": 0,
|
|
112
|
+
"default": 0
|
|
113
|
+
},
|
|
114
|
+
"pure_design": {
|
|
115
|
+
"type": "boolean",
|
|
116
|
+
"description": "Declares that every Requirement is pure-design (no observable runtime check). Lets verification_commands stay empty without failing shape validation. Contradictory (and rejected) when true alongside a non-empty verification_commands.",
|
|
117
|
+
"default": false
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Release identity and queue resource checks. Readers never write or create locks.
|
|
2
|
+
|
|
3
|
+
The release verifier is CLI-1's bin/release.js, not a second manifest verifier.
|
|
4
|
+
A Codex projection has .devlyn-harness-projection.json at its plugin root:
|
|
5
|
+
{schemaVersion: 1, release: ReleaseRef, source: "native/codex/plugins/devlyn"}.
|
|
6
|
+
Its complete regular-file tree (except that marker) must match that release subtree.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import re
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
PROJECTION = ".devlyn-harness-projection.json"
|
|
19
|
+
CODEX_SOURCE = "native/codex/plugins/devlyn"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class HarnessError(Exception):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def require(condition, message):
|
|
27
|
+
if not condition:
|
|
28
|
+
raise HarnessError(message)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def read_json(path):
|
|
32
|
+
try:
|
|
33
|
+
return json.loads(Path(path).read_bytes())
|
|
34
|
+
except (OSError, ValueError) as exc:
|
|
35
|
+
raise HarnessError(f"{path}: {exc}") from exc
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def file_identity(path):
|
|
39
|
+
path = Path(path)
|
|
40
|
+
info = path.lstat()
|
|
41
|
+
require(path.is_file() and not path.is_symlink() and info.st_nlink == 1, f"not a regular resource: {path}")
|
|
42
|
+
return {"path": str(path), "sha256": hashlib.sha256(path.read_bytes()).hexdigest()}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def release_ref(value):
|
|
46
|
+
require(isinstance(value, dict) and set(value) == {"package", "version", "digest", "root"}
|
|
47
|
+
and value["package"] == "devlyn-cli"
|
|
48
|
+
and all(isinstance(value[k], str) and value[k] and "\0" not in value[k] for k in ("version", "digest", "root"))
|
|
49
|
+
and re.fullmatch(r"[a-f0-9]{64}", value["digest"]) and Path(value["root"]).is_absolute(), "malformed release reference")
|
|
50
|
+
return value
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def verify_release(root):
|
|
54
|
+
root = Path(root)
|
|
55
|
+
# As harness-status verifies it: an npm-installed package keeps npm's own .bin shims, which are checked.
|
|
56
|
+
script = "const r=require(process.argv[1]);r.runJson(()=>r.verifyRelease(process.argv[2],{npmInstall:true}));"
|
|
57
|
+
result = subprocess.run(["node", "-e", script, str(root / "bin/release.js"), str(root)], capture_output=True, text=True, encoding="utf-8")
|
|
58
|
+
require(result.returncode == 0, f"release verification failed: {root}: {result.stderr.strip()}")
|
|
59
|
+
try:
|
|
60
|
+
release = release_ref(json.loads(result.stdout)["data"]["release"])
|
|
61
|
+
except (KeyError, ValueError, TypeError) as exc:
|
|
62
|
+
raise HarnessError(f"invalid release verifier result: {root}: {exc}") from exc
|
|
63
|
+
manifest = read_json(root / "release.json")
|
|
64
|
+
defaults = file_identity(root / manifest["principles"]["defaults"])
|
|
65
|
+
return {"schemaVersion": 1, "release": release, "defaults": defaults}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def walk_error(error):
|
|
69
|
+
raise error # os.walk otherwise silently skips unreadable projection directories.
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def resolve(skills):
|
|
73
|
+
"""Resolve only this invoking skill's real ancestry; never search another install."""
|
|
74
|
+
skill = (Path(skills) / "devlyn-ideate").resolve()
|
|
75
|
+
for parent in (skill, *skill.parents):
|
|
76
|
+
marker = parent / PROJECTION
|
|
77
|
+
if marker.exists() or marker.is_symlink():
|
|
78
|
+
file_identity(marker)
|
|
79
|
+
projection = read_json(marker)
|
|
80
|
+
require(isinstance(projection, dict) and set(projection) == {"schemaVersion", "release", "source"}
|
|
81
|
+
and type(projection["schemaVersion"]) is int and projection["schemaVersion"] == 1
|
|
82
|
+
and projection["source"] == CODEX_SOURCE, f"malformed projection: {marker}")
|
|
83
|
+
ref = release_ref(projection["release"])
|
|
84
|
+
binding = verify_release(ref["root"])
|
|
85
|
+
require(binding["release"] == ref, f"projection release changed: {marker}")
|
|
86
|
+
source = Path(ref["root"]) / CODEX_SOURCE
|
|
87
|
+
require(skill == parent / "skills/devlyn-ideate", f"unexpected projection skill: {skill}")
|
|
88
|
+
expected = {p.relative_to(source) for p in source.rglob("*") if p.is_file()}
|
|
89
|
+
actual = set()
|
|
90
|
+
for directory, dirs, files in os.walk(parent, onerror=walk_error):
|
|
91
|
+
for name in dirs:
|
|
92
|
+
require(not (Path(directory) / name).is_symlink(), f"projection directory link: {directory}/{name}")
|
|
93
|
+
for name in files:
|
|
94
|
+
file = Path(directory) / name
|
|
95
|
+
if file == marker:
|
|
96
|
+
continue
|
|
97
|
+
rel = file.relative_to(parent)
|
|
98
|
+
actual.add(rel)
|
|
99
|
+
require(rel in expected and file_identity(file)["sha256"] == file_identity(source / rel)["sha256"]
|
|
100
|
+
and (os.name == "nt" or bool(file.stat().st_mode & 0o111) == bool((source / rel).stat().st_mode & 0o111)),
|
|
101
|
+
f"projection resource changed: {file}")
|
|
102
|
+
require(actual == expected, f"projection file list changed: {parent}")
|
|
103
|
+
return binding
|
|
104
|
+
manifest = parent / "release.json"
|
|
105
|
+
if (manifest.exists() or manifest.is_symlink()) and skill in [parent / p / "devlyn-ideate" for p in
|
|
106
|
+
("config/skills", "native/claude/skills", CODEX_SOURCE + "/skills")]:
|
|
107
|
+
return verify_release(parent)
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def read_binding(common):
|
|
112
|
+
path = Path(common) / "devlyn-loops/harness.json"
|
|
113
|
+
if not path.exists() and not path.is_symlink():
|
|
114
|
+
return None
|
|
115
|
+
file_identity(path)
|
|
116
|
+
binding = read_json(path)
|
|
117
|
+
require(isinstance(binding, dict) and set(binding) == {"schemaVersion", "release", "defaults"}
|
|
118
|
+
and type(binding["schemaVersion"]) is int and binding["schemaVersion"] == 1, f"incompatible queue binding: {path}")
|
|
119
|
+
release_ref(binding["release"])
|
|
120
|
+
resource(binding["defaults"])
|
|
121
|
+
return binding
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def resource(value):
|
|
125
|
+
require(isinstance(value, dict) and set(value) == {"path", "sha256"}
|
|
126
|
+
and isinstance(value["path"], str) and "\0" not in value["path"] and Path(value["path"]).is_absolute()
|
|
127
|
+
and isinstance(value["sha256"], str) and re.fullmatch(r"[a-f0-9]{64}", value["sha256"]), "malformed resource binding")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def recovery_argv(binding, argv):
|
|
131
|
+
return [sys.executable, str(Path(binding["release"]["root"]) / "config/skills/devlyn-ideate/scripts/queue.py"), *argv]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def check_binding(binding, invoking):
|
|
135
|
+
if binding is None:
|
|
136
|
+
return
|
|
137
|
+
require(invoking is not None and binding["release"] == invoking["release"], "queue-release-mismatch")
|
|
138
|
+
require(binding == invoking, "queue defaults changed; restore the bound release")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def packet_schema(packet):
|
|
142
|
+
require(isinstance(packet, dict), "task packet must be an object")
|
|
143
|
+
version = packet.get("schema_version", 1)
|
|
144
|
+
require(type(version) is int and version in (1, 2), f"incompatible task packet version: {version!r}")
|
|
145
|
+
if version == 1:
|
|
146
|
+
return
|
|
147
|
+
require("harness" in packet and isinstance(packet.get("methodology"), dict), "malformed packet v2 harness/methodology")
|
|
148
|
+
methodology = packet["methodology"]
|
|
149
|
+
require(set(methodology) == {"defaults", "projectInstructions"} and isinstance(methodology["projectInstructions"], list),
|
|
150
|
+
"malformed packet v2 methodology")
|
|
151
|
+
for entry in methodology["projectInstructions"]:
|
|
152
|
+
resource(entry)
|
|
153
|
+
if packet["harness"] is None:
|
|
154
|
+
defaults = methodology["defaults"]
|
|
155
|
+
require(packet.get("workerPrompt") is None and (defaults is None or (isinstance(defaults, dict) and defaults.get("source") == "skill")),
|
|
156
|
+
"legacy packet has release resources")
|
|
157
|
+
if defaults is not None:
|
|
158
|
+
resource({k: v for k, v in defaults.items() if k != "source"})
|
|
159
|
+
return
|
|
160
|
+
release_ref(packet["harness"])
|
|
161
|
+
defaults = methodology["defaults"]
|
|
162
|
+
require(isinstance(defaults, dict) and defaults.get("source") == "release", "packet has no verified release defaults source")
|
|
163
|
+
resource({k: v for k, v in defaults.items() if k != "source"})
|
|
164
|
+
resource(packet.get("workerPrompt"))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def verify_packet(packet, path, skills):
|
|
168
|
+
packet_schema(packet)
|
|
169
|
+
if packet.get("schema_version", 1) == 1:
|
|
170
|
+
return # Preserve v1 execution without upgrading its resources.
|
|
171
|
+
receipt = packet.get("receipt")
|
|
172
|
+
require(isinstance(receipt, str) and "\0" not in receipt and Path(receipt).is_absolute()
|
|
173
|
+
and Path(receipt).name == "receipt.json" and Path(receipt).parent.parent.name == "devlyn-completion"
|
|
174
|
+
and Path(receipt) == Path(path).with_name("receipt.json"),
|
|
175
|
+
"malformed packet receipt path")
|
|
176
|
+
binding = read_binding(Path(receipt).parent.parent.parent)
|
|
177
|
+
if packet["harness"] is None:
|
|
178
|
+
require(binding is None, "bound task packet has no harness/defaults source")
|
|
179
|
+
# An unbound installation's own defaults change with its normal updates: never gate acceptance or delivery on
|
|
180
|
+
# their bytes (queue.py checks only their presence, before a spawn).
|
|
181
|
+
return
|
|
182
|
+
require(binding is not None, "packet queue binding is missing; restore harness.json")
|
|
183
|
+
invoking = resolve(skills)
|
|
184
|
+
ref = packet["harness"]
|
|
185
|
+
require(invoking is not None and invoking["release"] == ref,
|
|
186
|
+
f"packet-release-mismatch; use {Path(ref['root']) / 'config/skills/devlyn-ideate/scripts/queue.py'}")
|
|
187
|
+
check_binding(binding, invoking)
|
|
188
|
+
defaults = {"source": "release", **binding["defaults"]}
|
|
189
|
+
require(packet["methodology"]["defaults"] == defaults, "packet defaults differ from queue binding")
|
|
190
|
+
expected_runner = [str(Path(ref["root"]) / "config/skills/devlyn-ideate/scripts/acceptance.py"),
|
|
191
|
+
"run", "--packet", str(path)]
|
|
192
|
+
require(isinstance(packet.get("runner"), list) and packet["runner"][1:] == expected_runner, "packet runner differs from bound release")
|
|
193
|
+
prompt = packet["workerPrompt"]
|
|
194
|
+
require(prompt["path"] == str(Path(path).parent / "worker-prompt.md") and file_identity(prompt["path"]) == prompt,
|
|
195
|
+
"worker prompt changed or missing; restore the bound resource")
|
|
196
|
+
for entry in packet["methodology"]["projectInstructions"]:
|
|
197
|
+
name = Path(entry["path"]).name
|
|
198
|
+
require(name in ("CLAUDE.md", "AGENTS.md") and entry["path"] == str(Path(packet["worktree"]) / name),
|
|
199
|
+
"project instruction path escapes packet worktree")
|
|
200
|
+
committed = subprocess.run(["git", "-C", packet["worktree"], "cat-file", "blob", f"{packet['inputs_sha']}:{name}"], capture_output=True)
|
|
201
|
+
require(committed.returncode == 0 and hashlib.sha256(committed.stdout).hexdigest() == entry["sha256"],
|
|
202
|
+
f"project instruction differs from inputs commit: {name}")
|