@jesdi/skills 0.0.51 → 0.0.52

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jesdi/skills",
3
- "version": "0.0.51",
3
+ "version": "0.0.52",
4
4
  "description": "jesdi's agent skills (content package consumed by @jesdi/skills-cli)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -132,6 +132,9 @@ and do not know about the run, except `driver_run.py`, which the three deciding
132
132
  it blocks only after round 4. A red gate on a test-writer correction blocks.
133
133
  - The implementer and each fixer get the `--gate` command and run it before they report. The gate of the driver
134
134
  stays the source of truth: an agent's run of the gate counts for nothing.
135
+ - A finding that a correction reports in `not_addressed` goes to the fixer of the same round as kind `code`
136
+ (`driver_fixloop._correct` rewrites it in the findings file): the test needs a production change first, so a
137
+ second correction could not close it.
135
138
  - No test-writer correction runs after round 4 (`driver_fixloop._act_on`): its findings become `finding` questions
136
139
  with the others. A correction there would add red locked tests that no fix round makes green, and `accept` could
137
140
  not merge the ticket.
@@ -117,7 +117,7 @@ Rules for each agent:
117
117
 
118
118
  | Finding | What the driver does |
119
119
  |---|---|
120
- | kind `fixture`, any severity, or kind `test`, `critical` or `important`, on a locked file of the ticket | One test-writer correction, outside the fix rounds. The fixer may not edit a locked file, so a fix round cannot close a `test` finding there. The corrected files are the new locked versions, and the next review is a full review. A finding of this kind that is open after the correction blocks the ticket. No correction runs after round 4. |
120
+ | kind `fixture`, any severity, or kind `test`, `critical` or `important`, on a locked file of the ticket | One test-writer correction, outside the fix rounds. The fixer may not edit a locked file, so a fix round cannot close a `test` finding there. The corrected files are the new locked versions, and the next review is a full review. A finding that the correction reports in `not_addressed` (it needs a production change first) becomes a finding of kind `code`, with the reason as its evidence, and goes to a fix round at once; the ledger says it was not corrected. A finding of this kind that is open after the correction blocks the ticket. No correction runs after round 4. |
121
121
  | `critical` or `important` | One fix round. |
122
122
  | `minor` | It stays in the ledger and in the findings file. The final reviewer gets the list. |
123
123
 
@@ -176,7 +176,7 @@ Every report has `status` (`DONE`, `DONE_WITH_CONCERNS`, `NEEDS_CONTEXT`, `BLOCK
176
176
 
177
177
  | Role | Template | More fields | Checks after the schema |
178
178
  |---|---|---|---|
179
- | test-writer | `prompts/test-writer.md` | `locked` (test file paths), `red` (`test`, `line`) | The driver reads a test node id `<file>::<test>` in `locked` as its file. 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`. A correction (after `overrule-test`, or for a review finding) changes only the files that it lists in `locked` and the files that the driver restored before it. When the gate is red on the restore commit, a DONE correction lists a restored file in `locked`. |
179
+ | test-writer | `prompts/test-writer.md` | `locked` (test file paths), `red` (`test`, `line`) | The driver reads a test node id `<file>::<test>` in `locked` as its file. 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`. A correction (after `overrule-test`, or for a review finding) changes only the files that it lists in `locked` and the files that the driver restored before it. When the gate is red on the restore commit, a DONE correction lists a restored file in `locked`. Each `not_addressed[].finding` is the title of a finding of the correction; a DONE correction with each of its findings there needs no commit. |
180
180
  | implementer | `prompts/implementer.md` | `green` | With DONE: one commit or more. |
181
181
  | reviewer | `prompts/reviewer.md` | `findings` (`severity`, `file`, `line`, `title`, `evidence`, `kind`), `mutations` (`test`, `file`, `line`, `red`); `commits` is empty | In a full review (one with no fix range: round 1, and the review after a test-writer correction): `mutations` is required, with a proof for each locked test file (`test` is `<locked file>::<test name>`). Each `red` is in the log. |
182
182
  | correctness | `prompts/correctness.md` | the same as the reviewer; `commits` is empty | `mutations` is optional. |
@@ -104,8 +104,9 @@ def _review(job: Job) -> list[dict]:
104
104
  return found
105
105
 
106
106
 
107
- def _correct(job: Job, findings: list[dict]) -> None:
108
- """One test-writer correction for the findings on the locked tests. It is outside the fix rounds."""
107
+ def _correct(job: Job, findings: list[dict]) -> list[dict]:
108
+ """One test-writer correction for the findings on the locked tests. It is outside the fix rounds. Returns the
109
+ findings that it did not correct, as findings of kind code for the fixer (issue #111)."""
109
110
  run, number = job.run, job.ticket.number
110
111
  if job.corrected:
111
112
  titles = "; ".join(finding["title"] for finding in findings)
@@ -120,19 +121,39 @@ def _correct(job: Job, findings: list[dict]) -> None:
120
121
  job.previous = "" # no review has seen the restore commit
121
122
  ledger(f"Ticket {number}: the code does not need the change to {', '.join(restored)}: the restore closes it, "
122
123
  "with no correction.")
123
- return
124
+ return []
124
125
  values = {**job.pointers(job.round), "restored": " ".join(restored),
125
126
  "record": record and os.path.relpath(record, job.slot)}
126
127
  notes = ["correction"] + ["restored"] * bool(restored) + ["restored-red"] * bool(record)
127
128
  with during("the test-writer correction"):
128
129
  report = run.agent("test-writer", number, job.slot, values, notes=notes, fix_round=job.round + 1,
129
- check={"correction": True, "restored": restored, "restore_red": bool(record)})
130
+ check={"correction": True, "restored": restored, "restore_red": bool(record),
131
+ "findings": [finding["title"] for finding in findings]})
130
132
  job.tip, job.previous, job.corrected = driver_slots.head(job.slot), "", True
131
133
  entry = run.state.read()["tickets"][number]
132
134
  run.state.ticket(number, tip=job.tip, **driver_review.written(job.slot, entry, report["locked"], job.tip))
135
+ return _uncorrected(job, findings, report.get("not_addressed", []))
136
+
137
+
138
+ def _uncorrected(job: Job, findings: list[dict], not_addressed: list[dict]) -> list[dict]:
139
+ """Log what the correction did with each finding. Returns each one it did not correct as a finding of kind
140
+ code, with its reason as the evidence: the test needs a production change first, so a fixer closes it."""
141
+ number = job.ticket.number
142
+ reasons = {item["finding"]: item["reason"] for item in not_addressed}
133
143
  for finding in findings:
144
+ result = (f"The test-writer did not correct it: {reasons[finding['title']]}. It goes to the fixer as a finding "
145
+ "of kind code." if finding["title"] in reasons else f"The test-writer corrected it at {job.tip[:7]}.")
134
146
  ledger(f"Ticket {number}: {finding['kind']} correction in {finding['file']}, outside the fix rounds: "
135
- f"{finding['title']}: {finding['evidence']}. The test-writer corrected it at {job.tip[:7]}.")
147
+ f"{finding['title']}: {finding['evidence']}. {result}")
148
+ code = {finding["title"]: {**finding, "kind": "code", "evidence": f"the test-writer did not correct it: {reason}"}
149
+ for finding in findings if (reason := reasons.get(finding["title"]))}
150
+ if code:
151
+ # The fixer reads the findings file of this review, so the finding is of kind code there too.
152
+ path = OUT / number / f"findings-{job.round}.json"
153
+ written = [code.get(finding["title"], finding) if finding in findings else finding
154
+ for finding in json.loads(path.read_text())]
155
+ path.write_text(json.dumps(written, indent=1) + "\n")
156
+ return list(code.values())
136
157
 
137
158
 
138
159
  def _fix(job: Job) -> None:
@@ -170,7 +191,7 @@ def _act_on(job: Job, found: list[dict]) -> bool:
170
191
  # A changed locked file is fixed first: a correction on top of it would make the changed file the reference.
171
192
  locked_file_changed = any(finding["source"] == "driver" for finding in to_fix)
172
193
  if to_correct and not locked_file_changed:
173
- _correct(job, to_correct)
194
+ to_fix += [finding for finding in _correct(job, to_correct) if finding["severity"] in driver_review.OPEN]
174
195
  if not to_fix:
175
196
  return False
176
197
  if not to_fix:
@@ -2,7 +2,7 @@
2
2
 
3
3
  context: repo (slot path), branch, start (HEAD when the role started), log, full (a full review: no fix range), locked,
4
4
  correction (a test-writer corrects locked tests), restored (locked files the driver put back before it),
5
- restore_red (the gate is red on the commit of that restore),
5
+ restore_red (the gate is red on the commit of that restore), findings (the titles a correction gets),
6
6
  owners (ticket numbers, for the final fix batch), merged (locked files of merged tickets, not its own), pr (tip)."""
7
7
  from __future__ import annotations
8
8
 
@@ -81,7 +81,8 @@ def _correction_errors(report: dict, context: dict) -> list[str]:
81
81
  errors.append("the gate is red on the restore commit, so a restored file fails with the code of this ticket: "
82
82
  "update that file and list it in `locked`, or report BLOCKED with a concern that starts with "
83
83
  "`locked test contradicts spec:`")
84
- return errors
84
+ return errors + [f"not_addressed: {item['finding']} is not the title of a finding of this correction"
85
+ for item in report.get("not_addressed", []) if item["finding"] not in context.get("findings", [])]
85
86
 
86
87
 
87
88
  def _merged_errors(report: dict, context: dict) -> list[str]:
@@ -3,4 +3,5 @@ This call is a correction of the locked tests, not a new test set. Read in {find
3
3
  A `fixture` finding: a locked test encodes a stale path, format, fixture layout or setup detail. Correct only that detail and keep what each test asserts.
4
4
  A `test` finding: a locked test is too weak, for example it stays green when the production line it protects is broken. Make the test assert the behavior of its criterion so that it fails on that break. Do not remove an assertion.
5
5
  For this you may read the implementation.
6
+ When a finding needs a production change first, so that no correct test can be green, leave the test as it is and add the finding to `not_addressed`: `finding` is its `title` as the findings file gives it, `reason` is the production change it needs. The driver sends it to the fixer as a finding of kind `code`. A correction whose findings are all in `not_addressed` may report DONE with no commit.
6
7
  The implementation exists, so a corrected test is green: for `red`, break the production line that the test protects, run the test, print its failing output, then revert the edit. In `locked`, list each test file you corrected: test file paths only, not test node ids. Unlike `mutations[].test` of the reviewer, `locked` takes no `::<test name>`.
@@ -25,9 +25,10 @@ Write one JSON report at {report} that matches this schema:
25
25
  "files": {"type": "array", "items": {"type": "string"}},
26
26
  "concerns": {"type": "array", "items": {"type": "string"}},
27
27
  "locked": {"type": "array", "items": {"type": "string"}},
28
- "red": {"type": "array", "items": {"type": "object", "required": ["test", "line"], "properties": {"test": {"type": "string", "minLength": 1}, "line": {"type": "string", "minLength": 1}}}}
28
+ "red": {"type": "array", "items": {"type": "object", "required": ["test", "line"], "properties": {"test": {"type": "string", "minLength": 1}, "line": {"type": "string", "minLength": 1}}}},
29
+ "not_addressed": {"type": "array", "items": {"type": "object", "required": ["finding", "reason"], "properties": {"finding": {"type": "string", "minLength": 1}, "reason": {"type": "string", "minLength": 1}}}}
29
30
  },
30
- "if": {"properties": {"status": {"enum": ["DONE", "DONE_WITH_CONCERNS"]}}},
31
+ "if": {"properties": {"status": {"enum": ["DONE", "DONE_WITH_CONCERNS"]}}, "not": {"required": ["not_addressed"], "properties": {"not_addressed": {"minItems": 1}}}},
31
32
  "then": {"properties": {"commits": {"minItems": 1}, "locked": {"minItems": 1}, "red": {"minItems": 1}}}
32
33
  }
33
34
  ```
@@ -177,6 +177,56 @@ def test_a_test_finding_on_a_locked_file_gets_the_test_writer_correction_not_a_f
177
177
  assert driver_review.route([vacuous, minor, elsewhere], ["tests/test_01.py"]) == ([vacuous], [elsewhere])
178
178
 
179
179
 
180
+ NEEDS_CODE = "needs production code: the code has no multi-PR view"
181
+
182
+
183
+ def not_addressing(*titles, **report):
184
+ """A correction of tests/test_01.py that reports `titles` under `not_addressed`."""
185
+ step = {**tw("01"), "files": {"tests/test_01.py": "# corrected\n"}, "commit": "test: 01 fixture fix"}
186
+ reasons = [{"finding": title, "reason": NEEDS_CODE} for title in titles]
187
+ return {**step, "report": {**step["report"], **report, "not_addressed": reasons}}
188
+
189
+
190
+ def test_a_test_finding_the_correction_did_not_address_goes_to_the_fixer_as_a_code_finding(tmp_path):
191
+ # Issue #111: the ledger said "corrected it at <sha>" for every finding, and the finding came back each review.
192
+ stale = finding("STALE", kind="fixture", file="tests/test_01.py")
193
+ vacuous = finding("VACUOUS", "critical", kind="test", file="tests/test_01.py")
194
+ rig = Inside(tmp_path).run(plan(**{"01/test-writer": [tw("01"), not_addressing("VACUOUS")],
195
+ "01/fixer": [fix("01", 1)],
196
+ "01/reviewer": [review("01", [stale, vacuous]), review("01")]}))
197
+ assert rig.code == 0, rig.ledger()
198
+ order = [call["role"] for call in rig.calls("01")]
199
+ assert order == ["test-writer", "implementer", "reviewer", "test-writer", "fixer", "reviewer"]
200
+ assert rig.led("STALE: STALE. The test-writer corrected it at")
201
+ assert rig.led("VACUOUS", "corrected it at") == []
202
+ assert rig.led(f"test correction in tests/test_01.py, outside the fix rounds: VACUOUS: VACUOUS. The test-writer "
203
+ f"did not correct it: {NEEDS_CODE}. It goes to the fixer as a finding of kind code.")
204
+ # The fixer reads the finding as one of kind code, with the reason of the test-writer as its evidence.
205
+ (routed,) = [f for f in json.loads((rig.folder() / "findings-0.json").read_text()) if f["title"] == "VACUOUS"]
206
+ assert (routed["kind"], routed["evidence"]) == ("code", f"the test-writer did not correct it: {NEEDS_CODE}")
207
+ assert rig.state()["tickets"]["01"]["round"] == 1 and "findings-0.json" in rig.prompt("fixer-1")
208
+
209
+
210
+ def test_a_correction_that_addresses_no_finding_needs_no_commit(tmp_path):
211
+ vacuous = finding("VACUOUS", "critical", kind="test", file="tests/test_01.py")
212
+ none = not_addressing("VACUOUS", commits=[], files=[], locked=[], red=[])
213
+ del none["commit"], none["files"]
214
+ rig = Inside(tmp_path).run(plan(**{"01/test-writer": [tw("01"), none], "01/fixer": [fix("01", 1)],
215
+ "01/reviewer": [review("01", [vacuous]), review("01")]}))
216
+ assert rig.code == 0, rig.ledger()
217
+ assert [call["role"] for call in rig.calls("01")].count("test-writer") == 2 # no rerun
218
+ assert rig.led("VACUOUS. The test-writer did not correct it") and rig.state()["tickets"]["01"]["round"] == 1
219
+
220
+
221
+ def test_a_correction_that_names_a_finding_it_was_not_given_is_rerun(tmp_path):
222
+ vacuous = finding("VACUOUS", "critical", kind="test", file="tests/test_01.py")
223
+ rig = Inside(tmp_path).run(plan(**{"01/test-writer": [tw("01"), not_addressing("OTHER"), not_addressing()],
224
+ "01/reviewer": [review("01", [vacuous]), review("01")]}))
225
+ assert rig.code == 0, rig.ledger()
226
+ assert "not_addressed: OTHER is not the title of a finding of this correction" in rig.prompt("test-writer-1")
227
+ assert rig.led("VACUOUS: VACUOUS. The test-writer corrected it at")
228
+
229
+
180
230
  def test_a_test_writer_that_lists_test_node_ids_in_locked_locks_their_files(tmp_path):
181
231
  # Issue #71: a node id `<file>::<test>` in `locked` names its file, for the first run and for a correction.
182
232
  vacuous = finding("VACUOUS", "critical", kind="test", file="tests/test_01.py")
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-10-19T10:48:11.958Z",
3
+ "generatedAt": "2026-10-19T11:13:23.574Z",
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.26",
50
- "hash": "sha256-40637f9172d94edb3ef009f91f58e27f7cd400ed15935c5a62654822a57208c5",
49
+ "version": "0.1.27",
50
+ "hash": "sha256-5cd78074157850b0abb3de812a51a911486bd5da29fe5f553a4838b25e61d30b",
51
51
  "description": "Implement a specification in code."
52
52
  },
53
53
  {