@jesdi/skills 0.0.44 → 0.0.46

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.
Files changed (30) hide show
  1. package/package.json +1 -1
  2. package/skills/implement-spec/ARCHITECTURE.md +13 -6
  3. package/skills/implement-spec/README.md +9 -6
  4. package/skills/implement-spec/driver.py +82 -40
  5. package/skills/implement-spec/driver_final.py +9 -8
  6. package/skills/implement-spec/driver_fixloop.py +4 -1
  7. package/skills/implement-spec/driver_gate.py +5 -1
  8. package/skills/implement-spec/driver_legacy.py +92 -0
  9. package/skills/implement-spec/driver_merge.py +1 -5
  10. package/skills/implement-spec/driver_names.py +32 -0
  11. package/skills/implement-spec/driver_review.py +24 -2
  12. package/skills/implement-spec/driver_run.py +24 -13
  13. package/skills/implement-spec/driver_slots.py +13 -0
  14. package/skills/implement-spec/driver_state.py +12 -13
  15. package/skills/implement-spec/prompts/correctness.md +2 -0
  16. package/skills/implement-spec/prompts/final-reviewer.md +3 -1
  17. package/skills/implement-spec/prompts/pr-writer.md +1 -0
  18. package/skills/implement-spec/prompts/reviewer.md +2 -0
  19. package/skills/implement-spec/tests/test_agents.py +10 -10
  20. package/skills/implement-spec/tests/test_amendments.py +2 -2
  21. package/skills/implement-spec/tests/test_decisions.py +4 -4
  22. package/skills/implement-spec/tests/test_driver.py +183 -23
  23. package/skills/implement-spec/tests/test_final.py +5 -5
  24. package/skills/implement-spec/tests/test_fixloop.py +22 -5
  25. package/skills/implement-spec/tests/test_landing.py +26 -23
  26. package/skills/implement-spec/tests/test_legacy.py +139 -0
  27. package/skills/implement-spec/tests/test_stops.py +1 -1
  28. package/skills/implement-spec/tests/test_tidy.py +2 -2
  29. package/skills/implement-spec/tests/test_walk.py +10 -10
  30. package/skills-manifest.json +3 -3
@@ -0,0 +1,139 @@
1
+ """The names of a run, and the take-over of a run that an older driver started (driver_names, driver_legacy)."""
2
+ import subprocess
3
+
4
+ import driver_legacy
5
+ import driver_state
6
+ from conftest import GIT
7
+ from driver_names import Names
8
+
9
+
10
+ def sha(ref="HEAD"):
11
+ return subprocess.run(["git", "rev-parse", "-q", "--verify", ref], capture_output=True, text=True).stdout.strip()
12
+
13
+
14
+ def test_the_names_of_a_run_carry_its_pr_branch_as_one_ref_component(repo, tmp_path):
15
+ """Structure review: git refs are a tree, so `ticket/feat/01` and `ticket/feat/01/02` cannot both exist. The PR
16
+ branch is one escaped component, so runs on two PR branches never collide."""
17
+ names = Names("feat/x")
18
+ assert (names.ticket("01"), names.recovery) == ("ticket/feat%2Fx/01", "refs/implement-spec/feat%2Fx/recovery")
19
+ assert names.lock_file(tmp_path) == tmp_path / "implement-spec/feat%2Fx.lock"
20
+ assert Names("a%2Fb").ticket("01") != Names("a/b").ticket("01")
21
+ repo()
22
+ for name in (Names("feat").ticket("01"), Names("feat/01").ticket("02"), Names("x/recovery").ticket("01")):
23
+ subprocess.run(["git", "branch", name], check=True)
24
+ subprocess.run(["git", "update-ref", "refs/implement/recovery", "HEAD"], check=True) # of an older driver
25
+ for pr in ("feat", "feat/recovery", "recovery"):
26
+ subprocess.run(["git", "update-ref", Names(pr).recovery, "HEAD"], check=True)
27
+ assert sha(Names("feat/recovery").recovery) == sha()
28
+
29
+
30
+ def older_run(repo, tmp_path):
31
+ """A repository with the start of a run, its recorded tip on `ticket/01` and the state.json of an older driver:
32
+ no `schema`."""
33
+ repo()
34
+ start = sha()
35
+ subprocess.run([*GIT, "commit", "-q", "--allow-empty", "-m", "work"], check=True)
36
+ tip = sha()
37
+ subprocess.run(["git", "branch", "ticket/01", tip], check=True)
38
+ state = driver_state.State(tmp_path / ".agent/implement/state.json")
39
+ state.update(lambda s: s.update(
40
+ run={"pr_branch": "feat/x", "start": start, "final": {"recovery": None}},
41
+ tickets={"01": {"phase": "review", "tip": tip}, "02": {"phase": "pending", "tip": None}}))
42
+ return state, start, tip
43
+
44
+
45
+ def resume(state, tmp_path):
46
+ """driver_legacy.resume; (the errors, the ledger lines)."""
47
+ errors = driver_legacy.resume(state)
48
+ ledger = tmp_path / ".agent/implement/ledger.md"
49
+ return errors, ledger.read_text().splitlines() if ledger.exists() else []
50
+
51
+
52
+ def edit(state, ticket=None, **run):
53
+ """Change the record of the run and of ticket 01, and take the `schema` away: the run is the older driver's
54
+ again."""
55
+ state.update(lambda s: (s["run"].update(run), s["run"].pop("schema", None), s["tickets"]["01"].update(ticket or {})))
56
+
57
+
58
+ def test_resume_renames_the_ticket_branches_of_this_run_only_and_marks_the_run(repo, tmp_path):
59
+ """A `ticket/NN` is this run's only when it holds the start of the run and the recorded tip: a `ticket/NN` of
60
+ another run stays as it is."""
61
+ state, start, tip = older_run(repo, tmp_path)
62
+ subprocess.run(["git", "branch", "-f", "ticket/01", start], check=True)
63
+ assert resume(state, tmp_path) == ([], []) # it does not hold the recorded tip
64
+ subprocess.run([*GIT, "checkout", "-q", "--orphan", "other"], check=True)
65
+ subprocess.run([*GIT, "commit", "-q", "--allow-empty", "-m", "other run"], check=True)
66
+ subprocess.run(["git", "branch", "-f", "ticket/01", "other"], check=True)
67
+ edit(state, {"tip": None})
68
+ assert resume(state, tmp_path) == ([], []) # it does not hold the start of the run
69
+ subprocess.run(["git", "branch", "-f", "ticket/01", tip], check=True)
70
+ for phase in ("pending", "merged"):
71
+ edit(state, {"tip": tip, "phase": phase})
72
+ assert resume(state, tmp_path) == ([], [])
73
+ edit(state, {"phase": "review"})
74
+ assert resume(state, tmp_path) == (
75
+ [], ["Ticket 01: renamed the branch ticket/01 of an older driver to ticket/feat%2Fx/01."])
76
+ assert (sha("refs/heads/ticket/01"), sha("refs/heads/ticket/feat%2Fx/01")) == ("", tip)
77
+ assert state.read()["run"]["schema"] == driver_state.SCHEMA
78
+ subprocess.run(["git", "branch", "ticket/01", tip], check=True)
79
+ assert resume(state, tmp_path)[0] == [] and sha("refs/heads/ticket/01") == tip # a marked run is not touched
80
+
81
+
82
+ def test_resume_keeps_a_ticket_branch_that_a_worktree_outside_the_run_has_checked_out(repo, tmp_path):
83
+ """Correctness review: a common start does not prove whose branch it is. A `ticket/NN` that another worktree has
84
+ checked out is that worktree's."""
85
+ state, _, tip = older_run(repo, tmp_path)
86
+ subprocess.run(["git", "worktree", "add", "-q", str(tmp_path.parent / f"{tmp_path.name}-other"), "ticket/01"],
87
+ check=True)
88
+ assert resume(state, tmp_path) == ([], []) and sha("refs/heads/ticket/01") == tip
89
+ edit(state)
90
+ subprocess.run(["git", "worktree", "remove", "--force", str(tmp_path.parent / f"{tmp_path.name}-other")],
91
+ check=True)
92
+ subprocess.run(["git", "worktree", "add", "-q", str(tmp_path / ".agent/worktrees/slot-2"), "ticket/01"],
93
+ check=True)
94
+ assert resume(state, tmp_path)[1] == ["Ticket 01: renamed the branch ticket/01 of an older driver to "
95
+ "ticket/feat%2Fx/01."] # a slot of this run
96
+
97
+
98
+ def test_resume_stops_on_a_rename_that_git_refuses_and_loses_nothing(repo, tmp_path):
99
+ state, _, _ = older_run(repo, tmp_path)
100
+ subprocess.run(["git", "branch", "ticket/feat%2Fx"], check=True) # a branch where the folder of the new name goes
101
+ errors, ledger = resume(state, tmp_path)
102
+ assert ledger == [] and len(errors) == 1 and "schema" not in state.read()["run"]
103
+ assert errors[0].startswith("cannot rename the branch ticket/01 of an older driver to ticket/feat%2Fx/01: ")
104
+ assert sha("refs/heads/ticket/01")
105
+
106
+
107
+ def test_resume_moves_the_recovery_ref_that_this_run_recorded(repo, tmp_path):
108
+ state, start, tip = older_run(repo, tmp_path)
109
+ subprocess.run(["git", "branch", "-D", "-q", "ticket/01"], check=True)
110
+ subprocess.run(["git", "update-ref", "refs/implement/recovery", start], check=True)
111
+ edit(state, final={"recovery": tip})
112
+ assert resume(state, tmp_path) == ([], []) # it holds another commit: not this run's
113
+ edit(state, final={"recovery": start})
114
+ assert resume(state, tmp_path) == (
115
+ [], ["Recovery point: moved refs/implement/recovery of an older driver to refs/implement-spec/feat%2Fx/recovery."])
116
+ assert (sha("refs/implement/recovery"), sha("refs/implement-spec/feat%2Fx/recovery")) == ("", start)
117
+
118
+
119
+ def test_resume_keeps_the_recovery_ref_when_git_refuses_the_new_one(repo, tmp_path):
120
+ state, start, _ = older_run(repo, tmp_path)
121
+ subprocess.run(["git", "branch", "-D", "-q", "ticket/01"], check=True)
122
+ subprocess.run(["git", "update-ref", "refs/implement/recovery", start], check=True)
123
+ subprocess.run(["git", "update-ref", "refs/implement-spec/feat%2Fx", start], check=True) # a file where a folder goes
124
+ edit(state, final={"recovery": start})
125
+ errors, ledger = resume(state, tmp_path)
126
+ assert ledger == [] and errors[0].startswith("cannot move refs/implement/recovery of an older driver to ")
127
+ assert sha("refs/implement/recovery") == start
128
+
129
+
130
+ def test_the_lock_of_an_older_driver_is_shared_by_newer_drivers_and_excludes_an_older_one(tmp_path):
131
+ """Correctness review: an older driver holds `implement-spec.lock` alone for its whole run."""
132
+ (one, _), (two, _) = driver_legacy.lock(tmp_path), driver_legacy.lock(tmp_path)
133
+ with one, two:
134
+ assert driver_state.lock(tmp_path / "implement-spec.lock") is None
135
+ older = driver_state.lock(tmp_path / "implement-spec.lock")
136
+ assert older is not None and driver_legacy.lock(tmp_path) == (None, (
137
+ f"a driver of an older version runs in this repository: it holds the lock {tmp_path / 'implement-spec.lock'}. "
138
+ "Wait until it ends, then run the driver again."))
139
+ older.close()
@@ -266,7 +266,7 @@ def test_unattended_rerun_with_a_submitted_answers_file_at_the_current_revision_
266
266
 
267
267
  def test_unattended_blocked_ticket_ends_with_stage_json_blocked_naming_it_and_exit_1(tmp_path):
268
268
  from test_final import Final
269
- rig = Final(tmp_path, gate={"red": ["ticket/01"]})
269
+ rig = Final(tmp_path, gate={"red": ["ticket/feat%2Fcsv/01"]})
270
270
  rig.run(plan(), *UNATTENDED)
271
271
  assert rig.code == 1, rig.output()
272
272
  assert stage(rig) == {"stage": "implement", "status": "blocked", "blocked": ["01"]}
@@ -78,7 +78,7 @@ def test_a_local_run_stops_before_the_push_and_a_push_rerun_pushes_and_opens_the
78
78
  assert [(r["subject"], r["body"].strip()) for r in rows] == [
79
79
  (f"feat({nn}): {TITLES[nn]}", f"Ticket: {FILES[nn]}") for nn in order] and order[2] == "02"
80
80
  assert len(rig.commits(rig.start, rig.recovery())) == 6
81
- assert ref(rig.repo, "feat/csv^{tree}") == ref(rig.repo, "refs/implement/recovery^{tree}")
81
+ assert ref(rig.repo, "feat/csv^{tree}") == ref(rig.repo, "refs/implement-spec/feat%2Fcsv/recovery^{tree}")
82
82
  assert rig.led("Recovery point: feat/csv was at " + rig.recovery(), ref(rig.repo, "feat/csv^{tree}"))
83
83
  assert rig.led("End of run: final-reviewer ran on", FABLE) and rig.led("End of run: the gate is green on " + tip[:7])
84
84
  assert {nn: e["tip"] for nn, e in rig.state()["tickets"].items()} == {nn: r["sha"] for nn, r in zip(order, rows)}
@@ -111,7 +111,7 @@ def test_a_local_run_stops_before_the_push_and_a_push_rerun_pushes_and_opens_the
111
111
 
112
112
 
113
113
  def test_the_final_review_does_not_start_while_a_ticket_is_not_merged_and_the_ledger_says_so_once(tmp_path):
114
- rig = Inside(tmp_path, THREE, gate={"red": ["ticket/01"]})
114
+ rig = Inside(tmp_path, THREE, gate={"red": ["ticket/feat%2Fcsv/01"]})
115
115
  rig.run(plan())
116
116
  rig.run(plan(), *UNATTENDED)
117
117
  assert rig.code == 1 and not rig.final_calls("final-reviewer") and not rig.recovery()
@@ -12,7 +12,7 @@ Interface choices (ticket 03):
12
12
  `green_gates` holds full SHAs (a dict with a `commit` key is accepted too).
13
13
 
14
14
  Ticket 08: this fixture has no `origin`, and a run that merges every ticket now ends with the tidy, one gate on the
15
- tidied tip and the final review. The history as the walk made it is at `refs/implement/recovery`: `Rig.walked()` is
15
+ tidied tip and the final review. The history as the walk made it is at `refs/implement-spec/feat%2Fcsv/recovery`: `Rig.walked()` is
16
16
  that tip, `Rig.pr_subjects()` reads it, and `Rig.walk_gates()` leaves out the one gate of the end of run.
17
17
  """
18
18
  import json
@@ -234,7 +234,7 @@ class Rig:
234
234
 
235
235
  def walked(self):
236
236
  """The PR tip as the walk left it: the recovery point of the end-of-run tidy, or the branch with no tidy."""
237
- out = subprocess.run(["git", "rev-parse", "--verify", "-q", "refs/implement/recovery"], cwd=self.repo,
237
+ out = subprocess.run(["git", "rev-parse", "--verify", "-q", "refs/implement-spec/feat%2Fcsv/recovery"], cwd=self.repo,
238
238
  capture_output=True, text=True).stdout.strip()
239
239
  return out or git(self.repo, "rev-parse", "feat/csv").strip()
240
240
 
@@ -247,7 +247,7 @@ class Rig:
247
247
  return git(self.repo, "log", "--format=%s", self.walked()).splitlines()
248
248
 
249
249
  def branches(self):
250
- return git(self.repo, "branch", "--list", "ticket/*").split()
250
+ return git(self.repo, "branch", "--list", "ticket/feat%2Fcsv/*").split()
251
251
 
252
252
  def output(self):
253
253
  return f"exit {self.code}\n{self.out}{self.err}"
@@ -271,9 +271,9 @@ def std(tmp_path_factory):
271
271
 
272
272
  @pytest.fixture(scope="module")
273
273
  def blocked(tmp_path_factory):
274
- """The default scenario with a red gate on ticket/01 that four fix rounds do not make green."""
274
+ """The default scenario with a red gate on ticket/feat%2Fcsv/01 that four fix rounds do not make green."""
275
275
  fixes = [fix("01", n) for n in (1, 2, 3, 4)]
276
- return Rig(tmp_path_factory.mktemp("blocked"), gate={"red": ["ticket/01"]}).run(plan(**{"01/fixer": fixes}))
276
+ return Rig(tmp_path_factory.mktemp("blocked"), gate={"red": ["ticket/feat%2Fcsv/01"]}).run(plan(**{"01/fixer": fixes}))
277
277
 
278
278
 
279
279
  def test_01_starts_in_slot_1_03_in_slot_2_and_02_after_the_merge_of_01(std):
@@ -281,7 +281,7 @@ def test_01_starts_in_slot_1_03_in_slot_2_and_02_after_the_merge_of_01(std):
281
281
  first = lambda nn: std.calls(nn)[0]
282
282
  assert first("01")["cwd"] == str(std.repo.resolve()) and first("03")["cwd"] == str(std.slot2)
283
283
  assert "feat: 01 rows" in first("02")["pr_log"] and "feat: 01 rows" in first("02")["log"]
284
- assert first("02")["t0"] > std.calls("01")[-1]["t1"] and first("02")["branch"] == "ticket/02"
284
+ assert first("02")["t0"] > std.calls("01")[-1]["t1"] and first("02")["branch"] == "ticket/feat%2Fcsv/02"
285
285
 
286
286
 
287
287
  def test_touches_overlap_holds_03_until_01_is_merged(tmp_path):
@@ -349,7 +349,7 @@ def test_red_gate_blocks_the_ticket_after_round_4_names_the_record_and_launches_
349
349
  assert [l for l in blocked.ledger() if "01" in l and "BLOCKED" in l and f"gate-{last[:7]}.log" in l], blocked.ledger()
350
350
  assert [c["role"] for c in blocked.calls("01")] == ["test-writer", "implementer"] + ["fixer"] * 4
351
351
  assert blocked.state()["tickets"]["01"]["phase"] == "blocked"
352
- assert "ticket/01" in blocked.branches() and "feat: 01 rows" not in blocked.pr_subjects()
352
+ assert "ticket/feat%2Fcsv/01" in blocked.branches() and "feat: 01 rows" not in blocked.pr_subjects()
353
353
 
354
354
 
355
355
  def test_blocked_ticket_stops_only_the_tickets_it_blocks(blocked):
@@ -372,7 +372,7 @@ def test_non_fast_forward_merge_is_a_plumbing_merge_commit_gated_once(tmp_path):
372
372
  commits = [g["commit"] for g in rig.walk_gates()]
373
373
  assert sorted(commits) == sorted([a01, a03, tip]), commits
374
374
  waited = rig.calls("01", "implementer")[0]
375
- assert waited["after_wait"]["branch"] == "ticket/01" and waited["after_wait"]["head"] == waited["head"]
375
+ assert waited["after_wait"]["branch"] == "ticket/feat%2Fcsv/01" and waited["after_wait"]["head"] == waited["head"]
376
376
  assert waited["after_wait"]["status"] == ""
377
377
  assert git(rig.repo, "rev-parse", "--abbrev-ref", "HEAD").strip() == "feat/csv"
378
378
  assert git(rig.repo, "status", "--short") == "" and (rig.repo / "demo/cli.py").exists()
@@ -439,7 +439,7 @@ def test_resume_returns_a_ticket_with_an_unverifiable_tip_to_implement_from_the_
439
439
  commit="feat: 01 again")]})
440
440
  assert rig.code == 0, rig.output()
441
441
  again = rig.calls("01", "implementer")[1]
442
- assert (again["branch"], again["head"]) == ("ticket/01", tw_sha) and len(rig.calls("01", "test-writer")) == 1
442
+ assert (again["branch"], again["head"]) == ("ticket/feat%2Fcsv/01", tw_sha) and len(rig.calls("01", "test-writer")) == 1
443
443
  assert [l for l in rig.ledger()[len(before):] if "01" in l and fake[:7] in l], rig.ledger()
444
444
  assert "feat: 01 again" in rig.pr_subjects()
445
445
 
@@ -450,7 +450,7 @@ def test_resume_keeps_a_settled_gate_and_runs_no_gate_again(tmp_path):
450
450
  state["tickets"]["01"].update(phase="merge", tip=a1, green_gates=[a1])
451
451
  (rig.repo / ".agent/implement/state.json").write_text(json.dumps(state))
452
452
  gates_before = len(rig.gates())
453
- rig.gate_conf(red=["ticket/01"]) # any gate run on the ticket would block it; the end of run gates feat/csv
453
+ rig.gate_conf(red=["ticket/feat%2Fcsv/01"]) # any gate run on the ticket would block it; the end of run gates feat/csv
454
454
  rig.run(plan())
455
455
  assert rig.code == 0, rig.output()
456
456
  assert len(rig.walk_gates()) == gates_before and rig.walked() == a1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-10-16T23:46:48.180Z",
3
+ "generatedAt": "2026-10-17T15:47:59.977Z",
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.20",
50
- "hash": "sha256-9c85f3c37deb0c2e9715172ccdfbb71ef65c066aa8e8c1505d9d2e968b84f0fd",
49
+ "version": "0.1.22",
50
+ "hash": "sha256-66e2dec93c2b4ca09e8ca6693307e5e92f46fe1bd9b0a44379e7d4a4bd82d3f9",
51
51
  "description": "Implement a specification in code."
52
52
  },
53
53
  {