@jesdi/skills 0.0.34 → 0.0.36
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/ARCHITECTURE.md +2 -0
- package/skills/implement-spec/README.md +6 -4
- package/skills/implement-spec/driver_fixloop.py +34 -10
- package/skills/implement-spec/driver_run.py +8 -2
- package/skills/implement-spec/prompts/correctness.md +3 -2
- package/skills/implement-spec/prompts/fixer.md +1 -0
- package/skills/implement-spec/prompts/reviewer.md +3 -3
- package/skills/implement-spec/tests/test_fixloop.py +54 -0
- package/skills/implement-spec/tests/test_walk.py +2 -1
- package/skills-manifest.json +3 -3
package/package.json
CHANGED
|
@@ -107,6 +107,8 @@ and do not know about the run, except `driver_run.py`, which the three deciding
|
|
|
107
107
|
A timeout doubles the time of the rerun.
|
|
108
108
|
- Fix rounds stop at 4, and round 3 goes to the next implementer entry. The correctness pass never runs on the
|
|
109
109
|
provider of the implementer.
|
|
110
|
+
- A red gate on a fixer's commit is a finding of the next round (`driver_fixloop._gate`), so it blocks only after
|
|
111
|
+
round 4.
|
|
110
112
|
|
|
111
113
|
### Questions
|
|
112
114
|
|
|
@@ -82,7 +82,7 @@ Everything is under `.agent/`, which is never committed.
|
|
|
82
82
|
| `.agent/implement/<NN>/` | One folder for each ticket. |
|
|
83
83
|
| `.agent/implement/<NN>/<role>-<K>.json`, `.log`, `.prompt.md` | The report, the log and the prompt of one agent. K is the fix round. |
|
|
84
84
|
| `.agent/implement/<NN>/gate-<sha7>.log` | One gate record: start, commit, status, command, output, exit, the state after the command, end. |
|
|
85
|
-
| `.agent/implement/<NN>/findings-<K>.json` | The merged findings of the review after fix round K. |
|
|
85
|
+
| `.agent/implement/<NN>/findings-<K>.json` | The merged findings of the review after fix round K, or the red gate of round K. |
|
|
86
86
|
| `.agent/implement/final/` | The same files for the end of run, and `pr-body.md`: the pr-writer's body, then the facts of the run. |
|
|
87
87
|
| `.agent/implement/pycache/` | Compiled Python of the gate, kept out of the slots. |
|
|
88
88
|
| `.agent/implement-review.html` | The page with the open questions. |
|
|
@@ -119,6 +119,8 @@ Rules for each agent:
|
|
|
119
119
|
|
|
120
120
|
A fix round is one **fixer** (`prompts/fixer.md`), then the gate on the new tip, then a re-review of the range of the fix. A mutation proof is repeated only when the fix changed its production line.
|
|
121
121
|
|
|
122
|
+
A red gate on the commit of a fixer is a critical finding with source `gate`. Its file is the gate record, and the record holds the output. No review runs on that tip, and the next round starts. The re-review after that round covers each fix since the last review and repeats each proof that one of them made due. A red gate after round 4 blocks the ticket, as does a red gate on the commit of the implementer or of a fixture correction.
|
|
123
|
+
|
|
122
124
|
- Rounds 1 and 2 run on the first entry of the implementer list.
|
|
123
125
|
- Rounds 3 and 4 run on the second entry of the implementer list. When the list has one entry, they stay on it and the ledger says so.
|
|
124
126
|
- There is no ruling at round 2.
|
|
@@ -172,8 +174,8 @@ Every report has `status` (`DONE`, `DONE_WITH_CONCERNS`, `NEEDS_CONTEXT`, `BLOCK
|
|
|
172
174
|
|---|---|---|---|
|
|
173
175
|
| test-writer | `prompts/test-writer.md` | `locked` (paths), `red` (`test`, `line`) | With DONE: one commit, one locked file and one red line or more. Each locked path is in a commit that the role made. Each `line` is in the log. A locked file of a merged ticket that the role changed is in `locked`. |
|
|
174
176
|
| implementer | `prompts/implementer.md` | `green` | With DONE: one commit or more. |
|
|
175
|
-
| reviewer | `prompts/reviewer.md` | `findings` (`severity`, `file`, `line`, `title`, `evidence`, `kind`), `mutations` (`test`, `file`, `line`, `red`) | In the first review of a tip: `mutations` is required, with a proof for each locked test file. Each `red` is in the log. |
|
|
176
|
-
| correctness | `prompts/correctness.md` | the same as the reviewer | `mutations` is optional. |
|
|
177
|
+
| reviewer | `prompts/reviewer.md` | `findings` (`severity`, `file`, `line`, `title`, `evidence`, `kind`), `mutations` (`test`, `file`, `line`, `red`); `commits` is empty | In the first review of a tip: `mutations` is required, with a proof for each locked test file (`test` is `<locked file>::<test name>`). Each `red` is in the log. |
|
|
178
|
+
| correctness | `prompts/correctness.md` | the same as the reviewer; `commits` is empty | `mutations` is optional. |
|
|
177
179
|
| fixer | `prompts/fixer.md` | `green`, `ticket` | With DONE: one commit or more. |
|
|
178
180
|
| final-reviewer | `prompts/final-reviewer.md` | `findings` | None. |
|
|
179
181
|
| pr-writer | `prompts/pr-writer.md` | `body`; `commits` is empty | None. |
|
|
@@ -212,6 +214,6 @@ The driver enforces the mutation proof for each locked test file, not for each l
|
|
|
212
214
|
|
|
213
215
|
- **A `codex` agent cannot commit.** A role that commits (test-writer, implementer, fixer, conflict) on an `openai` entry runs with `--sandbox workspace-write`, and that sandbox cannot run `git commit`. In the default `models.json` this is the second implementer entry, so fix rounds 3 and 4 fail. What to do: give a `--models` file with no `openai` entry in the `implementer`, `test-writer`, `fixer` and `conflict` lists.
|
|
214
216
|
- **A `codex` reviewer cannot make a mutation proof.** A reviewer on an `openai` entry runs with `--sandbox read-only` and cannot make the temporary edit. The default `models.json` has such an entry first for the reviewer, and the first review needs the proofs. What to do: give a `--models` file whose reviewer list starts with an `anthropic` entry.
|
|
215
|
-
- **The correctness pass reads a tree that the reviewer changes.** The two agents share one slot, and the reviewer makes temporary edits. `prompts/correctness.md` tells the pass to read from git at the reviewed commit and to run no tests. That
|
|
217
|
+
- **The correctness pass reads a tree that the reviewer changes.** The two agents share one slot, and the reviewer makes temporary edits. `prompts/correctness.md` tells the pass to read from git at the reviewed commit and to run no tests. It reads only the spec, the ticket and the findings file from the working tree, because git can lack them: `.agent/` is ignored. That rule is the only guard: the driver cannot enforce it, so a pass that ignores it can report a false finding and start a fix round. What to do: when a correctness finding looks wrong, compare it with `git show <reviewed commit>:<file>` before you rule on it.
|
|
216
218
|
- **A ticket that is not merged stops the end of run.** While one ticket is BLOCKED, there is no tidy and no final review of the merged tickets.
|
|
217
219
|
- **A published PR branch keeps its history.** See "End of run".
|
|
@@ -30,8 +30,9 @@ class Job:
|
|
|
30
30
|
tip: str
|
|
31
31
|
previous: str = "" # the tip before the last fix; "" makes the next review a full one
|
|
32
32
|
proofs: set[tuple[str, int]] = field(default_factory=set) # mutation proofs so far, at `tip`
|
|
33
|
-
again:
|
|
33
|
+
again: set[tuple[str, int]] = field(default_factory=set) # each proof at `tip` that a fix made due again
|
|
34
34
|
corrected: bool = False # the last step was a fixture correction
|
|
35
|
+
red: bool = False # the gate of `tip` is red, so no review saw it
|
|
35
36
|
|
|
36
37
|
def pointers(self, review: int) -> dict[str, str]:
|
|
37
38
|
"""The values of a prompt; `review` is the round whose findings the agent reads."""
|
|
@@ -39,7 +40,7 @@ class Job:
|
|
|
39
40
|
findings = os.path.relpath(OUT / self.ticket.number / f"findings-{review}.json", self.slot)
|
|
40
41
|
return {**self.values, "start_sha": self.tip, "from_sha": self.previous, "round": str(self.round),
|
|
41
42
|
"locked": " ".join(entry["locked"]), "test_writer_sha": str(entry["test_writer_sha"] or ""),
|
|
42
|
-
"findings": findings, "proofs": " ".join(self.again)}
|
|
43
|
+
"findings": findings, "proofs": " ".join(f"{file}:{line}" for file, line in sorted(self.again))}
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
def entry(models: dict[str, list[str]], round: int) -> str:
|
|
@@ -129,12 +130,16 @@ def _fix(job: Job) -> None:
|
|
|
129
130
|
with during(f"fix round {job.round}"):
|
|
130
131
|
run.agent("fixer", number, job.slot, job.pointers(job.round - 1), entry=on, fix_round=job.round)
|
|
131
132
|
before, job.tip = job.tip, driver_slots.head(job.slot)
|
|
132
|
-
# No review has seen a fixture correction, so the review after a fix on a corrected tip is a full one.
|
|
133
|
-
job.previous, job.corrected = "" if job.corrected else before, False
|
|
134
133
|
run.state.ticket(number, tip=job.tip)
|
|
135
134
|
followed = driver_review.follow(job.slot, before, job.tip, job.proofs)
|
|
136
|
-
|
|
137
|
-
job.
|
|
135
|
+
due = {proof for proof, changed in followed.items() if changed}
|
|
136
|
+
if job.red:
|
|
137
|
+
# No review saw `before`: the next review covers the fix before it too, and its due proofs stay due.
|
|
138
|
+
due |= set(driver_review.follow(job.slot, before, job.tip, job.again))
|
|
139
|
+
else:
|
|
140
|
+
# No review has seen a fixture correction, so the review after a fix on a corrected tip is a full one.
|
|
141
|
+
job.previous = "" if job.corrected else before
|
|
142
|
+
job.corrected, job.proofs, job.again = False, set(followed), due
|
|
138
143
|
|
|
139
144
|
|
|
140
145
|
def _act_on(job: Job, found: list[dict]) -> bool:
|
|
@@ -157,14 +162,33 @@ def _act_on(job: Job, found: list[dict]) -> bool:
|
|
|
157
162
|
return False
|
|
158
163
|
|
|
159
164
|
|
|
165
|
+
def _gate(job: Job) -> list[dict]:
|
|
166
|
+
"""Gate the tip. A red gate on a fixer's commit before round 4 is a finding for the next round; any other red
|
|
167
|
+
gate blocks. Returns the findings: the red gate, or none."""
|
|
168
|
+
run, number = job.run, job.ticket.number
|
|
169
|
+
job.red = False
|
|
170
|
+
with during("the gate"):
|
|
171
|
+
if not 0 < job.round < ROUNDS or job.corrected:
|
|
172
|
+
run.gate(number, job.slot, job.tip)
|
|
173
|
+
return []
|
|
174
|
+
record = run.red_gate(number, job.slot, job.tip)
|
|
175
|
+
if not record:
|
|
176
|
+
return []
|
|
177
|
+
job.red, path = True, os.path.relpath(record, job.slot)
|
|
178
|
+
found = [{"severity": "critical", "file": path, "line": 0, "title": f"the gate is red on {job.tip[:7]}",
|
|
179
|
+
"evidence": f"the output of the gate in {path}", "kind": "code", "source": "gate"}]
|
|
180
|
+
(OUT / number / f"findings-{job.round}.json").write_text(json.dumps(found, indent=1) + "\n")
|
|
181
|
+
return _log_findings(job, found)
|
|
182
|
+
|
|
183
|
+
|
|
160
184
|
def run(ticket: Ticket, slot: str, run: Run, values: dict[str, str]) -> None:
|
|
161
185
|
"""Gate, review and fix the ticket tip until a review leaves no finding open."""
|
|
162
186
|
state = run.state.read()["tickets"][ticket.number]
|
|
163
187
|
job = Job(ticket, slot, run, values, state["round"], state["tip"])
|
|
164
188
|
while True:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
189
|
+
found = _gate(job)
|
|
190
|
+
if not found:
|
|
191
|
+
with during("the review"):
|
|
192
|
+
found = _review(job)
|
|
169
193
|
if _act_on(job, found):
|
|
170
194
|
return
|
|
@@ -129,8 +129,13 @@ class Run:
|
|
|
129
129
|
|
|
130
130
|
def gate(self, number: str, slot: str, commit: str) -> None:
|
|
131
131
|
"""Gate `commit` in the slot, unless a green record names it already."""
|
|
132
|
+
if record := self.red_gate(number, slot, commit):
|
|
133
|
+
raise Blocked(f"the gate is red on {commit[:7]}, record {record}")
|
|
134
|
+
|
|
135
|
+
def red_gate(self, number: str, slot: str, commit: str) -> str:
|
|
136
|
+
"""`gate`, for a caller that acts on a red gate: returns its record, or "" when the gate is green."""
|
|
132
137
|
if commit in _gate_record(self.state.read(), number)["green_gates"]:
|
|
133
|
-
return
|
|
138
|
+
return ""
|
|
134
139
|
at = driver_slots.head(slot)
|
|
135
140
|
if at != commit:
|
|
136
141
|
raise Blocked(f"the slot is at {at[:7]}, not at {commit[:7]}: the gate did not run")
|
|
@@ -143,8 +148,9 @@ class Run:
|
|
|
143
148
|
|
|
144
149
|
self.state.update(count)
|
|
145
150
|
if not green:
|
|
146
|
-
|
|
151
|
+
return str(record)
|
|
147
152
|
ledger(f"{_owner(number)}: the gate is green on {commit[:7]}, record {record}.")
|
|
153
|
+
return ""
|
|
148
154
|
|
|
149
155
|
def restore_locked_files(self, number: str, slot: str) -> list[str]:
|
|
150
156
|
"""Before a test-writer correction: put each locked file the implementer changed back to its locked
|
|
@@ -10,6 +10,7 @@ Checks: {correctness}
|
|
|
10
10
|
Read the checks file and apply each of its checks to the diff of the range, with the Risk line of the ticket first: races, idempotency, time, authorization, persistence keys and identities, migrations, error paths.
|
|
11
11
|
The output section of the checks file does not apply here: your findings go into the report.
|
|
12
12
|
Read file content from git at {start_sha} (`git diff`, `git show {start_sha}:<path>`), never from the working tree, and run no tests: another agent makes temporary edits in this worktree while you work.
|
|
13
|
+
The one exception is the input of the driver, which git can lack because `.agent/` is ignored: read the spec, the ticket and the findings file from the working tree. The other agent does not edit them.
|
|
13
14
|
Report only a defect that you can show with a concrete sequence of events. `mutations` is optional for this pass.
|
|
14
15
|
Give each finding a severity (critical, important or minor), the file and line, a title, the evidence, and a kind: `code`, `test`, or `fixture` when a locked test encodes a stale path, format or setup detail.
|
|
15
16
|
Do not commit and do not dispatch subagents. Leave the tree as you found it.
|
|
@@ -22,11 +23,11 @@ Write one JSON report at {report}. When your sandbox is read-only, give the JSON
|
|
|
22
23
|
"required": ["status", "commits", "files", "concerns", "findings"],
|
|
23
24
|
"properties": {
|
|
24
25
|
"status": {"enum": ["DONE", "DONE_WITH_CONCERNS", "NEEDS_CONTEXT", "BLOCKED"]},
|
|
25
|
-
"commits": {"type": "array", "items": {"type": "string", "pattern": "^[0-9a-f]{7,40}$"}},
|
|
26
|
+
"commits": {"description": "Only the commits you created. A review creates none, so this list is empty: never list a commit of the range.", "type": "array", "items": {"type": "string", "pattern": "^[0-9a-f]{7,40}$"}},
|
|
26
27
|
"files": {"type": "array", "items": {"type": "string"}},
|
|
27
28
|
"concerns": {"type": "array", "items": {"type": "string"}},
|
|
28
29
|
"findings": {"type": "array", "items": {"type": "object", "required": ["severity", "file", "line", "title", "evidence", "kind"], "properties": {"severity": {"enum": ["critical", "important", "minor"]}, "file": {"type": "string"}, "line": {"type": "integer"}, "title": {"type": "string", "minLength": 1}, "evidence": {"type": "string", "minLength": 1}, "kind": {"enum": ["code", "fixture", "test"]}}}},
|
|
29
|
-
"mutations": {"type": "array", "items": {"type": "object", "required": ["test", "file", "line", "red"], "properties": {"test": {"type": "string", "minLength": 1}, "file": {"type": "string", "minLength": 1}, "line": {"type": "integer"}, "red": {"type": "string", "minLength": 1}}}}
|
|
30
|
+
"mutations": {"description": "Optional for this pass.", "type": "array", "items": {"type": "object", "required": ["test", "file", "line", "red"], "properties": {"test": {"type": "string", "minLength": 1}, "file": {"type": "string", "minLength": 1}, "line": {"type": "integer"}, "red": {"type": "string", "minLength": 1}}}}
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
```
|
|
@@ -10,6 +10,7 @@ Findings of the review: {findings}
|
|
|
10
10
|
Fix each finding of severity critical or important in the findings file. Leave the minor findings: the final review gets them. Leave each finding of kind `fixture`: the test-writer corrects those.
|
|
11
11
|
Never edit a locked file. The one exception is a finding `locked file changed`: make that file equal to its content at {test_writer_sha}.
|
|
12
12
|
Use targeted tests while you work. The driver runs the gate on your final commit, and a re-review reads the lines you changed.
|
|
13
|
+
A finding from the `gate` is a red gate: its file is the gate record. Read the output there and make the gate green.
|
|
13
14
|
Commit your work on the checked-out ticket branch. Do not dispatch subagents.
|
|
14
15
|
In `ticket`, give the number of the ticket that owns your commits. In `green`, give the summary lines of the passing test output.
|
|
15
16
|
|
|
@@ -10,7 +10,7 @@ Test-writer report, when the ticket has a test-writer: {writer_report}
|
|
|
10
10
|
Check the diff against the ticket and the spec, then for quality. Check that each criterion has a locked test. Test each suspicion that the ticket names in its Suspicions line.
|
|
11
11
|
In round 1, for each locked test, break the production line the test protects, run the test, print its failing output, then revert the edit. A test that stays green is a critical finding. Run again each mutation that the test-writer report names in `concerns`.
|
|
12
12
|
A mutation proof is keyed to its test and its production line: a later round repeats it only when that line changed.
|
|
13
|
-
In `mutations`, give for each proof the test
|
|
13
|
+
In `mutations`, give for each proof the test as `<locked file>::<test name>`, the production file and line, and one failure line copied exactly from the output you printed.
|
|
14
14
|
Give each finding a severity (critical, important or minor), the file and line, a title, the evidence, and a kind: `code`, `test`, or `fixture` when a locked test encodes a stale path, format or setup detail.
|
|
15
15
|
Do not commit and do not dispatch subagents. Leave the tree as you found it.
|
|
16
16
|
|
|
@@ -22,11 +22,11 @@ Write one JSON report at {report}. When your sandbox is read-only, give the JSON
|
|
|
22
22
|
"required": ["status", "commits", "files", "concerns", "findings"],
|
|
23
23
|
"properties": {
|
|
24
24
|
"status": {"enum": ["DONE", "DONE_WITH_CONCERNS", "NEEDS_CONTEXT", "BLOCKED"]},
|
|
25
|
-
"commits": {"type": "array", "items": {"type": "string", "pattern": "^[0-9a-f]{7,40}$"}},
|
|
25
|
+
"commits": {"description": "Only the commits you created. A review creates none, so this list is empty: never list a commit of the range.", "type": "array", "items": {"type": "string", "pattern": "^[0-9a-f]{7,40}$"}},
|
|
26
26
|
"files": {"type": "array", "items": {"type": "string"}},
|
|
27
27
|
"concerns": {"type": "array", "items": {"type": "string"}},
|
|
28
28
|
"findings": {"type": "array", "items": {"type": "object", "required": ["severity", "file", "line", "title", "evidence", "kind"], "properties": {"severity": {"enum": ["critical", "important", "minor"]}, "file": {"type": "string"}, "line": {"type": "integer"}, "title": {"type": "string", "minLength": 1}, "evidence": {"type": "string", "minLength": 1}, "kind": {"enum": ["code", "fixture", "test"]}}}},
|
|
29
|
-
"mutations": {"type": "array", "items": {"type": "object", "required": ["test", "file", "line", "red"], "properties": {"test": {"type": "string", "minLength": 1}, "file": {"type": "string", "minLength": 1}, "line": {"type": "integer"}, "red": {"type": "string", "minLength": 1}}}}
|
|
29
|
+
"mutations": {"description": "Required in the first review of a tip: one proof or more for each locked test file, with `test` as `<locked file>::<test name>`. A review scoped to a fix gives the proofs that it names.", "type": "array", "items": {"type": "object", "required": ["test", "file", "line", "red"], "properties": {"test": {"type": "string", "minLength": 1}, "file": {"type": "string", "minLength": 1}, "line": {"type": "integer"}, "red": {"type": "string", "minLength": 1}}}}
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
```
|
|
@@ -311,6 +311,34 @@ def test_four_rounds_then_blocked_with_the_escalation_at_round_3(tmp_path):
|
|
|
311
311
|
assert rig.state()["tickets"]["03"]["phase"] == "merged"
|
|
312
312
|
|
|
313
313
|
|
|
314
|
+
def test_a_red_gate_on_a_fixer_commit_starts_the_next_round_with_the_record_as_evidence(tmp_path):
|
|
315
|
+
"""Issue 64: the gate of a fix round is red. That is a finding for the next round, not a BLOCKED ticket."""
|
|
316
|
+
rig = Inside(tmp_path, gate={"subjects": ["fix: 01 round 1"]})
|
|
317
|
+
rig.run(plan(**{"01/reviewer": [review("01", [STUCK], lines=(2,)), review("01", lines=(2,))],
|
|
318
|
+
"01/fixer": [fix("01", 1), fix("01", 2, files={"demo/other.py": "x = 1\n"})]}))
|
|
319
|
+
assert rig.code == 0, rig.ledger()
|
|
320
|
+
assert [call["role"] for call in rig.calls("01")] == ["test-writer", "implementer", "reviewer", "fixer", "fixer",
|
|
321
|
+
"reviewer"]
|
|
322
|
+
first, second = (call["sha"] for call in rig.calls("01", "fixer"))
|
|
323
|
+
record = f".agent/implement/01/gate-{first[:7]}.log"
|
|
324
|
+
(gate,) = json.loads((rig.folder() / "findings-1.json").read_text())
|
|
325
|
+
assert (gate["source"], gate["severity"], gate["kind"]) == ("gate", "critical", "code")
|
|
326
|
+
assert gate["title"] == f"the gate is red on {first[:7]}" and record in gate["evidence"]
|
|
327
|
+
assert rig.led(f"Ticket 01: critical finding of kind code from the gate at {record}:0: the gate is red on {first[:7]}.")
|
|
328
|
+
assert "findings-1.json" in rig.prompt("fixer-2") and rig.state()["tickets"]["01"]["round"] == 2
|
|
329
|
+
# No review saw the red tip: the re-review covers both fixes, and the proof that round 1 made due stays due.
|
|
330
|
+
again = rig.prompt("reviewer-2")
|
|
331
|
+
assert f"The fix is {rig.sha('01', 'implementer')}..{second}" in again and "demo/export.py:2" in again
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def test_a_red_gate_on_the_fixer_commit_of_round_4_blocks_the_ticket(tmp_path):
|
|
335
|
+
rig = Inside(tmp_path, gate={"subjects": ["fix: 01 round 4"]})
|
|
336
|
+
rig.run(plan(**{"01/reviewer": [review("01", [STUCK])] * 4, "01/fixer": [fix("01", n) for n in (1, 2, 3, 4)]}))
|
|
337
|
+
last = rig.calls("01", "fixer")[-1]["sha"]
|
|
338
|
+
assert f"BLOCKED at the gate: the gate is red on {last[:7]}" in blocked(rig)
|
|
339
|
+
assert len(rig.calls("01", "reviewer")) == 4 and not (rig.folder() / "findings-4.json").exists()
|
|
340
|
+
|
|
341
|
+
|
|
314
342
|
def test_round_3_stays_on_its_entry_when_the_implementer_has_no_next_entry(tmp_path):
|
|
315
343
|
rig = Inside(tmp_path, ONE, {"implementer": [OPUS]})
|
|
316
344
|
rig.run(plan(**{"01/reviewer": [review("01", [STUCK])] * 3 + [review("01")], "01/fixer": [fix("01", n) for n in (1, 2, 3)]}))
|
|
@@ -516,6 +544,32 @@ def test_the_correctness_prompt_keeps_the_agent_off_the_tree_the_reviewer_edits(
|
|
|
516
544
|
assert "run no tests" in text
|
|
517
545
|
|
|
518
546
|
|
|
547
|
+
def test_the_correctness_pass_reads_the_untracked_inputs_of_the_driver_from_the_working_tree(tmp_path):
|
|
548
|
+
"""Issue 66: the ticket and the findings files are in the ignored .agent/, so git at the tip has none of them."""
|
|
549
|
+
rig = Inside(tmp_path, RISKY, {"correctness": [SOL, OPUS], "implementer": [OPUS]})
|
|
550
|
+
rig.run(plan(**{"02/correctness": [correct([finding("C-IMPORTANT")])], "02/fixer": [fix("02", 1)]}))
|
|
551
|
+
assert rig.code == 0, rig.ledger()
|
|
552
|
+
again = rig.prompt("correctness-1", "02")
|
|
553
|
+
inputs = (".agent/tickets/02-quote-fields.md", ".agent/implement/02/findings-0.json")
|
|
554
|
+
assert all(path in again for path in inputs), again
|
|
555
|
+
tracked = subprocess.run(["git", "-C", str(rig.repo), "ls-files", *inputs], capture_output=True, text=True).stdout
|
|
556
|
+
assert tracked == ""
|
|
557
|
+
assert "read the spec, the ticket and the findings file from the working tree" in again
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
@pytest.mark.parametrize("role", ["reviewer", "correctness"])
|
|
561
|
+
def test_the_schema_of_a_review_says_that_commits_holds_only_the_agents_own_commits(role):
|
|
562
|
+
"""Issue 66: a reviewer that lists a commit of the range costs a rerun (`is not a new commit`)."""
|
|
563
|
+
description = driver_report.schema(role)["properties"]["commits"]["description"]
|
|
564
|
+
assert "only the commits you created" in description.lower() and "empty" in description
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def test_the_reviewer_schema_asks_for_one_proof_for_each_locked_test_file_in_the_first_review():
|
|
568
|
+
"""Issue 66: a first review with no proof for a locked test file costs a rerun."""
|
|
569
|
+
description = driver_report.schema("reviewer")["properties"]["mutations"]["description"]
|
|
570
|
+
assert "each locked test file" in description and "`<locked file>::<test name>`" in description
|
|
571
|
+
|
|
572
|
+
|
|
519
573
|
def test_git_identity_is_not_needed_to_open_the_merge_for_the_conflict_agent(tmp_path):
|
|
520
574
|
import driver_merge
|
|
521
575
|
rig = Inside(tmp_path)
|
|
@@ -102,7 +102,8 @@ def git(*a):
|
|
|
102
102
|
conf = json.loads((rig / "gate.json").read_text())
|
|
103
103
|
(rig / "gates").mkdir(exist_ok=True)
|
|
104
104
|
branch = git("rev-parse", "--abbrev-ref", "HEAD")
|
|
105
|
-
|
|
105
|
+
subject = git("log", "-1", "--format=%s")
|
|
106
|
+
code = 1 if conf.get("all") or branch in conf.get("red", []) or subject in conf.get("subjects", []) else 0
|
|
106
107
|
rec = dict(t0=time.time(), cwd=os.path.realpath(os.getcwd()), commit=git("rev-parse", "HEAD"), branch=branch,
|
|
107
108
|
status=git("status", "--short"), base=os.environ.get("IMPLEMENT_SPEC_BASE"),
|
|
108
109
|
pycache=os.environ.get("PYTHONPYCACHEPREFIX"), exit=code)
|
package/skills-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"generatedAt": "2026-10-15T17:11:47.113Z",
|
|
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.12",
|
|
50
|
+
"hash": "sha256-d50cbfcb02b148c9f122d16333274b2d758efc8a1b35e0cf54eb831f34eba6de",
|
|
51
51
|
"description": "Implement a specification in code."
|
|
52
52
|
},
|
|
53
53
|
{
|