@jesdi/skills 0.0.48 → 0.0.50
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/package.json +1 -1
- package/skills/implement-spec/driver_agents.py +20 -2
- package/skills/implement-spec/driver_report.py +14 -6
- package/skills/implement-spec/driver_run.py +2 -0
- package/skills/implement-spec/tests/test_agents.py +35 -0
- package/skills/implement-spec/tests/test_driver.py +1 -1
- package/skills/implement-spec/tests/test_report.py +2 -1
- package/skills-manifest.json +3 -3
package/package.json
CHANGED
|
@@ -117,14 +117,32 @@ def command(role: str, entry: str, report: str) -> list[str]:
|
|
|
117
117
|
"""The CLI call for one agent; the prompt goes to its stdin."""
|
|
118
118
|
provider, model, effort = split(entry)
|
|
119
119
|
if provider == "openai":
|
|
120
|
-
# No --ephemeral: the rollout on disk is the trace. A read-only agent
|
|
121
|
-
|
|
120
|
+
# No --ephemeral: the rollout on disk is the trace. A read-only agent may not write its report, so -o saves
|
|
121
|
+
# the last message next to it; settle_report picks one. -o on the report itself overwrites a written report.
|
|
122
|
+
tail = ["--sandbox", "read-only", "-o", last_message(report)] if role in READ_ONLY else [
|
|
123
|
+
"--sandbox", "workspace-write"]
|
|
122
124
|
return ["codex", "exec", "--model", model, "-c", f'model_reasoning_effort="{effort}"', *tail]
|
|
123
125
|
# stream-json keeps tool output in the log and ends with a result that has total_cost_usd.
|
|
124
126
|
return ["claude", "-p", "--model", model, "--effort", effort, "--permission-mode", "auto",
|
|
125
127
|
"--output-format", "stream-json", "--verbose"]
|
|
126
128
|
|
|
127
129
|
|
|
130
|
+
def last_message(report: str | Path) -> str:
|
|
131
|
+
"""The file where codex saves the last message of a read-only agent."""
|
|
132
|
+
return f"{report}.last"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def settle_report(report: Path) -> None:
|
|
136
|
+
"""The report is the one the agent wrote when it parses, else the saved last message, when there is one."""
|
|
137
|
+
last = Path(last_message(report))
|
|
138
|
+
if not last.exists():
|
|
139
|
+
return
|
|
140
|
+
with contextlib.suppress(OSError, ValueError):
|
|
141
|
+
json.loads(report.read_text())
|
|
142
|
+
return
|
|
143
|
+
report.write_text(last.read_text())
|
|
144
|
+
|
|
145
|
+
|
|
128
146
|
def render(name: str, values: dict[str, str]) -> str:
|
|
129
147
|
return re.sub(r"\{([a-z_]+)\}", lambda m: values[m.group(1)], (SKILL / "prompts" / f"{name}.md").read_text())
|
|
130
148
|
|
|
@@ -27,9 +27,16 @@ def _git(repo: str, *args: str, end: str = "\n") -> list[str]:
|
|
|
27
27
|
return [line for line in out.split(end) if line]
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
def _squash(text: str) -> str:
|
|
31
|
+
"""Each run of whitespace other than a line break as one space."""
|
|
32
|
+
return re.sub(r"[^\S\n]+", " ", text)
|
|
33
|
+
|
|
34
|
+
|
|
30
35
|
def _seen(line: str, log: str) -> bool:
|
|
31
|
-
# A stream-json log holds tool output as JSON strings, so the escaped
|
|
32
|
-
|
|
36
|
+
# A stream-json log holds tool output as JSON strings, so the escaped forms of the line count too: json.dumps
|
|
37
|
+
# escapes non-ASCII, the claude log keeps it raw. Spacing does not count: pytest pads some lines.
|
|
38
|
+
line, log = _squash(line.strip()), _squash(log)
|
|
39
|
+
return any(form in log for form in (line, json.dumps(line)[1:-1], json.dumps(line, ensure_ascii=False)[1:-1]))
|
|
33
40
|
|
|
34
41
|
|
|
35
42
|
def _shas(report: dict) -> list[str]:
|
|
@@ -89,10 +96,11 @@ def _merged_errors(report: dict, context: dict) -> list[str]:
|
|
|
89
96
|
|
|
90
97
|
def _proof_errors(report: dict, context: dict) -> list[str]:
|
|
91
98
|
"""Every claimed red line (a test-writer `red`, a reviewer `mutations`) is in the agent's log."""
|
|
92
|
-
lines = [r["
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
lines = ([(r["test"], r["line"]) for r in report.get("red", [])]
|
|
100
|
+
+ [(m["test"], m["red"]) for m in report.get("mutations", [])])
|
|
101
|
+
log = Path(context["log"]).read_text(errors="replace") if lines else ""
|
|
102
|
+
errors = [f"{RED}: {test}: {line if len(line) <= 200 else line[:197] + '...'}"
|
|
103
|
+
for test, line in lines if not _seen(line, log)]
|
|
96
104
|
if context.get("full") and "findings" in report and "mutations" not in report:
|
|
97
105
|
errors.append("mutations: required in a full review")
|
|
98
106
|
return errors
|
|
@@ -291,9 +291,11 @@ def _attempts(run: Run, role: str, number: str, slot: str, values: dict[str, str
|
|
|
291
291
|
for _ in range(2):
|
|
292
292
|
prompt.write_text(text)
|
|
293
293
|
report.unlink(missing_ok=True)
|
|
294
|
+
Path(driver_agents.last_message(report)).unlink(missing_ok=True)
|
|
294
295
|
start = time.monotonic()
|
|
295
296
|
code = driver_agents.launch(argv, prompt, log, minutes * 60, slot)
|
|
296
297
|
seconds = time.monotonic() - start
|
|
298
|
+
driver_agents.settle_report(report)
|
|
297
299
|
error, rerun_lines = _rejection(role, code, minutes, report, context, alone, detached)
|
|
298
300
|
_record_attempt(run, role, number, entry, seconds, log, error)
|
|
299
301
|
if not error:
|
|
@@ -211,6 +211,29 @@ def test_reviewing_roles_launch_codex_exec_read_only(env, role):
|
|
|
211
211
|
assert value_after(argv, "--model", "-m") == "gpt-sol"
|
|
212
212
|
|
|
213
213
|
|
|
214
|
+
@pytest.mark.parametrize("writes", ["file", "message"])
|
|
215
|
+
def test_a_read_only_codex_report_is_the_file_it_wrote_else_its_last_message(env, writes):
|
|
216
|
+
# A read-only sandbox can still let the agent write its report; codex then writes the last message to the -o file.
|
|
217
|
+
w = World(env, refactor=True, correctness=["openai/gpt-sol@medium"])
|
|
218
|
+
report = {"status": "DONE", "commits": [], "files": [], "concerns": [], "findings": []}
|
|
219
|
+
(w.tmp / "bin/codex").write_text(f"""#!{sys.executable}
|
|
220
|
+
import re, sys
|
|
221
|
+
from pathlib import Path
|
|
222
|
+
prompt, argv = sys.stdin.read(), sys.argv[1:]
|
|
223
|
+
last = Path(argv[argv.index("-o") + 1])
|
|
224
|
+
if "implement-spec preflight" in prompt:
|
|
225
|
+
last.write_text("OK")
|
|
226
|
+
elif {writes!r} == "file":
|
|
227
|
+
Path(re.search(r"JSON report at (\\S+?)\\.? ", prompt).group(1)).write_text({json.dumps(report)!r})
|
|
228
|
+
last.write_text("Wrote and validated the report.\\n")
|
|
229
|
+
else:
|
|
230
|
+
last.write_text({json.dumps(report)!r})
|
|
231
|
+
""")
|
|
232
|
+
ok(w.run(None, "--only", "correctness", runner=False))
|
|
233
|
+
assert json.loads((w.repo / ".agent/implement/01/correctness-0.json").read_text()) == report
|
|
234
|
+
assert "correctness ran on openai/gpt-sol@medium" in w.ledger() and "report accepted" in w.ledger()
|
|
235
|
+
|
|
236
|
+
|
|
214
237
|
@pytest.mark.parametrize("entry", ["claude-opus-5@medium", "anthropic/claude-opus-5@medium"])
|
|
215
238
|
def test_claude_entry_launches_claude_p_and_bare_means_anthropic(env, entry):
|
|
216
239
|
w = World(env, refactor=True, implementer=[entry])
|
|
@@ -324,6 +347,7 @@ def test_red_line_missing_from_the_log_reruns_once_then_blocks(env, second_match
|
|
|
324
347
|
proc = w.run(plan)
|
|
325
348
|
second = w.calls("test-writer")[1]["prompt"]
|
|
326
349
|
assert "red line not found in log" in second and "red line not found" not in w.calls("test-writer")[0]["prompt"]
|
|
350
|
+
assert f"red line not found in log: tests/test_export.py::test_rows: {RED}" in second
|
|
327
351
|
if second_matches:
|
|
328
352
|
ok(proc)
|
|
329
353
|
assert len(w.calls("implementer")) == 1
|
|
@@ -333,6 +357,17 @@ def test_red_line_missing_from_the_log_reruns_once_then_blocks(env, second_match
|
|
|
333
357
|
assert any("01" in l and "BLOCKED" in l for l in w.ledger().splitlines()), w.ledger()
|
|
334
358
|
|
|
335
359
|
|
|
360
|
+
@pytest.mark.parametrize("claimed, printed", [
|
|
361
|
+
("E assert 0 == 'rows'", "E assert 0 == 'rows'"), # pytest pads a parametrized test's lines
|
|
362
|
+
("AssertionError: '12 UNP\u2212 EL' !~ /Resilience\\s*4\\.3/", # a stream-json log keeps non-ASCII raw
|
|
363
|
+
'"text": "AssertionError: \'12 UNP\u2212 EL\' !~ /Resilience\\\\s*4\\\\.3/"')], ids=["spacing", "non-ascii"])
|
|
364
|
+
def test_red_line_that_differs_from_the_log_only_in_spacing_or_escaping_is_seen(env, claimed, printed):
|
|
365
|
+
w = World(env)
|
|
366
|
+
writer = with_report(tw(0, printed + "\n"), red=[{"test": "tests/test_export.py::test_rows", "line": claimed}])
|
|
367
|
+
ok(w.run({"test-writer": [writer], "implementer": [impl()]}))
|
|
368
|
+
assert len(w.calls("test-writer")) == 1
|
|
369
|
+
|
|
370
|
+
|
|
336
371
|
def test_implementer_report_with_no_new_commit_is_a_violation(env):
|
|
337
372
|
w = World(env)
|
|
338
373
|
nothing = with_report(impl(), commits=[])
|
|
@@ -342,7 +342,7 @@ def test_command_lines_per_provider():
|
|
|
342
342
|
assert driver_agents.command("implementer", "openai/gpt-sol@high", "r.json") == [
|
|
343
343
|
"codex", "exec", "--model", "gpt-sol", "-c", 'model_reasoning_effort="high"', "--sandbox", "workspace-write"]
|
|
344
344
|
assert driver_agents.command("reviewer", "openai/gpt-sol@high", "r.json")[-4:] == [
|
|
345
|
-
"--sandbox", "read-only", "-o", "r.json"]
|
|
345
|
+
"--sandbox", "read-only", "-o", "r.json.last"]
|
|
346
346
|
assert driver_agents.command("reviewer", "opus@low", "r.json")[:6] == [
|
|
347
347
|
"claude", "-p", "--model", "opus", "--effort", "low"]
|
|
348
348
|
|
|
@@ -146,7 +146,8 @@ def test_red_line_absent_from_the_log_is_a_violation(world):
|
|
|
146
146
|
shas, check, _ = world
|
|
147
147
|
assert check("test-writer", writer(shas), start=shas["base"]) == []
|
|
148
148
|
report = writer(shas, red=[{"test": "t", "line": "FAILED tests/test_export.py::test_rows - KeyError"}])
|
|
149
|
-
assert
|
|
149
|
+
assert check("test-writer", report, start=shas["base"]) == [
|
|
150
|
+
"red line not found in log: t: FAILED tests/test_export.py::test_rows - KeyError"]
|
|
150
151
|
|
|
151
152
|
|
|
152
153
|
def test_reviewer_fields_and_mutation_proofs(world):
|
package/skills-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"generatedAt": "2026-10-19T09:42:49.911Z",
|
|
4
4
|
"skills": [
|
|
5
5
|
{
|
|
6
6
|
"name": "backlog",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
48
|
"name": "implement-spec",
|
|
49
|
-
"version": "0.1.
|
|
50
|
-
"hash": "sha256-
|
|
49
|
+
"version": "0.1.26",
|
|
50
|
+
"hash": "sha256-40637f9172d94edb3ef009f91f58e27f7cd400ed15935c5a62654822a57208c5",
|
|
51
51
|
"description": "Implement a specification in code."
|
|
52
52
|
},
|
|
53
53
|
{
|